Blender V5.0
node_geo_boolean.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#include "BKE_instances.hh"
7
8#include "DNA_mesh_types.h"
9#include "DNA_object_types.h"
10
11#include "NOD_rna_define.hh"
12
14#include "UI_resources.hh"
15
17#include "GEO_mesh_boolean.hh"
18#include "GEO_randomize.hh"
19
20#include "node_geometry_util.hh"
21
23
25{
26 const bNode *node = b.node_or_null();
27
28 auto &first_geometry = b.add_input<decl::Geometry>("Mesh 1")
29 .only_realized_data()
30 .supported_type(GeometryComponent::Type::Mesh)
31 .description("Base mesh to subtract geometry from");
32
33 if (node != nullptr) {
37 b.add_input<decl::Geometry>("Mesh", "Mesh 2")
38 .supported_type(GeometryComponent::Type::Mesh)
39 .multi_input()
40 .description("Meshes to union or intersect");
41 break;
43 b.add_input<decl::Geometry>("Mesh 2")
44 .supported_type(GeometryComponent::Type::Mesh)
45 .multi_input()
46 .description("Mesh that is subtracted from the first mesh");
47 break;
48 }
49 }
50
51 auto make_mesh_arr = [](bNode &node) {
53 };
54 auto &self_intersect =
55 b.add_input<decl::Bool>("Self Intersection").make_available(make_mesh_arr);
56 auto &hole_tolerant = b.add_input<decl::Bool>("Hole Tolerant").make_available(make_mesh_arr);
57 b.add_output<decl::Geometry>("Mesh").propagate_all();
58 auto &output_edges =
59 b.add_output<decl::Bool>("Intersecting Edges").field_on_all().make_available(make_mesh_arr);
60
61 if (node != nullptr) {
62 const auto operation = geometry::boolean::Operation(node->custom1);
63 const auto solver = geometry::boolean::Solver(node->custom2);
64
65 output_edges.available(
67 self_intersect.available(solver == geometry::boolean::Solver::MeshArr);
68 hole_tolerant.available(solver == geometry::boolean::Solver::MeshArr);
69
70 switch (operation) {
73 first_geometry.available(false);
74 break;
76 break;
77 }
78 }
79}
80
81static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
82{
83 layout->prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
84 layout->prop(ptr, "solver", UI_ITEM_NONE, "", ICON_NONE);
85}
86
88 std::optional<std::string> intersecting_edges_id;
89};
90
91static void node_init(bNodeTree * /*tree*/, bNode *node)
92{
95}
96
98{
99 Array<short> map(mesh.totcol);
100 for (const int i : IndexRange(mesh.totcol)) {
101 Material *material = mesh.mat[i];
102 map[i] = material ? all_materials.index_of_or_add(material) : -1;
103 }
104 return map;
105}
106
108{
111 bool use_self = false;
112 bool hole_tolerant = false;
114 use_self = params.extract_input<bool>("Self Intersection");
115 hole_tolerant = params.extract_input<bool>("Hole Tolerant");
116 }
117
119 Vector<float4x4> transforms;
120 VectorSet<Material *> materials;
121 Vector<Array<short>> material_remaps;
122
123 GeometrySet set_a;
125 set_a = params.extract_input<GeometrySet>("Mesh 1");
126 /* Note that it technically wouldn't be necessary to realize the instances for the first
127 * geometry input, but the boolean code expects the first shape for the difference operation
128 * to be a single mesh. */
129 if (const Mesh *mesh_in_a = set_a.get_mesh()) {
130 meshes.append(mesh_in_a);
131 transforms.append(float4x4::identity());
132 if (mesh_in_a->totcol == 0) {
133 /* Necessary for faces using the default material when there are no material slots. */
134 materials.add(nullptr);
135 }
136 else {
137 materials.add_multiple({mesh_in_a->mat, mesh_in_a->totcol});
138 }
139 material_remaps.append({});
140 }
141 }
142
143 GeoNodesMultiInput<GeometrySet> geometry_sets =
144 params.extract_input<GeoNodesMultiInput<GeometrySet>>("Mesh 2");
145
146 for (const GeometrySet &geometry : geometry_sets.values) {
147 if (const Mesh *mesh = geometry.get_mesh()) {
148 meshes.append(mesh);
149 transforms.append(float4x4::identity());
150 material_remaps.append(calc_mesh_material_map(*mesh, materials));
151 }
152 if (const bke::Instances *instances = geometry.get_instances()) {
153 const Span<bke::InstanceReference> references = instances->references();
154 const Span<int> handles = instances->reference_handles();
155 const Span<float4x4> instance_transforms = instances->transforms();
156 for (const int i : handles.index_range()) {
157 const bke::InstanceReference &reference = references[handles[i]];
158 switch (reference.type()) {
161 reference.object());
162 if (const Mesh *mesh = object_geometry.get_mesh()) {
163 meshes.append(mesh);
164 transforms.append(instance_transforms[i]);
165 material_remaps.append(calc_mesh_material_map(*mesh, materials));
166 }
167 break;
168 }
170 if (const Mesh *mesh = reference.geometry_set().get_mesh()) {
171 meshes.append(mesh);
172 transforms.append(instance_transforms[i]);
173 material_remaps.append(calc_mesh_material_map(*mesh, materials));
174 }
175 break;
176 }
179 break;
180 }
181 }
182 }
183 }
184
186 /* Manifold remaps materials using realize_instances. */
187 material_remaps.resize(0);
188 }
189
190 AttributeOutputs attribute_outputs;
192 attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
193 "Intersecting Edges");
194 }
195
196 Vector<int> intersecting_edges;
198 op_params.boolean_mode = operation;
199 op_params.no_self_intersections = !use_self;
200 op_params.watertight = !hole_tolerant;
201 op_params.no_nested_components = true; /* TODO: make this configurable. */
204 meshes,
205 transforms,
206 material_remaps,
207 op_params,
208 solver,
209 attribute_outputs.intersecting_edges_id ? &intersecting_edges : nullptr,
210 &error);
212 params.error_message_add(NodeWarningType::Error, TIP_("An input was not manifold"));
213 }
215 params.error_message_add(NodeWarningType::Error,
216 TIP_("Boolean result is too big for solver to handle"));
217 }
219 params.error_message_add(NodeWarningType::Error,
220 TIP_("Boolean solver not available (compiled without it)"));
221 }
223 params.error_message_add(NodeWarningType::Error, TIP_("Unknown Boolean error"));
224 }
225 if (!result) {
226 params.set_default_remaining_outputs();
227 return;
228 }
229
230 MEM_SAFE_FREE(result->mat);
231 result->mat = MEM_malloc_arrayN<Material *>(size_t(materials.size()), __func__);
232 result->totcol = materials.size();
233 MutableSpan(result->mat, result->totcol).copy_from(materials);
234
235 /* Store intersecting edges in attribute. */
236 if (attribute_outputs.intersecting_edges_id) {
237 MutableAttributeAccessor attributes = result->attributes_for_write();
239 *attribute_outputs.intersecting_edges_id, AttrDomain::Edge);
240
241 selection.span.fill(false);
242 for (const int i : intersecting_edges) {
243 selection.span[i] = true;
244 }
245 selection.finish();
246 }
248
249 Vector<GeometrySet> all_geometries;
250 all_geometries.append(set_a);
251 all_geometries.extend(geometry_sets.values);
252
253 const std::array types_to_join = {GeometryComponent::Type::Edit};
254 GeometrySet result_geometry = geometry::join_geometries(
255 all_geometries, {}, std::make_optional(types_to_join));
256 result_geometry.replace_mesh(result);
257 result_geometry.name = set_a.name;
258
259 params.set_output("Mesh", std::move(result_geometry));
260}
261
262static void node_rna(StructRNA *srna)
263{
264 static const EnumPropertyItem rna_node_geometry_boolean_method_items[] = {
266 "INTERSECT",
267 0,
268 "Intersect",
269 "Keep the part of the mesh that is common between all operands"},
271 "UNION",
272 0,
273 "Union",
274 "Combine meshes in an additive way"},
276 "DIFFERENCE",
277 0,
278 "Difference",
279 "Combine meshes in a subtractive way"},
280 {0, nullptr, 0, nullptr, nullptr},
281 };
282 static const EnumPropertyItem rna_geometry_boolean_solver_items[] = {
284 "EXACT",
285 0,
286 "Exact",
287 "Slower solver with the best results for coplanar faces"},
289 "FLOAT",
290 0,
291 "Float",
292 "Simple solver with good performance, without support for overlapping geometry"},
294 "MANIFOLD",
295 0,
296 "Manifold",
297 "Fastest solver that works only on manifold meshes but gives better results"},
298 {0, nullptr, 0, nullptr, nullptr},
299 };
300
302 "operation",
303 "Operation",
304 "",
305 rna_node_geometry_boolean_method_items,
308
310 "solver",
311 "Solver",
312 "",
313 rna_geometry_boolean_solver_items,
316}
317
318static void node_register()
319{
320 static blender::bke::bNodeType ntype;
321 geo_node_type_base(&ntype, "GeometryNodeMeshBoolean", GEO_NODE_MESH_BOOLEAN);
322 ntype.ui_name = "Mesh Boolean";
323 ntype.ui_description = "Cut, subtract, or join multiple mesh inputs";
324 ntype.enum_name_legacy = "MESH_BOOLEAN";
326 ntype.declare = node_declare;
328 ntype.initfunc = node_init;
331
332 node_rna(ntype.rna_ext.srna);
333}
335
336} // namespace blender::nodes::node_geo_boolean_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_MESH_BOOLEAN
#define ELEM(...)
#define TIP_(msgid)
Object is a sort of wrapper for general info.
#define MEM_SAFE_FREE(v)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_inline_enum_accessors(member)
#define UI_ITEM_NONE
constexpr void copy_from(Span< T > values) const
Definition BLI_span.hh:739
bool add(const Key &key)
int64_t index_of_or_add(const Key &key)
void add_multiple(Span< Key > keys)
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
void extend(Span< T > array)
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType data_type)
void make_available(bNode &node) const
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
static void error(const char *str)
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
GeometrySet object_get_evaluated_geometry_set(const Object &object, bool apply_subdiv=true)
Mesh * mesh_boolean(Span< const Mesh * > meshes, Span< float4x4 > transforms, Span< Array< short > > material_remaps, BooleanOpParameters op_params, Solver solver, Vector< int > *r_intersecting_edges, BooleanError *r_error)
bke::GeometrySet join_geometries(Span< bke::GeometrySet > geometries, const bke::AttributeFilter &attribute_filter, const std::optional< Span< bke::GeometryComponent::Type > > &component_types_to_join=std::nullopt, bool allow_merging_instance_references=true)
void debug_randomize_mesh_order(Mesh *mesh)
Definition randomize.cc:288
static void node_init(bNodeTree *, bNode *node)
static Array< short > calc_mesh_material_map(const Mesh &mesh, VectorSet< Material * > &all_materials)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static void node_geo_exec(GeoNodeExecParams params)
static void node_rna(StructRNA *srna)
static void node_declare(NodeDeclarationBuilder &b)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
StructRNA * srna
struct Material ** mat
short totcol
int16_t custom1
int16_t custom2
const Mesh * get_mesh() const
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeDeclareFunction declare
Definition BKE_node.hh:362
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
ParamHandle ** handles
PointerRNA * ptr
Definition wm_files.cc:4238