GPv3: Basic vertex group operators

Adds vertex groups and basic operator support to the `GreasePencil` data
block.

Vertex groups in the `GreasePencil` ID are used as the source of truth
for vertex groups names and ordering in the UI. Individual drawings also
have vertex group lists, but they should not be modified directly by
users or the API. The main purpose of storing vertex group names in in a
drawing's `CurveGeometry` is to make it self-contained, so that vertex
weights can be associated with names without requiring the
`GreasePencil` parent data.

Vertex group operators are implemented generically for some ID types.
Grease Pencil needs its own handling in these operators. After
manipulating `vertex_group_names` the `validate_drawing_vertex_groups`
utility function should be called to ensure that drawings only contain a
true subset of the `GreasePencil` data block.

Operators for assigning/removing/selecting/deselecting vertices are also
implemented here. To avoid putting grease pencil logic into the generic
`object_deform.cc` file a number of utility functions have been added in
`BKE_grease_pencil_vgroup.hh`.

Fixes #117337

Pull Request: https://projects.blender.org/blender/blender/pulls/117476
This commit is contained in:
Lukas Tönne
2024-01-31 17:45:59 +01:00
parent 4d4f8bbfe4
commit 7e7165b085
10 changed files with 329 additions and 17 deletions

View File

@@ -226,7 +226,7 @@ class DATA_PT_vertex_groups(MeshButtonsPanel, Panel):
def poll(cls, context):
engine = context.engine
obj = context.object
return (obj and obj.type in {'MESH', 'LATTICE'} and (engine in cls.COMPAT_ENGINES))
return (obj and obj.type in {'MESH', 'LATTICE', 'GREASEPENCIL'} and (engine in cls.COMPAT_ENGINES))
def draw(self, context):
layout = self.layout

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bke
* \brief Utility functions for vertex groups in grease pencil objects
*/
#include "DNA_grease_pencil_types.h"
namespace blender::bke::greasepencil {
/** Make sure drawings only contain vertex groups of the #GreasePencil. */
void validate_drawing_vertex_groups(GreasePencil &grease_pencil);
/** Assign selected vertices to the vertex group. */
void assign_to_vertex_group(GreasePencil &grease_pencil, StringRef name, float weight);
/**
* Remove selected vertices from the vertex group.
* \return True if at least one vertex was removed from the group.
*/
bool remove_from_vertex_group(GreasePencil &grease_pencil, StringRef name, bool use_selection);
/** Remove vertices from all vertex groups. */
void clear_vertex_groups(GreasePencil &grease_pencil);
/** Select or deselect vertices assigned to this group. */
void select_from_group(GreasePencil &grease_pencil, StringRef name, bool select);
} // namespace blender::bke::greasepencil

View File

@@ -150,6 +150,7 @@ set(SRC
intern/gpencil_update_cache_legacy.cc
intern/grease_pencil.cc
intern/grease_pencil_convert_legacy.cc
intern/grease_pencil_vertex_groups.cc
intern/icons.cc
intern/icons_rasterize.cc
intern/idprop.cc
@@ -403,6 +404,7 @@ set(SRC
BKE_gpencil_update_cache_legacy.h
BKE_grease_pencil.h
BKE_grease_pencil.hh
BKE_grease_pencil_vertex_groups.hh
BKE_icons.h
BKE_idprop.h
BKE_idprop.hh

View File

@@ -15,6 +15,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -33,6 +34,8 @@
#include "BKE_customdata.hh"
#include "BKE_data_transfer.h"
#include "BKE_deform.hh" /* own include */
#include "BKE_grease_pencil.hh"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "BKE_object.hh"
@@ -57,6 +60,11 @@ bDeformGroup *BKE_object_defgroup_new(Object *ob, const char *name)
BLI_addtail(defbase, defgroup);
BKE_object_defgroup_unique_name(defgroup, ob);
if (ob->type == OB_GREASE_PENCIL) {
blender::bke::greasepencil::validate_drawing_vertex_groups(
*static_cast<GreasePencil *>(ob->data));
}
BKE_object_batch_cache_dirty_tag(ob);
return defgroup;
@@ -440,7 +448,7 @@ bool BKE_id_supports_vertex_groups(const ID *id)
if (id == nullptr) {
return false;
}
return ELEM(GS(id->name), ID_ME, ID_LT, ID_GD_LEGACY);
return ELEM(GS(id->name), ID_ME, ID_LT, ID_GD_LEGACY, ID_GP);
}
bool BKE_object_supports_vertex_groups(const Object *ob)
@@ -465,6 +473,10 @@ const ListBase *BKE_id_defgroup_list_get(const ID *id)
const bGPdata *gpd = (const bGPdata *)id;
return &gpd->vertex_group_names;
}
case ID_GP: {
const GreasePencil *grease_pencil = (const GreasePencil *)id;
return &grease_pencil->vertex_group_names;
}
default: {
BLI_assert_unreachable();
}
@@ -488,6 +500,10 @@ static const int *object_defgroup_active_index_get_p(const Object *ob)
const bGPdata *gpd = (const bGPdata *)ob->data;
return &gpd->vertex_group_active_index;
}
case OB_GREASE_PENCIL: {
const GreasePencil *grease_pencil = (const GreasePencil *)ob->data;
return &grease_pencil->vertex_group_active_index;
}
}
return nullptr;
}

View File

@@ -11,6 +11,7 @@
#include "BKE_anim_data.h"
#include "BKE_curves.hh"
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_geometry_set.hh"
#include "BKE_grease_pencil.h"
#include "BKE_grease_pencil.hh"
@@ -116,6 +117,9 @@ static void grease_pencil_copy_data(Main * /*bmain*/,
CD_MASK_ALL,
grease_pencil_dst->layers().size());
BKE_defgroup_copy_list(&grease_pencil_dst->vertex_group_names,
&grease_pencil_src->vertex_group_names);
/* Make sure the runtime pointer exists. */
grease_pencil_dst->runtime = MEM_new<bke::GreasePencilRuntime>(__func__);
}
@@ -132,6 +136,8 @@ static void grease_pencil_free_data(ID *id)
free_drawing_array(*grease_pencil);
MEM_delete(&grease_pencil->root_group());
BLI_freelistN(&grease_pencil->vertex_group_names);
BKE_grease_pencil_batch_cache_free(grease_pencil);
MEM_delete(grease_pencil->runtime);
@@ -179,6 +185,8 @@ static void grease_pencil_blend_write(BlendWriter *writer, ID *id, const void *i
/* Write materials. */
BLO_write_pointer_array(
writer, grease_pencil->material_array_num, grease_pencil->material_array);
/* Write vertex group names. */
BKE_defbase_blend_write(writer, &grease_pencil->vertex_group_names);
}
static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id)
@@ -195,6 +203,8 @@ static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id)
/* Read materials. */
BLO_read_pointer_array(reader, reinterpret_cast<void **>(&grease_pencil->material_array));
/* Read vertex group names. */
BLO_read_list(reader, &grease_pencil->vertex_group_names);
grease_pencil->runtime = MEM_new<blender::bke::GreasePencilRuntime>(__func__);
}

View File

@@ -0,0 +1,196 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "DNA_meshdata_types.h"
#include "BLI_listbase.h"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_string_utils.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BLT_translation.h"
namespace blender::bke::greasepencil {
/* ------------------------------------------------------------------- */
/** \name Vertex groups in drawings
* \{ */
void validate_drawing_vertex_groups(GreasePencil &grease_pencil)
{
Set<std::string> valid_names;
LISTBASE_FOREACH (const bDeformGroup *, defgroup, &grease_pencil.vertex_group_names) {
valid_names.add_new(defgroup->name);
}
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
if (base->type != GP_DRAWING) {
continue;
}
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
/* Remove unknown vertex groups. */
CurvesGeometry &curves = drawing.strokes_for_write();
int defgroup_index = 0;
LISTBASE_FOREACH_MUTABLE (bDeformGroup *, defgroup, &curves.vertex_group_names) {
if (!valid_names.contains(defgroup->name)) {
remove_defgroup_index(curves.deform_verts_for_write(), defgroup_index);
BLI_remlink(&curves.vertex_group_names, defgroup);
MEM_SAFE_FREE(defgroup);
}
++defgroup_index;
}
}
}
void assign_to_vertex_group(GreasePencil &grease_pencil, const StringRef name, const float weight)
{
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
if (base->type != GP_DRAWING) {
continue;
}
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
bke::CurvesGeometry &curves = drawing.strokes_for_write();
ListBase &vertex_group_names = curves.vertex_group_names;
const bke::AttributeAccessor attributes = curves.attributes();
const VArray<bool> selection = *attributes.lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
/* Look for existing group, otherwise lazy-initialize if any vertex is selected. */
int def_nr = BLI_findstringindex(
&vertex_group_names, name.data(), offsetof(bDeformGroup, name));
const MutableSpan<MDeformVert> dverts = curves.deform_verts_for_write();
for (const int i : dverts.index_range()) {
if (selection[i]) {
/* Lazily add the vertex group if any vertex is selected. */
if (def_nr < 0) {
bDeformGroup *defgroup = MEM_cnew<bDeformGroup>(__func__);
STRNCPY(defgroup->name, name.data());
BLI_addtail(&vertex_group_names, defgroup);
def_nr = BLI_listbase_count(&vertex_group_names) - 1;
BLI_assert(def_nr >= 0);
}
MDeformWeight *dw = BKE_defvert_ensure_index(&dverts[i], def_nr);
if (dw) {
dw->weight = weight;
}
}
}
}
}
/** Remove selected vertices from the vertex group. */
bool remove_from_vertex_group(GreasePencil &grease_pencil,
const StringRef name,
const bool use_selection)
{
bool changed = false;
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
if (base->type != GP_DRAWING) {
continue;
}
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
bke::CurvesGeometry &curves = drawing.strokes_for_write();
ListBase &vertex_group_names = curves.vertex_group_names;
const int def_nr = BLI_findstringindex(
&vertex_group_names, name.data(), offsetof(bDeformGroup, name));
if (def_nr < 0) {
/* No vertices assigned to the group in this drawing. */
continue;
}
const MutableSpan<MDeformVert> dverts = curves.deform_verts_for_write();
const bke::AttributeAccessor attributes = curves.attributes();
const VArray<bool> selection = *attributes.lookup_or_default<bool>(
".selection", bke::AttrDomain::Point, true);
for (const int i : dverts.index_range()) {
if (!use_selection || selection[i]) {
MDeformVert *dv = &dverts[i];
MDeformWeight *dw = BKE_defvert_find_index(dv, def_nr);
BKE_defvert_remove_group(dv, dw);
/* Adjust remaining vertex group indices. */
for (const int j : IndexRange(dv->totweight)) {
if (dv->dw[j].def_nr > def_nr) {
dv->dw[j].def_nr--;
}
}
changed = true;
}
}
}
return changed;
}
void clear_vertex_groups(GreasePencil &grease_pencil)
{
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
if (base->type != GP_DRAWING) {
continue;
}
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
bke::CurvesGeometry &curves = drawing.strokes_for_write();
for (MDeformVert &dvert : curves.deform_verts_for_write()) {
BKE_defvert_clear(&dvert);
}
}
}
void select_from_group(GreasePencil &grease_pencil, const StringRef name, const bool select)
{
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {
if (base->type != GP_DRAWING) {
continue;
}
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(base)->wrap();
bke::CurvesGeometry &curves = drawing.strokes_for_write();
ListBase &vertex_group_names = curves.vertex_group_names;
const int def_nr = BLI_findstringindex(
&vertex_group_names, name.data(), offsetof(bDeformGroup, name));
if (def_nr < 0) {
/* No vertices assigned to the group in this drawing. */
continue;
}
const Span<MDeformVert> dverts = curves.deform_verts_for_write();
if (!dverts.is_empty()) {
bke::MutableAttributeAccessor attributes = curves.attributes_for_write();
SpanAttributeWriter<bool> selection = attributes.lookup_or_add_for_write_span<bool>(
".selection",
bke::AttrDomain::Point,
AttributeInitVArray(VArray<bool>::ForSingle(true, curves.point_num)));
for (const int i : selection.span.index_range()) {
if (BKE_defvert_find_index(&dverts[i], def_nr)) {
selection.span[i] = select;
}
}
selection.finish();
}
}
}
/** \} */
} // namespace blender::bke::greasepencil

View File

@@ -22,6 +22,7 @@
#include "DNA_cloth_types.h"
#include "DNA_curve_types.h"
#include "DNA_gpencil_legacy_types.h"
#include "DNA_grease_pencil_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -35,6 +36,7 @@
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_gpencil_legacy.h"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BKE_mesh.hh"
#include "BKE_modifier.hh"
#include "BKE_object.hh"
@@ -197,6 +199,11 @@ bool BKE_object_defgroup_clear(Object *ob, bDeformGroup *dg, const bool use_sele
}
}
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
changed = blender::bke::greasepencil::remove_from_vertex_group(
*grease_pencil, dg->name, use_selection);
}
return changed;
}
@@ -264,6 +271,10 @@ static void object_defgroup_remove_common(Object *ob, bDeformGroup *dg, const in
Lattice *lt = object_defgroup_lattice_get((ID *)(ob->data));
MEM_SAFE_FREE(lt->dvert);
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
blender::bke::greasepencil::clear_vertex_groups(*grease_pencil);
}
}
else if (BKE_object_defgroup_active_index_get(ob) < 1) {
/* Keep a valid active index if we still have some vgroups. */
@@ -281,24 +292,30 @@ static void object_defgroup_remove_object_mode(Object *ob, bDeformGroup *dg)
BLI_assert(def_nr != -1);
BKE_object_defgroup_array_get(static_cast<ID *>(ob->data), &dvert_array, &dvert_tot);
if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
blender::bke::greasepencil::remove_from_vertex_group(*grease_pencil, dg->name, false);
}
else {
BKE_object_defgroup_array_get(static_cast<ID *>(ob->data), &dvert_array, &dvert_tot);
if (dvert_array) {
int i, j;
MDeformVert *dv;
for (i = 0, dv = dvert_array; i < dvert_tot; i++, dv++) {
MDeformWeight *dw;
if (dvert_array) {
int i, j;
MDeformVert *dv;
for (i = 0, dv = dvert_array; i < dvert_tot; i++, dv++) {
MDeformWeight *dw;
dw = BKE_defvert_find_index(dv, def_nr);
BKE_defvert_remove_group(dv, dw); /* dw can be nullptr */
dw = BKE_defvert_find_index(dv, def_nr);
BKE_defvert_remove_group(dv, dw); /* dw can be nullptr */
/* inline, make into a function if anything else needs to do this */
for (j = 0; j < dv->totweight; j++) {
if (dv->dw[j].def_nr > def_nr) {
dv->dw[j].def_nr--;
/* inline, make into a function if anything else needs to do this */
for (j = 0; j < dv->totweight; j++) {
if (dv->dw[j].def_nr > def_nr) {
dv->dw[j].def_nr--;
}
}
/* done */
}
/* done */
}
}
@@ -357,6 +374,10 @@ static void object_defgroup_remove_edit_mode(Object *ob, bDeformGroup *dg)
}
}
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
blender::bke::greasepencil::remove_from_vertex_group(*grease_pencil, dg->name, false);
}
object_defgroup_remove_common(ob, dg, def_nr);
}
@@ -374,6 +395,11 @@ void BKE_object_defgroup_remove(Object *ob, bDeformGroup *defgroup)
object_defgroup_remove_object_mode(ob, defgroup);
}
if (ob->type == OB_GREASE_PENCIL) {
blender::bke::greasepencil::validate_drawing_vertex_groups(
*static_cast<GreasePencil *>(ob->data));
}
BKE_object_batch_cache_dirty_tag(ob);
}
}
@@ -411,6 +437,10 @@ void BKE_object_defgroup_remove_all_ex(Object *ob, bool only_unlocked)
Lattice *lt = object_defgroup_lattice_get((ID *)(ob->data));
MEM_SAFE_FREE(lt->dvert);
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
blender::bke::greasepencil::clear_vertex_groups(*grease_pencil);
}
/* Fix counters/indices */
BKE_object_defgroup_active_index_set(ob, 0);
}
@@ -506,6 +536,10 @@ bool BKE_object_defgroup_array_get(ID *id, MDeformVert **dvert_arr, int *dvert_t
*dvert_tot = lt->pntsu * lt->pntsv * lt->pntsw;
return true;
}
case ID_GP:
/* Should not be used with grease pencil objects.*/
BLI_assert_unreachable();
break;
default:
break;
}

View File

@@ -35,6 +35,7 @@
#include "BKE_customdata.hh"
#include "BKE_deform.hh"
#include "BKE_editmesh.hh"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BKE_lattice.hh"
#include "BKE_layer.hh"
#include "BKE_mesh.hh"
@@ -1024,7 +1025,8 @@ static void vgroup_select_verts(Object *ob, int select)
const int def_nr = BKE_object_defgroup_active_index_get(ob) - 1;
const ListBase *defbase = BKE_object_defgroup_list(ob);
if (!BLI_findlink(defbase, def_nr)) {
const bDeformGroup *def_group = static_cast<bDeformGroup *>(BLI_findlink(defbase, def_nr));
if (!def_group) {
return;
}
@@ -1107,6 +1109,11 @@ static void vgroup_select_verts(Object *ob, int select)
}
}
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
blender::bke::greasepencil::select_from_group(*grease_pencil, def_group->name, bool(select));
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_GEOMETRY);
}
}
static void vgroup_duplicate(Object *ob)
@@ -2309,6 +2316,12 @@ static void vgroup_assign_verts(Object *ob, const float weight)
}
}
}
else if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
const bDeformGroup *defgroup = static_cast<const bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(ob), def_nr));
blender::bke::greasepencil::assign_to_vertex_group(*grease_pencil, defgroup->name, weight);
}
}
/** \} */

View File

@@ -51,6 +51,7 @@ struct GreasePencil;
struct BlendDataReader;
struct BlendWriter;
struct Object;
struct bDeformGroup;
typedef enum GreasePencilStrokeCapType {
GP_STROKE_CAP_TYPE_ROUND = 0,
@@ -433,6 +434,11 @@ typedef struct GreasePencil {
* Global flag on the data-block.
*/
uint32_t flag;
ListBase vertex_group_names;
int vertex_group_active_index;
char _pad4[4];
/**
* Onion skinning settings.
*/

View File

@@ -491,7 +491,8 @@ typedef enum ObjectType {
OB_POINTCLOUD, \
OB_VOLUME, \
OB_GREASE_PENCIL))
#define OB_TYPE_SUPPORT_VGROUP(_type) (ELEM(_type, OB_MESH, OB_LATTICE, OB_GPENCIL_LEGACY))
#define OB_TYPE_SUPPORT_VGROUP(_type) \
(ELEM(_type, OB_MESH, OB_LATTICE, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL))
#define OB_TYPE_SUPPORT_EDITMODE(_type) \
(ELEM(_type, \
OB_MESH, \