Blender V4.5
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
13#include "UI_interface.hh"
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").only_realized_data().supported_type(
29 GeometryComponent::Type::Mesh);
30
31 if (node != nullptr) {
35 b.add_input<decl::Geometry>("Mesh", "Mesh 2")
36 .supported_type(GeometryComponent::Type::Mesh)
37 .multi_input();
38 break;
40 b.add_input<decl::Geometry>("Mesh 2")
41 .supported_type(GeometryComponent::Type::Mesh)
42 .multi_input();
43 break;
44 }
45 }
46
47 auto make_mesh_arr = [](bNode &node) {
49 };
50 auto &self_intersect =
51 b.add_input<decl::Bool>("Self Intersection").make_available(make_mesh_arr);
52 auto &hole_tolerant = b.add_input<decl::Bool>("Hole Tolerant").make_available(make_mesh_arr);
53 b.add_output<decl::Geometry>("Mesh").propagate_all();
54 auto &output_edges =
55 b.add_output<decl::Bool>("Intersecting Edges").field_on_all().make_available(make_mesh_arr);
56
57 if (node != nullptr) {
58 const auto operation = geometry::boolean::Operation(node->custom1);
59 const auto solver = geometry::boolean::Solver(node->custom2);
60
61 output_edges.available(
63 self_intersect.available(solver == geometry::boolean::Solver::MeshArr);
64 hole_tolerant.available(solver == geometry::boolean::Solver::MeshArr);
65
66 switch (operation) {
69 first_geometry.available(false);
70 break;
72 break;
73 }
74 }
75}
76
77static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
78{
79 layout->prop(ptr, "operation", UI_ITEM_NONE, "", ICON_NONE);
80 layout->prop(ptr, "solver", UI_ITEM_NONE, "", ICON_NONE);
81}
82
84 std::optional<std::string> intersecting_edges_id;
85};
86
87static void node_init(bNodeTree * /*tree*/, bNode *node)
88{
91}
92
94{
95 Array<short> map(mesh.totcol);
96 for (const int i : IndexRange(mesh.totcol)) {
97 Material *material = mesh.mat[i];
98 map[i] = material ? all_materials.index_of_or_add(material) : -1;
99 }
100 return map;
101}
102
104{
107 bool use_self = false;
108 bool hole_tolerant = false;
110 use_self = params.extract_input<bool>("Self Intersection");
111 hole_tolerant = params.extract_input<bool>("Hole Tolerant");
112 }
113
115 Vector<float4x4> transforms;
116 VectorSet<Material *> materials;
117 Vector<Array<short>> material_remaps;
118
119 GeometrySet set_a;
121 set_a = params.extract_input<GeometrySet>("Mesh 1");
122 /* Note that it technically wouldn't be necessary to realize the instances for the first
123 * geometry input, but the boolean code expects the first shape for the difference operation
124 * to be a single mesh. */
125 if (const Mesh *mesh_in_a = set_a.get_mesh()) {
126 meshes.append(mesh_in_a);
127 transforms.append(float4x4::identity());
128 if (mesh_in_a->totcol == 0) {
129 /* Necessary for faces using the default material when there are no material slots. */
130 materials.add(nullptr);
131 }
132 else {
133 materials.add_multiple({mesh_in_a->mat, mesh_in_a->totcol});
134 }
135 material_remaps.append({});
136 }
137 }
138
139 Vector<GeometrySet> geometry_sets = params.extract_input<Vector<GeometrySet>>("Mesh 2");
140
141 for (const GeometrySet &geometry : geometry_sets) {
142 if (const Mesh *mesh = geometry.get_mesh()) {
143 meshes.append(mesh);
144 transforms.append(float4x4::identity());
145 material_remaps.append(calc_mesh_material_map(*mesh, materials));
146 }
147 if (const bke::Instances *instances = geometry.get_instances()) {
148 const Span<bke::InstanceReference> references = instances->references();
149 const Span<int> handles = instances->reference_handles();
150 const Span<float4x4> instance_transforms = instances->transforms();
151 for (const int i : handles.index_range()) {
152 const bke::InstanceReference &reference = references[handles[i]];
153 switch (reference.type()) {
156 reference.object());
157 if (const Mesh *mesh = object_geometry.get_mesh()) {
158 meshes.append(mesh);
159 transforms.append(instance_transforms[i]);
160 material_remaps.append(calc_mesh_material_map(*mesh, materials));
161 }
162 break;
163 }
165 if (const Mesh *mesh = reference.geometry_set().get_mesh()) {
166 meshes.append(mesh);
167 transforms.append(instance_transforms[i]);
168 material_remaps.append(calc_mesh_material_map(*mesh, materials));
169 }
170 break;
171 }
174 break;
175 }
176 }
177 }
178 }
179
181 /* Manifold remaps materials using realize_instances. */
182 material_remaps.resize(0);
183 }
184
185 AttributeOutputs attribute_outputs;
187 attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed(
188 "Intersecting Edges");
189 }
190
191 Vector<int> intersecting_edges;
193 op_params.boolean_mode = operation;
194 op_params.no_self_intersections = !use_self;
195 op_params.watertight = !hole_tolerant;
196 op_params.no_nested_components = true; /* TODO: make this configurable. */
199 meshes,
200 transforms,
201 material_remaps,
202 op_params,
203 solver,
204 attribute_outputs.intersecting_edges_id ? &intersecting_edges : nullptr,
205 &error);
207 params.error_message_add(NodeWarningType::Error, TIP_("An input was not manifold"));
208 }
210 params.error_message_add(NodeWarningType::Error,
211 TIP_("Boolean result is too big for solver to handle"));
212 }
214 params.error_message_add(NodeWarningType::Error,
215 TIP_("Boolean solver not available (compiled without it)"));
216 }
218 params.error_message_add(NodeWarningType::Error, TIP_("Unknown Boolean error"));
219 }
220 if (!result) {
221 params.set_default_remaining_outputs();
222 return;
223 }
224
225 MEM_SAFE_FREE(result->mat);
226 result->mat = MEM_malloc_arrayN<Material *>(size_t(materials.size()), __func__);
227 result->totcol = materials.size();
228 MutableSpan(result->mat, result->totcol).copy_from(materials);
229
230 /* Store intersecting edges in attribute. */
231 if (attribute_outputs.intersecting_edges_id) {
232 MutableAttributeAccessor attributes = result->attributes_for_write();
234 *attribute_outputs.intersecting_edges_id, AttrDomain::Edge);
235
236 selection.span.fill(false);
237 for (const int i : intersecting_edges) {
238 selection.span[i] = true;
239 }
240 selection.finish();
241 }
243
244 Vector<GeometrySet> all_geometries;
245 all_geometries.append(set_a);
246 all_geometries.extend(geometry_sets);
247
248 const std::array types_to_join = {GeometryComponent::Type::Edit};
249 GeometrySet result_geometry = geometry::join_geometries(
250 all_geometries, {}, std::make_optional(types_to_join));
251 result_geometry.replace_mesh(result);
252 result_geometry.name = set_a.name;
253
254 params.set_output("Mesh", std::move(result_geometry));
255}
256
257static void node_rna(StructRNA *srna)
258{
259 static const EnumPropertyItem rna_node_geometry_boolean_method_items[] = {
261 "INTERSECT",
262 0,
263 "Intersect",
264 "Keep the part of the mesh that is common between all operands"},
266 "UNION",
267 0,
268 "Union",
269 "Combine meshes in an additive way"},
271 "DIFFERENCE",
272 0,
273 "Difference",
274 "Combine meshes in a subtractive way"},
275 {0, nullptr, 0, nullptr, nullptr},
276 };
277 static const EnumPropertyItem rna_geometry_boolean_solver_items[] = {
279 "EXACT",
280 0,
281 "Exact",
282 "Slower solver with the best results for coplanar faces"},
284 "FLOAT",
285 0,
286 "Float",
287 "Simple solver with good performance, without support for overlapping geometry"},
289 "MANIFOLD",
290 0,
291 "Manifold",
292 "Fastest solver that works only on manifold meshes but gives better results"},
293 {0, nullptr, 0, nullptr, nullptr},
294 };
295
297 "operation",
298 "Operation",
299 "",
300 rna_node_geometry_boolean_method_items,
303
305 "solver",
306 "Solver",
307 "",
308 rna_geometry_boolean_solver_items,
311}
312
313static void node_register()
314{
315 static blender::bke::bNodeType ntype;
316 geo_node_type_base(&ntype, "GeometryNodeMeshBoolean", GEO_NODE_MESH_BOOLEAN);
317 ntype.ui_name = "Mesh Boolean";
318 ntype.ui_description = "Cut, subtract, or join multiple mesh inputs";
319 ntype.enum_name_legacy = "MESH_BOOLEAN";
321 ntype.declare = node_declare;
323 ntype.initfunc = node_init;
326
327 node_rna(ntype.rna_ext.srna);
328}
330
331} // namespace blender::nodes::node_geo_boolean_cc
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_MESH_BOOLEAN
#define ELEM(...)
#define TIP_(msgid)
Object is a sort of wrapper for general info.
#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, eCustomDataType data_type)
void make_available(bNode &node) const
#define MEM_SAFE_FREE(v)
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:2748
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)
void debug_randomize_mesh_order(Mesh *mesh)
Definition randomize.cc:217
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)
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
Definition RNA_types.hh:909
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:226
std::string ui_description
Definition BKE_node.hh:232
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
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:4226