Geometry Nodes: Add Delete Geometry Node

This node is similar to the mask modifier, but it deletes the elements
of the geometry corresponding to the selection, which is retrieved as
a boolean attribute. The node currently supports both mesh and point
cloud data. For meshes, which elements are deleted depends on the
domain of the input selection attribute, just like how behavior depends
on the selection mode in mesh edit mode.

In the future this node will support curve data, and ideally volume
data in some way.

Differential Revision: https://developer.blender.org/D10748
This commit is contained in:
Wannes Malfait 2021-06-01 17:32:03 -04:00 committed by Hans Goudey
parent 3400ba329e
commit 464797078d
Notes: blender-bot 2023-02-14 09:33:11 +01:00
Referenced by issue #86640, Geometry Delete Node
10 changed files with 567 additions and 18 deletions

View File

@ -507,6 +507,7 @@ geometry_node_categories = [
]),
GeometryNodeCategory("GEO_GEOMETRY", "Geometry", items=[
NodeItem("GeometryNodeBoundBox"),
NodeItem("GeometryNodeDeleteGeometry"),
NodeItem("GeometryNodeTransform"),
NodeItem("GeometryNodeJoinGeometry"),
]),

View File

@ -1430,6 +1430,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_INPUT_MATERIAL 1050
#define GEO_NODE_MATERIAL_REPLACE 1051
#define GEO_NODE_MESH_TO_CURVE 1052
#define GEO_NODE_DELETE_GEOMETRY 1053
/** \} */

View File

@ -5052,6 +5052,7 @@ static void registerGeometryNodes()
register_node_type_geo_collection_info();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_resample();
register_node_type_geo_delete_geometry();
register_node_type_geo_edge_split();
register_node_type_geo_input_material();
register_node_type_geo_is_viewport();

View File

@ -69,6 +69,19 @@ using blender::MutableSpan;
using blender::Span;
using blender::Vector;
/* For delete geometry node. */
void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> vertex_map);
void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map);
void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map,
Span<int> masked_poly_indices,
Span<int> new_loop_starts);
static void initData(ModifierData *md)
{
MaskModifierData *mmd = (MaskModifierData *)md;
@ -237,9 +250,7 @@ static void computed_masked_polygons(const Mesh *mesh,
*r_num_masked_loops = num_masked_loops;
}
static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map)
void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> vertex_map)
{
BLI_assert(src_mesh.totvert == vertex_map.size());
for (const int i_src : vertex_map.index_range()) {
@ -256,10 +267,10 @@ static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh,
}
}
static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map)
void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map)
{
BLI_assert(src_mesh.totvert == vertex_map.size());
BLI_assert(src_mesh.totedge == edge_map.size());
@ -279,12 +290,12 @@ static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
}
}
static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map,
Span<int> masked_poly_indices,
Span<int> new_loop_starts)
void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
Span<int> vertex_map,
Span<int> edge_map,
Span<int> masked_poly_indices,
Span<int> new_loop_starts)
{
for (const int i_dst : masked_poly_indices.index_range()) {
const int i_src = masked_poly_indices[i_dst];

View File

@ -164,6 +164,7 @@ set(SRC
geometry/nodes/node_geo_common.cc
geometry/nodes/node_geo_curve_to_mesh.cc
geometry/nodes/node_geo_curve_resample.cc
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_edge_split.cc
geometry/nodes/node_geo_input_material.cc
geometry/nodes/node_geo_is_viewport.cc

View File

@ -52,6 +52,7 @@ void register_node_type_geo_bounding_box(void);
void register_node_type_geo_collection_info(void);
void register_node_type_geo_curve_to_mesh(void);
void register_node_type_geo_curve_resample(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_edge_split(void);
void register_node_type_geo_input_material(void);
void register_node_type_geo_is_viewport(void);

View File

@ -291,6 +291,7 @@ DefNode(GeometryNode, GEO_NODE_BOUNDING_BOX, 0, "BOUNDING_BOX", BoundBox, "Bound
DefNode(GeometryNode, GEO_NODE_COLLECTION_INFO, def_geo_collection_info, "COLLECTION_INFO", CollectionInfo, "Collection Info", "")
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "")

View File

@ -62,4 +62,12 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top,
Mesh *create_cube_mesh(const float size);
/**
* Copies the point domain attributes from `in_component` that are in the mask to `out_component`.
*/
void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
GeometryComponent &result_component,
Span<bool> masks,
const bool invert);
} // namespace blender::nodes

View File

@ -0,0 +1,524 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "BLI_array.hh"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "BKE_customdata.h"
#include "BKE_mesh.h"
#include "BKE_pointcloud.h"
#include "node_geometry_util.hh"
/* Code from the mask modifier in MOD_mask.cc. */
extern void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
blender::Span<int> vertex_map);
extern void copy_masked_edges_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
blender::Span<int> vertex_map,
blender::Span<int> edge_map);
extern void copy_masked_polys_to_new_mesh(const Mesh &src_mesh,
Mesh &dst_mesh,
blender::Span<int> vertex_map,
blender::Span<int> edge_map,
blender::Span<int> masked_poly_indices,
blender::Span<int> new_loop_starts);
static bNodeSocketTemplate geo_node_delete_geometry_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{SOCK_STRING, N_("Selection")},
{SOCK_BOOLEAN, N_("Invert")},
{-1, ""},
};
static bNodeSocketTemplate geo_node_delete_geometry_out[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{-1, ""},
};
namespace blender::nodes {
static void delete_point_cloud_selection(const PointCloudComponent &in_component,
const GeoNodeExecParams &params,
PointCloudComponent &out_component)
{
const bool invert = params.get_input<bool>("Invert");
const std::string selection_name = params.get_input<std::string>("Selection");
if (selection_name.empty()) {
return;
}
const GVArray_Typed<bool> selection_attribute = in_component.attribute_get_for_read<bool>(
selection_name, ATTR_DOMAIN_POINT, false);
VArray_Span<bool> selection{selection_attribute};
const int total = selection.count(invert);
if (total == 0) {
out_component.clear();
return;
}
out_component.replace(BKE_pointcloud_new_nomain(total));
/* Invert the inversion, because this deletes the selected points instead of keeping them. */
copy_point_attributes_based_on_mask(in_component, out_component, selection, !invert);
}
static void compute_selected_vertices_from_vertex_selection(const VArray<bool> &vertex_selection,
const bool invert,
MutableSpan<int> r_vertex_map,
uint *r_num_selected_vertices)
{
BLI_assert(vertex_selection.size() == r_vertex_map.size());
uint num_selected_vertices = 0;
for (const int i : r_vertex_map.index_range()) {
if (vertex_selection[i] != invert) {
r_vertex_map[i] = num_selected_vertices;
num_selected_vertices++;
}
else {
r_vertex_map[i] = -1;
}
}
*r_num_selected_vertices = num_selected_vertices;
}
static void compute_selected_edges_from_vertex_selection(const Mesh &mesh,
const VArray<bool> &vertex_selection,
const bool invert,
MutableSpan<int> r_edge_map,
uint *r_num_selected_edges)
{
BLI_assert(mesh.totedge == r_edge_map.size());
uint num_selected_edges = 0;
for (const int i : IndexRange(mesh.totedge)) {
const MEdge &edge = mesh.medge[i];
/* Only add the edge if both vertices will be in the new mesh. */
if (vertex_selection[edge.v1] != invert && vertex_selection[edge.v2] != invert) {
r_edge_map[i] = num_selected_edges;
num_selected_edges++;
}
else {
r_edge_map[i] = -1;
}
}
*r_num_selected_edges = num_selected_edges;
}
static void compute_selected_polygons_from_vertex_selection(const Mesh &mesh,
const VArray<bool> &vertex_selection,
const bool invert,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_polys,
uint *r_num_selected_loops)
{
BLI_assert(mesh.totvert == vertex_selection.size());
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
uint num_selected_loops = 0;
for (const int i : IndexRange(mesh.totpoly)) {
const MPoly &poly_src = mesh.mpoly[i];
bool all_verts_in_selection = true;
Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop);
for (const MLoop &loop : loops_src) {
if (vertex_selection[loop.v] == invert) {
all_verts_in_selection = false;
break;
}
}
if (all_verts_in_selection) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(num_selected_loops);
num_selected_loops += poly_src.totloop;
}
}
*r_num_selected_polys = r_selected_poly_indices.size();
*r_num_selected_loops = num_selected_loops;
}
/**
* Checks for every edge if it is in `edge_selection`. If it is, then the two vertices of the edge
* are kept along with the edge.
*/
static void compute_selected_vertices_and_edges_from_edge_selection(
const Mesh &mesh,
const VArray<bool> &edge_selection,
const bool invert,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
uint *r_num_selected_vertices,
uint *r_num_selected_edges)
{
BLI_assert(mesh.totedge == edge_selection.size());
uint num_selected_edges = 0;
uint num_selected_vertices = 0;
for (const int i : IndexRange(mesh.totedge)) {
const MEdge &edge = mesh.medge[i];
if (edge_selection[i] != invert) {
r_edge_map[i] = num_selected_edges;
num_selected_edges++;
if (r_vertex_map[edge.v1] == -1) {
r_vertex_map[edge.v1] = num_selected_vertices;
num_selected_vertices++;
}
if (r_vertex_map[edge.v2] == -1) {
r_vertex_map[edge.v2] = num_selected_vertices;
num_selected_vertices++;
}
}
else {
r_edge_map[i] = -1;
}
}
*r_num_selected_vertices = num_selected_vertices;
*r_num_selected_edges = num_selected_edges;
}
/**
* Checks for every polygon if all the edges are in `edge_selection`. If they are, then that
* polygon is kept.
*/
static void compute_selected_polygons_from_edge_selection(const Mesh &mesh,
const VArray<bool> &edge_selection,
const bool invert,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_polys,
uint *r_num_selected_loops)
{
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
uint num_selected_loops = 0;
for (const int i : IndexRange(mesh.totpoly)) {
const MPoly &poly_src = mesh.mpoly[i];
bool all_edges_in_selection = true;
Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop);
for (const MLoop &loop : loops_src) {
if (edge_selection[loop.e] == invert) {
all_edges_in_selection = false;
break;
}
}
if (all_edges_in_selection) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(num_selected_loops);
num_selected_loops += poly_src.totloop;
}
}
*r_num_selected_polys = r_selected_poly_indices.size();
*r_num_selected_loops = num_selected_loops;
}
/**
* Checks for every vertex if it is in `vertex_selection`. The polygons and edges are kept if all
* vertices of that polygon or edge are in the selection.
*/
static void compute_selected_mesh_data_from_vertex_selection(const Mesh &mesh,
const VArray<bool> &vertex_selection,
const bool invert,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_vertices,
uint *r_num_selected_edges,
uint *r_num_selected_polys,
uint *r_num_selected_loops)
{
compute_selected_vertices_from_vertex_selection(
vertex_selection, invert, r_vertex_map, r_num_selected_vertices);
compute_selected_edges_from_vertex_selection(
mesh, vertex_selection, invert, r_edge_map, r_num_selected_edges);
compute_selected_polygons_from_vertex_selection(mesh,
vertex_selection,
invert,
r_selected_poly_indices,
r_loop_starts,
r_num_selected_polys,
r_num_selected_loops);
}
/**
* Checks for every edge if it is in `edge_selection`. If it is, the vertices belonging to
* that edge are kept as well. The polygons are kept if all edges are in the selection.
*/
static void compute_selected_mesh_data_from_edge_selection(const Mesh &mesh,
const VArray<bool> &edge_selection,
const bool invert,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_vertices,
uint *r_num_selected_edges,
uint *r_num_selected_polys,
uint *r_num_selected_loops)
{
r_vertex_map.fill(-1);
compute_selected_vertices_and_edges_from_edge_selection(mesh,
edge_selection,
invert,
r_vertex_map,
r_edge_map,
r_num_selected_vertices,
r_num_selected_edges);
compute_selected_polygons_from_edge_selection(mesh,
edge_selection,
invert,
r_selected_poly_indices,
r_loop_starts,
r_num_selected_polys,
r_num_selected_loops);
}
/**
* Checks for every polygon if it is in `poly_selection`. If it is, the edges and vertices
* belonging to that polygon are kept as well.
*/
static void compute_selected_mesh_data_from_poly_selection(const Mesh &mesh,
const VArray<bool> &poly_selection,
const bool invert,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_vertices,
uint *r_num_selected_edges,
uint *r_num_selected_polys,
uint *r_num_selected_loops)
{
BLI_assert(mesh.totpoly == poly_selection.size());
BLI_assert(mesh.totedge == r_edge_map.size());
r_vertex_map.fill(-1);
r_edge_map.fill(-1);
r_selected_poly_indices.reserve(mesh.totpoly);
r_loop_starts.reserve(mesh.totloop);
uint num_selected_loops = 0;
uint num_selected_vertices = 0;
uint num_selected_edges = 0;
for (const int i : IndexRange(mesh.totpoly)) {
const MPoly &poly_src = mesh.mpoly[i];
/* We keep this one. */
if (poly_selection[i] != invert) {
r_selected_poly_indices.append_unchecked(i);
r_loop_starts.append_unchecked(num_selected_loops);
num_selected_loops += poly_src.totloop;
/* Add the vertices and the edges. */
Span<MLoop> loops_src(&mesh.mloop[poly_src.loopstart], poly_src.totloop);
for (const MLoop &loop : loops_src) {
/* Check first if it has not yet been added. */
if (r_vertex_map[loop.v] == -1) {
r_vertex_map[loop.v] = num_selected_vertices;
num_selected_vertices++;
}
if (r_edge_map[loop.e] == -1) {
r_edge_map[loop.e] = num_selected_edges;
num_selected_edges++;
}
}
}
}
*r_num_selected_vertices = num_selected_vertices;
*r_num_selected_edges = num_selected_edges;
*r_num_selected_polys = r_selected_poly_indices.size();
*r_num_selected_loops = num_selected_loops;
}
using FillMapsFunction = void (*)(const Mesh &mesh,
const VArray<bool> &selection,
const bool invert,
MutableSpan<int> r_vertex_map,
MutableSpan<int> r_edge_map,
Vector<int> &r_selected_poly_indices,
Vector<int> &r_loop_starts,
uint *r_num_selected_vertices,
uint *r_num_selected_edges,
uint *r_num_selected_polys,
uint *r_num_selected_loops);
/**
* Delete the parts of the mesh that are in the selection. The `fill_maps_function`
* depends on the selection type: vertices, edges or faces.
*/
static Mesh *delete_mesh_selection(const Mesh &mesh_in,
const VArray<bool> &selection,
const bool invert,
FillMapsFunction fill_maps_function)
{
Array<int> vertex_map(mesh_in.totvert);
uint num_selected_vertices;
Array<int> edge_map(mesh_in.totedge);
uint num_selected_edges;
Vector<int> selected_poly_indices;
Vector<int> new_loop_starts;
uint num_selected_polys;
uint num_selected_loops;
/* Fill all the maps based on the selection. We delete everything
* in the selection instead of keeping it, so we need to invert it. */
fill_maps_function(mesh_in,
selection,
!invert,
vertex_map,
edge_map,
selected_poly_indices,
new_loop_starts,
&num_selected_vertices,
&num_selected_edges,
&num_selected_polys,
&num_selected_loops);
Mesh *result = BKE_mesh_new_nomain_from_template(&mesh_in,
num_selected_vertices,
num_selected_edges,
0,
num_selected_loops,
num_selected_polys);
/* Copy the selected parts of the mesh over to the new mesh. */
copy_masked_vertices_to_new_mesh(mesh_in, *result, vertex_map);
copy_masked_edges_to_new_mesh(mesh_in, *result, vertex_map, edge_map);
copy_masked_polys_to_new_mesh(
mesh_in, *result, vertex_map, edge_map, selected_poly_indices, new_loop_starts);
BKE_mesh_calc_edges_loose(result);
/* Tag to recalculate normals later. */
result->runtime.cd_dirty_vert |= CD_MASK_NORMAL;
return result;
}
static AttributeDomain get_mesh_selection_domain(MeshComponent &component, const StringRef name)
{
std::optional<AttributeMetaData> selection_attribute = component.attribute_get_meta_data(name);
if (!selection_attribute) {
/* The node will not do anything in this case, but this function must return something. */
return ATTR_DOMAIN_POINT;
}
/* Corners can't be deleted separately, so interpolate corner attributes
* to the face domain. Note that this choice is somewhat arbitrary. */
if (selection_attribute->domain == ATTR_DOMAIN_CORNER) {
return ATTR_DOMAIN_FACE;
}
return selection_attribute->domain;
}
static void delete_mesh_selection(MeshComponent &component,
const GeoNodeExecParams &params,
const Mesh &mesh_in)
{
const bool invert_selection = params.get_input<bool>("Invert");
const std::string selection_name = params.get_input<std::string>("Selection");
if (selection_name.empty()) {
return;
}
/* Figure out the best domain to use. */
const AttributeDomain selection_domain = get_mesh_selection_domain(component, selection_name);
/* This already checks if the attribute exists, and displays a warning in that case. */
GVArray_Typed<bool> selection = params.get_input_attribute<bool>(
"Selection", component, selection_domain, false);
/* Check if there is anything to delete. */
bool delete_nothing = true;
for (const int i : selection.index_range()) {
if (selection[i] != invert_selection) {
delete_nothing = false;
break;
}
}
if (delete_nothing) {
return;
}
Mesh *mesh_out;
switch (selection_domain) {
case ATTR_DOMAIN_POINT:
mesh_out = delete_mesh_selection(
mesh_in, selection, invert_selection, compute_selected_mesh_data_from_vertex_selection);
break;
case ATTR_DOMAIN_EDGE:
mesh_out = delete_mesh_selection(
mesh_in, selection, invert_selection, compute_selected_mesh_data_from_edge_selection);
break;
case ATTR_DOMAIN_FACE:
mesh_out = delete_mesh_selection(
mesh_in, selection, invert_selection, compute_selected_mesh_data_from_poly_selection);
break;
default:
BLI_assert_unreachable();
break;
}
component.replace_mesh_but_keep_vertex_group_names(mesh_out);
}
static void geo_node_delete_geometry_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
geometry_set = bke::geometry_set_realize_instances(geometry_set);
GeometrySet out_set(geometry_set);
if (geometry_set.has<PointCloudComponent>()) {
delete_point_cloud_selection(*geometry_set.get_component_for_read<PointCloudComponent>(),
params,
out_set.get_component_for_write<PointCloudComponent>());
}
if (geometry_set.has<MeshComponent>()) {
delete_mesh_selection(out_set.get_component_for_write<MeshComponent>(),
params,
*geometry_set.get_mesh_for_read());
}
params.set_output("Geometry", std::move(out_set));
}
} // namespace blender::nodes
void register_node_type_geo_delete_geometry()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_DELETE_GEOMETRY, "Delete Geometry", NODE_CLASS_GEOMETRY, 0);
node_type_socket_templates(&ntype, geo_node_delete_geometry_in, geo_node_delete_geometry_out);
ntype.geometry_node_execute = blender::nodes::geo_node_delete_geometry_exec;
nodeRegisterType(&ntype);
}

View File

@ -52,10 +52,10 @@ static void copy_data_based_on_mask(Span<T> data,
}
}
static void copy_attributes_based_on_mask(const GeometryComponent &in_component,
GeometryComponent &result_component,
Span<bool> masks,
const bool invert)
void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
GeometryComponent &result_component,
Span<bool> masks,
const bool invert)
{
for (const std::string &name : in_component.attribute_names()) {
ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name);
@ -118,7 +118,7 @@ static void separate_points_from_component(const GeometryComponent &in_component
create_component_points(out_component, total);
copy_attributes_based_on_mask(in_component, out_component, masks, invert);
copy_point_attributes_based_on_mask(in_component, out_component, masks, invert);
}
static GeometrySet separate_geometry_set(const GeometrySet &set_in,