Move the `ME_SHARP` flag for mesh edges to a generic boolean attribute. This will help allow changing mesh edges to just a pair of integers, giving performance improvements. In the future it could also give benefits for normal calculation, which could more easily check if all or no edges are marked sharp, which is helpful considering the plans in T93551. The attribute is generally only allocated when it's necessary. When leaving edit mode, it will only be created if an edge is marked sharp. The data can be edited with geometry nodes just like a regular edge domain boolean attribute. The attribute is named `sharp_edge`, aiming to reflect the similar `select_edge` naming and to allow a future `sharp_face` name in a separate commit. Ref T95966 Differential Revision: https://developer.blender.org/D16921
1485 lines
46 KiB
C++
1485 lines
46 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2020 Blender Foundation. All rights reserved. */
|
|
|
|
/** \file
|
|
* \ingroup edsculpt
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <queue>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_bit_vector.hh"
|
|
#include "BLI_function_ref.hh"
|
|
#include "BLI_hash.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_math_vector.hh"
|
|
#include "BLI_span.hh"
|
|
#include "BLI_task.h"
|
|
#include "BLI_task.hh"
|
|
|
|
#include "DNA_brush_types.h"
|
|
#include "DNA_customdata_types.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
|
|
#include "BKE_attribute.hh"
|
|
#include "BKE_ccg.h"
|
|
#include "BKE_colortools.h"
|
|
#include "BKE_context.h"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_fair.h"
|
|
#include "BKE_mesh_mapping.h"
|
|
#include "BKE_object.h"
|
|
#include "BKE_paint.h"
|
|
#include "BKE_pbvh.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "ED_sculpt.h"
|
|
|
|
#include "sculpt_intern.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_define.h"
|
|
|
|
#include "bmesh.h"
|
|
|
|
/* Utils. */
|
|
|
|
int ED_sculpt_face_sets_find_next_available_id(struct Mesh *mesh)
|
|
{
|
|
const int *face_sets = static_cast<const int *>(
|
|
CustomData_get_layer_named(&mesh->pdata, CD_PROP_INT32, ".sculpt_face_set"));
|
|
if (!face_sets) {
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
|
|
int next_face_set_id = 0;
|
|
for (int i = 0; i < mesh->totpoly; i++) {
|
|
next_face_set_id = max_ii(next_face_set_id, face_sets[i]);
|
|
}
|
|
next_face_set_id++;
|
|
|
|
return next_face_set_id;
|
|
}
|
|
|
|
void ED_sculpt_face_sets_initialize_none_to_id(struct Mesh *mesh, const int new_id)
|
|
{
|
|
int *face_sets = static_cast<int *>(
|
|
CustomData_get_layer_named(&mesh->pdata, CD_PROP_INT32, ".sculpt_face_set"));
|
|
if (!face_sets) {
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < mesh->totpoly; i++) {
|
|
if (face_sets[i] == SCULPT_FACE_SET_NONE) {
|
|
face_sets[i] = new_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
int ED_sculpt_face_sets_active_update_and_get(bContext *C, Object *ob, const float mval[2])
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
if (!ss) {
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
|
|
SculptCursorGeometryInfo gi;
|
|
if (!SCULPT_cursor_geometry_info_update(C, &gi, mval, false)) {
|
|
return SCULPT_FACE_SET_NONE;
|
|
}
|
|
|
|
return SCULPT_active_face_set_get(ss);
|
|
}
|
|
|
|
/* Draw Face Sets Brush. */
|
|
|
|
static void do_draw_face_sets_brush_task_cb_ex(void *__restrict userdata,
|
|
const int n,
|
|
const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = static_cast<SculptThreadedTaskData *>(userdata);
|
|
SculptSession *ss = data->ob->sculpt;
|
|
const Brush *brush = data->brush;
|
|
const float bstrength = ss->cache->bstrength;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
|
|
const float(*positions)[3] = SCULPT_mesh_deformed_positions_get(ss);
|
|
AutomaskingNodeData automask_data;
|
|
SCULPT_automasking_node_begin(
|
|
data->ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]);
|
|
|
|
bool changed = false;
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
SCULPT_automasking_node_update(ss, &automask_data, &vd);
|
|
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_FACES) {
|
|
MeshElemMap *vert_map = &ss->pmap[vd.index];
|
|
for (int j = 0; j < ss->pmap[vd.index].count; j++) {
|
|
const MPoly *p = &ss->mpoly[vert_map->indices[j]];
|
|
|
|
float poly_center[3];
|
|
BKE_mesh_calc_poly_center(p, &ss->mloop[p->loopstart], positions, poly_center);
|
|
|
|
if (!sculpt_brush_test_sq_fn(&test, poly_center)) {
|
|
continue;
|
|
}
|
|
const bool face_hidden = ss->hide_poly && ss->hide_poly[vert_map->indices[j]];
|
|
if (face_hidden) {
|
|
continue;
|
|
}
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
if (fade > 0.05f) {
|
|
ss->face_sets[vert_map->indices[j]] = ss->cache->paint_face_set;
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
else if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
if (fade > 0.05f) {
|
|
SCULPT_vertex_face_set_set(ss, vd.vertex, ss->cache->paint_face_set);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
|
|
if (changed) {
|
|
SCULPT_undo_push_node(data->ob, data->nodes[n], SCULPT_UNDO_FACE_SETS);
|
|
}
|
|
}
|
|
|
|
static void do_relax_face_sets_brush_task_cb_ex(void *__restrict userdata,
|
|
const int n,
|
|
const TaskParallelTLS *__restrict tls)
|
|
{
|
|
SculptThreadedTaskData *data = static_cast<SculptThreadedTaskData *>(userdata);
|
|
SculptSession *ss = data->ob->sculpt;
|
|
const Brush *brush = data->brush;
|
|
float bstrength = ss->cache->bstrength;
|
|
|
|
PBVHVertexIter vd;
|
|
|
|
SculptBrushTest test;
|
|
SculptBrushTestFn sculpt_brush_test_sq_fn = SCULPT_brush_test_init_with_falloff_shape(
|
|
ss, &test, data->brush->falloff_shape);
|
|
|
|
const bool relax_face_sets = !(ss->cache->iteration_count % 3 == 0);
|
|
/* This operations needs a strength tweak as the relax deformation is too weak by default. */
|
|
if (relax_face_sets && data->iteration < 2) {
|
|
bstrength *= 1.5f;
|
|
}
|
|
|
|
const int thread_id = BLI_task_parallel_thread_id(tls);
|
|
AutomaskingNodeData automask_data;
|
|
SCULPT_automasking_node_begin(
|
|
data->ob, ss, ss->cache->automasking, &automask_data, data->nodes[n]);
|
|
|
|
BKE_pbvh_vertex_iter_begin (ss->pbvh, data->nodes[n], vd, PBVH_ITER_UNIQUE) {
|
|
SCULPT_automasking_node_update(ss, &automask_data, &vd);
|
|
|
|
if (!sculpt_brush_test_sq_fn(&test, vd.co)) {
|
|
continue;
|
|
}
|
|
if (relax_face_sets == SCULPT_vertex_has_unique_face_set(ss, vd.vertex)) {
|
|
continue;
|
|
}
|
|
|
|
const float fade = bstrength * SCULPT_brush_strength_factor(ss,
|
|
brush,
|
|
vd.co,
|
|
sqrtf(test.dist),
|
|
vd.no,
|
|
vd.fno,
|
|
vd.mask ? *vd.mask : 0.0f,
|
|
vd.vertex,
|
|
thread_id,
|
|
&automask_data);
|
|
|
|
SCULPT_relax_vertex(ss, &vd, fade * bstrength, relax_face_sets, vd.co);
|
|
if (vd.is_mesh) {
|
|
BKE_pbvh_vert_tag_update_normal(ss->pbvh, vd.vertex);
|
|
}
|
|
}
|
|
BKE_pbvh_vertex_iter_end;
|
|
}
|
|
|
|
void SCULPT_do_draw_face_sets_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
Brush *brush = BKE_paint_brush(&sd->paint);
|
|
|
|
if (ss->pbvh) {
|
|
Mesh *mesh = BKE_mesh_from_object(ob);
|
|
BKE_pbvh_face_sets_color_set(
|
|
ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
|
|
}
|
|
|
|
BKE_curvemapping_init(brush->curve);
|
|
|
|
/* Threaded loop over nodes. */
|
|
SculptThreadedTaskData data{};
|
|
data.sd = sd;
|
|
data.ob = ob;
|
|
data.brush = brush;
|
|
data.nodes = nodes;
|
|
|
|
TaskParallelSettings settings;
|
|
BKE_pbvh_parallel_range_settings(&settings, true, totnode);
|
|
if (ss->cache->alt_smooth) {
|
|
SCULPT_boundary_info_ensure(ob);
|
|
for (int i = 0; i < 4; i++) {
|
|
data.iteration = i;
|
|
BLI_task_parallel_range(0, totnode, &data, do_relax_face_sets_brush_task_cb_ex, &settings);
|
|
}
|
|
}
|
|
else {
|
|
BLI_task_parallel_range(0, totnode, &data, do_draw_face_sets_brush_task_cb_ex, &settings);
|
|
}
|
|
}
|
|
|
|
/* Face Sets Operators */
|
|
|
|
enum eSculptFaceGroupsCreateModes {
|
|
SCULPT_FACE_SET_MASKED = 0,
|
|
SCULPT_FACE_SET_VISIBLE = 1,
|
|
SCULPT_FACE_SET_ALL = 2,
|
|
SCULPT_FACE_SET_SELECTION = 3,
|
|
};
|
|
|
|
static EnumPropertyItem prop_sculpt_face_set_create_types[] = {
|
|
{
|
|
SCULPT_FACE_SET_MASKED,
|
|
"MASKED",
|
|
0,
|
|
"Face Set from Masked",
|
|
"Create a new Face Set from the masked faces",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_VISIBLE,
|
|
"VISIBLE",
|
|
0,
|
|
"Face Set from Visible",
|
|
"Create a new Face Set from the visible vertices",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_ALL,
|
|
"ALL",
|
|
0,
|
|
"Face Set Full Mesh",
|
|
"Create an unique Face Set with all faces in the sculpt",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_SELECTION,
|
|
"SELECTION",
|
|
0,
|
|
"Face Set from Edit Mode Selection",
|
|
"Create an Face Set corresponding to the Edit Mode face selection",
|
|
},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sculpt_face_set_create_exec(bContext *C, wmOperator *op)
|
|
{
|
|
using namespace blender;
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
|
|
const int mode = RNA_enum_get(op->ptr, "mode");
|
|
|
|
/* Dyntopo not supported. */
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
ss->face_sets = BKE_sculpt_face_sets_ensure(mesh);
|
|
|
|
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, mode == SCULPT_FACE_SET_MASKED, false);
|
|
|
|
const int tot_vert = SCULPT_vertex_count_get(ss);
|
|
float threshold = 0.5f;
|
|
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
|
|
if (!nodes) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
SCULPT_undo_push_begin(ob, op);
|
|
for (const int i : blender::IndexRange(totnode)) {
|
|
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_FACE_SETS);
|
|
}
|
|
|
|
const int next_face_set = SCULPT_face_set_next_available_get(ss);
|
|
|
|
if (mode == SCULPT_FACE_SET_MASKED) {
|
|
for (int i = 0; i < tot_vert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
if (SCULPT_vertex_mask_get(ss, vertex) >= threshold &&
|
|
SCULPT_vertex_visible_get(ss, vertex)) {
|
|
SCULPT_vertex_face_set_set(ss, vertex, next_face_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == SCULPT_FACE_SET_VISIBLE) {
|
|
|
|
/* If all vertices in the sculpt are visible, create the new face set and update the default
|
|
* color. This way the new face set will be white, which is a quick way of disabling all face
|
|
* sets and the performance hit of rendering the overlay. */
|
|
bool all_visible = true;
|
|
for (int i = 0; i < tot_vert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
if (!SCULPT_vertex_visible_get(ss, vertex)) {
|
|
all_visible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_visible) {
|
|
mesh->face_sets_color_default = next_face_set;
|
|
BKE_pbvh_face_sets_color_set(
|
|
ss->pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
|
|
}
|
|
|
|
for (int i = 0; i < tot_vert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
if (SCULPT_vertex_visible_get(ss, vertex)) {
|
|
SCULPT_vertex_face_set_set(ss, vertex, next_face_set);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mode == SCULPT_FACE_SET_ALL) {
|
|
for (int i = 0; i < tot_vert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
SCULPT_vertex_face_set_set(ss, vertex, next_face_set);
|
|
}
|
|
}
|
|
|
|
if (mode == SCULPT_FACE_SET_SELECTION) {
|
|
const bke::AttributeAccessor attributes = mesh->attributes();
|
|
const VArraySpan<bool> select_poly = attributes.lookup_or_default<bool>(
|
|
".select_poly", ATTR_DOMAIN_FACE, false);
|
|
threading::parallel_for(IndexRange(mesh->totvert), 4096, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
if (select_poly[i]) {
|
|
ss->face_sets[i] = next_face_set;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_redraw(nodes[i]);
|
|
}
|
|
|
|
MEM_SAFE_FREE(nodes);
|
|
|
|
SCULPT_undo_push_end(ob);
|
|
|
|
SCULPT_tag_update_overlays(C);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SCULPT_OT_face_sets_create(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Create Face Set";
|
|
ot->idname = "SCULPT_OT_face_sets_create";
|
|
ot->description = "Create a new Face Set";
|
|
|
|
/* api callbacks */
|
|
ot->exec = sculpt_face_set_create_exec;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(
|
|
ot->srna, "mode", prop_sculpt_face_set_create_types, SCULPT_FACE_SET_MASKED, "Mode", "");
|
|
}
|
|
|
|
enum eSculptFaceSetsInitMode {
|
|
SCULPT_FACE_SETS_FROM_LOOSE_PARTS = 0,
|
|
SCULPT_FACE_SETS_FROM_MATERIALS = 1,
|
|
SCULPT_FACE_SETS_FROM_NORMALS = 2,
|
|
SCULPT_FACE_SETS_FROM_UV_SEAMS = 3,
|
|
SCULPT_FACE_SETS_FROM_CREASES = 4,
|
|
SCULPT_FACE_SETS_FROM_SHARP_EDGES = 5,
|
|
SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT = 6,
|
|
SCULPT_FACE_SETS_FROM_FACE_MAPS = 7,
|
|
SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES = 8,
|
|
};
|
|
|
|
static EnumPropertyItem prop_sculpt_face_sets_init_types[] = {
|
|
{
|
|
SCULPT_FACE_SETS_FROM_LOOSE_PARTS,
|
|
"LOOSE_PARTS",
|
|
0,
|
|
"Face Sets from Loose Parts",
|
|
"Create a Face Set per loose part in the mesh",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_MATERIALS,
|
|
"MATERIALS",
|
|
0,
|
|
"Face Sets from Material Slots",
|
|
"Create a Face Set per Material Slot",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_NORMALS,
|
|
"NORMALS",
|
|
0,
|
|
"Face Sets from Mesh Normals",
|
|
"Create Face Sets for Faces that have similar normal",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_UV_SEAMS,
|
|
"UV_SEAMS",
|
|
0,
|
|
"Face Sets from UV Seams",
|
|
"Create Face Sets using UV Seams as boundaries",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_CREASES,
|
|
"CREASES",
|
|
0,
|
|
"Face Sets from Edge Creases",
|
|
"Create Face Sets using Edge Creases as boundaries",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT,
|
|
"BEVEL_WEIGHT",
|
|
0,
|
|
"Face Sets from Bevel Weight",
|
|
"Create Face Sets using Bevel Weights as boundaries",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_SHARP_EDGES,
|
|
"SHARP_EDGES",
|
|
0,
|
|
"Face Sets from Sharp Edges",
|
|
"Create Face Sets using Sharp Edges as boundaries",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_FACE_MAPS,
|
|
"FACE_MAPS",
|
|
0,
|
|
"Face Sets from Face Maps",
|
|
"Create a Face Set per Face Map",
|
|
},
|
|
{
|
|
SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES,
|
|
"FACE_SET_BOUNDARIES",
|
|
0,
|
|
"Face Sets from Face Set Boundaries",
|
|
"Create a Face Set per isolated Face Set",
|
|
},
|
|
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
using FaceSetsFloodFillFn = blender::FunctionRef<bool(int from_face, int edge, int to_face)>;
|
|
|
|
static void sculpt_face_sets_init_flood_fill(Object *ob, const FaceSetsFloodFillFn &test_fn)
|
|
{
|
|
using namespace blender;
|
|
SculptSession *ss = ob->sculpt;
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
|
|
BitVector<> visited_faces(mesh->totpoly, false);
|
|
|
|
int *face_sets = ss->face_sets;
|
|
|
|
const Span<MEdge> edges = mesh->edges();
|
|
const Span<MPoly> polys = mesh->polys();
|
|
const Span<MLoop> loops = mesh->loops();
|
|
|
|
if (!ss->epmap) {
|
|
BKE_mesh_edge_poly_map_create(&ss->epmap,
|
|
&ss->epmap_mem,
|
|
edges.data(),
|
|
edges.size(),
|
|
polys.data(),
|
|
polys.size(),
|
|
loops.data(),
|
|
loops.size());
|
|
}
|
|
|
|
int next_face_set = 1;
|
|
|
|
for (const int i : polys.index_range()) {
|
|
if (visited_faces[i]) {
|
|
continue;
|
|
}
|
|
std::queue<int> queue;
|
|
|
|
face_sets[i] = next_face_set;
|
|
visited_faces[i].set(true);
|
|
queue.push(i);
|
|
|
|
while (!queue.empty()) {
|
|
const int poly_i = queue.front();
|
|
const MPoly &poly = polys[poly_i];
|
|
queue.pop();
|
|
|
|
for (const MLoop &loop : loops.slice(poly.loopstart, poly.totloop)) {
|
|
const int edge_i = loop.e;
|
|
const Span<int> neighbor_polys(ss->epmap[edge_i].indices, ss->epmap[edge_i].count);
|
|
for (const int neighbor_i : neighbor_polys) {
|
|
if (neighbor_i == poly_i) {
|
|
continue;
|
|
}
|
|
if (visited_faces[neighbor_i]) {
|
|
continue;
|
|
}
|
|
if (!test_fn(poly_i, edge_i, neighbor_i)) {
|
|
continue;
|
|
}
|
|
|
|
face_sets[neighbor_i] = next_face_set;
|
|
visited_faces[neighbor_i].set(true);
|
|
queue.push(neighbor_i);
|
|
}
|
|
}
|
|
}
|
|
|
|
next_face_set += 1;
|
|
}
|
|
}
|
|
|
|
static void sculpt_face_sets_init_loop(Object *ob, const int mode)
|
|
{
|
|
using namespace blender;
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
if (mode == SCULPT_FACE_SETS_FROM_MATERIALS) {
|
|
const bke::AttributeAccessor attributes = mesh->attributes();
|
|
const VArraySpan<int> material_indices = attributes.lookup_or_default<int>(
|
|
"material_index", ATTR_DOMAIN_FACE, 0);
|
|
for (const int i : IndexRange(mesh->totpoly)) {
|
|
ss->face_sets[i] = material_indices[i] + 1;
|
|
}
|
|
}
|
|
else if (mode == SCULPT_FACE_SETS_FROM_FACE_MAPS) {
|
|
const int *face_maps = static_cast<int *>(CustomData_get_layer(&mesh->pdata, CD_FACEMAP));
|
|
for (const int i : IndexRange(mesh->totpoly)) {
|
|
ss->face_sets[i] = face_maps ? face_maps[i] : 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sculpt_face_set_init_exec(bContext *C, wmOperator *op)
|
|
{
|
|
using namespace blender;
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
|
|
const int mode = RNA_enum_get(op->ptr, "mode");
|
|
|
|
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
|
|
|
|
/* Dyntopo not supported. */
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
|
|
if (!nodes) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
SCULPT_undo_push_begin(ob, op);
|
|
for (const int i : blender::IndexRange(totnode)) {
|
|
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_FACE_SETS);
|
|
}
|
|
|
|
const float threshold = RNA_float_get(op->ptr, "threshold");
|
|
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
ss->face_sets = BKE_sculpt_face_sets_ensure(mesh);
|
|
const bke::AttributeAccessor attributes = mesh->attributes();
|
|
|
|
switch (mode) {
|
|
case SCULPT_FACE_SETS_FROM_LOOSE_PARTS: {
|
|
const VArray<bool> hide_poly = attributes.lookup_or_default<bool>(
|
|
".hide_poly", ATTR_DOMAIN_FACE, false);
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int from_face, const int /*edge*/, const int to_face) {
|
|
return hide_poly[from_face] == hide_poly[to_face];
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_MATERIALS: {
|
|
sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_MATERIALS);
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_NORMALS: {
|
|
const Span<float3> poly_normals(
|
|
reinterpret_cast<const float3 *>(BKE_mesh_poly_normals_ensure(mesh)), mesh->totpoly);
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool {
|
|
return std::abs(math::dot(poly_normals[from_face], poly_normals[to_face])) > threshold;
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_UV_SEAMS: {
|
|
const Span<MEdge> edges = mesh->edges();
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool {
|
|
return (edges[edge].flag & ME_SEAM) == 0;
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_CREASES: {
|
|
const float *creases = static_cast<const float *>(
|
|
CustomData_get_layer(&mesh->edata, CD_CREASE));
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool {
|
|
return creases ? creases[edge] < threshold : true;
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_SHARP_EDGES: {
|
|
const VArraySpan<bool> sharp_edges = mesh->attributes().lookup_or_default<bool>(
|
|
"sharp_edge", ATTR_DOMAIN_EDGE, false);
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool {
|
|
return !sharp_edges[edge];
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_BEVEL_WEIGHT: {
|
|
const float *bevel_weights = static_cast<const float *>(
|
|
CustomData_get_layer(&mesh->edata, CD_BWEIGHT));
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int /*from_face*/, const int edge, const int /*to_face*/) -> bool {
|
|
return bevel_weights ? bevel_weights[edge] < threshold : true;
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_FACE_SET_BOUNDARIES: {
|
|
Array<int> face_sets_copy(Span<int>(ss->face_sets, mesh->totpoly));
|
|
sculpt_face_sets_init_flood_fill(
|
|
ob, [&](const int from_face, const int /*edge*/, const int to_face) -> bool {
|
|
return face_sets_copy[from_face] == face_sets_copy[to_face];
|
|
});
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SETS_FROM_FACE_MAPS: {
|
|
sculpt_face_sets_init_loop(ob, SCULPT_FACE_SETS_FROM_FACE_MAPS);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SCULPT_undo_push_end(ob);
|
|
|
|
/* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */
|
|
SCULPT_visibility_sync_all_from_faces(ob);
|
|
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_update_visibility(nodes[i]);
|
|
}
|
|
|
|
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
|
|
|
|
MEM_SAFE_FREE(nodes);
|
|
|
|
if (BKE_pbvh_type(pbvh) == PBVH_FACES) {
|
|
BKE_mesh_flush_hidden_from_verts(mesh);
|
|
}
|
|
|
|
SCULPT_tag_update_overlays(C);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SCULPT_OT_face_sets_init(wmOperatorType *ot)
|
|
{
|
|
/* identifiers */
|
|
ot->name = "Init Face Sets";
|
|
ot->idname = "SCULPT_OT_face_sets_init";
|
|
ot->description = "Initializes all Face Sets in the mesh";
|
|
|
|
/* api callbacks */
|
|
ot->exec = sculpt_face_set_init_exec;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(
|
|
ot->srna, "mode", prop_sculpt_face_sets_init_types, SCULPT_FACE_SET_MASKED, "Mode", "");
|
|
RNA_def_float(
|
|
ot->srna,
|
|
"threshold",
|
|
0.5f,
|
|
0.0f,
|
|
1.0f,
|
|
"Threshold",
|
|
"Minimum value to consider a certain attribute a boundary when creating the Face Sets",
|
|
0.0f,
|
|
1.0f);
|
|
}
|
|
|
|
enum eSculptFaceGroupVisibilityModes {
|
|
SCULPT_FACE_SET_VISIBILITY_TOGGLE = 0,
|
|
SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE = 1,
|
|
SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE = 2,
|
|
SCULPT_FACE_SET_VISIBILITY_INVERT = 3,
|
|
};
|
|
|
|
static EnumPropertyItem prop_sculpt_face_sets_change_visibility_types[] = {
|
|
{
|
|
SCULPT_FACE_SET_VISIBILITY_TOGGLE,
|
|
"TOGGLE",
|
|
0,
|
|
"Toggle Visibility",
|
|
"Hide all Face Sets except for the active one",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE,
|
|
"SHOW_ACTIVE",
|
|
0,
|
|
"Show Active Face Set",
|
|
"Show Active Face Set",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE,
|
|
"HIDE_ACTIVE",
|
|
0,
|
|
"Hide Active Face Sets",
|
|
"Hide Active Face Sets",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_VISIBILITY_INVERT,
|
|
"INVERT",
|
|
0,
|
|
"Invert Face Set Visibility",
|
|
"Invert Face Set Visibility",
|
|
},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static int sculpt_face_sets_change_visibility_exec(bContext *C, wmOperator *op)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
|
|
Mesh *mesh = BKE_object_get_original_mesh(ob);
|
|
|
|
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
|
|
|
|
/* Not supported for dyntopo. */
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const int mode = RNA_enum_get(op->ptr, "mode");
|
|
const int tot_vert = SCULPT_vertex_count_get(ss);
|
|
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
|
|
if (totnode == 0) {
|
|
MEM_SAFE_FREE(nodes);
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
const int active_face_set = SCULPT_active_face_set_get(ss);
|
|
|
|
SCULPT_undo_push_begin(ob, op);
|
|
for (int i = 0; i < totnode; i++) {
|
|
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_HIDDEN);
|
|
}
|
|
|
|
switch (mode) {
|
|
case SCULPT_FACE_SET_VISIBILITY_TOGGLE: {
|
|
bool hidden_vertex = false;
|
|
|
|
/* This can fail with regular meshes with non-manifold geometry as the visibility state can't
|
|
* be synced from face sets to non-manifold vertices. */
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
|
for (int i = 0; i < tot_vert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
if (!SCULPT_vertex_visible_get(ss, vertex)) {
|
|
hidden_vertex = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ss->hide_poly) {
|
|
for (int i = 0; i < ss->totfaces; i++) {
|
|
if (ss->hide_poly[i]) {
|
|
hidden_vertex = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh);
|
|
|
|
if (hidden_vertex) {
|
|
SCULPT_face_visibility_all_set(ss, true);
|
|
}
|
|
else {
|
|
if (ss->face_sets) {
|
|
SCULPT_face_visibility_all_set(ss, false);
|
|
SCULPT_face_set_visibility_set(ss, active_face_set, true);
|
|
}
|
|
else {
|
|
SCULPT_face_visibility_all_set(ss, true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE:
|
|
ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh);
|
|
|
|
if (ss->face_sets) {
|
|
SCULPT_face_visibility_all_set(ss, false);
|
|
SCULPT_face_set_visibility_set(ss, active_face_set, true);
|
|
}
|
|
else {
|
|
SCULPT_face_set_visibility_set(ss, active_face_set, true);
|
|
}
|
|
break;
|
|
case SCULPT_FACE_SET_VISIBILITY_HIDE_ACTIVE:
|
|
ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh);
|
|
|
|
if (ss->face_sets) {
|
|
SCULPT_face_set_visibility_set(ss, active_face_set, false);
|
|
}
|
|
else {
|
|
SCULPT_face_visibility_all_set(ss, false);
|
|
}
|
|
|
|
break;
|
|
case SCULPT_FACE_SET_VISIBILITY_INVERT:
|
|
ss->hide_poly = BKE_sculpt_hide_poly_ensure(mesh);
|
|
SCULPT_face_visibility_all_invert(ss);
|
|
break;
|
|
}
|
|
|
|
/* For modes that use the cursor active vertex, update the rotation origin for viewport
|
|
* navigation.
|
|
*/
|
|
if (ELEM(mode, SCULPT_FACE_SET_VISIBILITY_TOGGLE, SCULPT_FACE_SET_VISIBILITY_SHOW_ACTIVE)) {
|
|
UnifiedPaintSettings *ups = &CTX_data_tool_settings(C)->unified_paint_settings;
|
|
float location[3];
|
|
copy_v3_v3(location, SCULPT_active_vertex_co_get(ss));
|
|
mul_m4_v3(ob->object_to_world, location);
|
|
copy_v3_v3(ups->average_stroke_accum, location);
|
|
ups->average_stroke_counter = 1;
|
|
ups->last_stroke_valid = true;
|
|
}
|
|
|
|
/* Sync face sets visibility and vertex visibility. */
|
|
SCULPT_visibility_sync_all_from_faces(ob);
|
|
|
|
SCULPT_undo_push_end(ob);
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_update_visibility(nodes[i]);
|
|
}
|
|
|
|
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
|
|
|
|
MEM_SAFE_FREE(nodes);
|
|
|
|
SCULPT_tag_update_overlays(C);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
static int sculpt_face_sets_change_visibility_invoke(bContext *C,
|
|
wmOperator *op,
|
|
const wmEvent *event)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
/* Update the active vertex and Face Set using the cursor position to avoid relying on the paint
|
|
* cursor updates. */
|
|
SculptCursorGeometryInfo sgi;
|
|
const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])};
|
|
SCULPT_vertex_random_access_ensure(ss);
|
|
SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false);
|
|
|
|
return sculpt_face_sets_change_visibility_exec(C, op);
|
|
}
|
|
|
|
void SCULPT_OT_face_sets_change_visibility(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Face Sets Visibility";
|
|
ot->idname = "SCULPT_OT_face_set_change_visibility";
|
|
ot->description = "Change the visibility of the Face Sets of the sculpt";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sculpt_face_sets_change_visibility_exec;
|
|
ot->invoke = sculpt_face_sets_change_visibility_invoke;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(ot->srna,
|
|
"mode",
|
|
prop_sculpt_face_sets_change_visibility_types,
|
|
SCULPT_FACE_SET_VISIBILITY_TOGGLE,
|
|
"Mode",
|
|
"");
|
|
}
|
|
|
|
static int sculpt_face_sets_randomize_colors_exec(bContext *C, wmOperator * /*op*/)
|
|
{
|
|
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
/* Dyntopo not supported. */
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
if (!ss->face_sets) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
|
|
mesh->face_sets_color_seed += 1;
|
|
if (ss->face_sets) {
|
|
const int random_index = clamp_i(ss->totfaces * BLI_hash_int_01(mesh->face_sets_color_seed),
|
|
0,
|
|
max_ii(0, ss->totfaces - 1));
|
|
mesh->face_sets_color_default = ss->face_sets[random_index];
|
|
}
|
|
BKE_pbvh_face_sets_color_set(pbvh, mesh->face_sets_color_seed, mesh->face_sets_color_default);
|
|
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_redraw(nodes[i]);
|
|
}
|
|
|
|
MEM_SAFE_FREE(nodes);
|
|
|
|
SCULPT_tag_update_overlays(C);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SCULPT_OT_face_sets_randomize_colors(wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Randomize Face Sets Colors";
|
|
ot->idname = "SCULPT_OT_face_sets_randomize_colors";
|
|
ot->description = "Generates a new set of random colors to render the Face Sets in the viewport";
|
|
|
|
/* Api callbacks. */
|
|
ot->exec = sculpt_face_sets_randomize_colors_exec;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
}
|
|
|
|
enum eSculptFaceSetEditMode {
|
|
SCULPT_FACE_SET_EDIT_GROW = 0,
|
|
SCULPT_FACE_SET_EDIT_SHRINK = 1,
|
|
SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY = 2,
|
|
SCULPT_FACE_SET_EDIT_FAIR_POSITIONS = 3,
|
|
SCULPT_FACE_SET_EDIT_FAIR_TANGENCY = 4,
|
|
};
|
|
|
|
static EnumPropertyItem prop_sculpt_face_sets_edit_types[] = {
|
|
{
|
|
SCULPT_FACE_SET_EDIT_GROW,
|
|
"GROW",
|
|
0,
|
|
"Grow Face Set",
|
|
"Grows the Face Sets boundary by one face based on mesh topology",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_EDIT_SHRINK,
|
|
"SHRINK",
|
|
0,
|
|
"Shrink Face Set",
|
|
"Shrinks the Face Sets boundary by one face based on mesh topology",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY,
|
|
"DELETE_GEOMETRY",
|
|
0,
|
|
"Delete Geometry",
|
|
"Deletes the faces that are assigned to the Face Set",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_EDIT_FAIR_POSITIONS,
|
|
"FAIR_POSITIONS",
|
|
0,
|
|
"Fair Positions",
|
|
"Creates a smooth as possible geometry patch from the Face Set minimizing changes in "
|
|
"vertex positions",
|
|
},
|
|
{
|
|
SCULPT_FACE_SET_EDIT_FAIR_TANGENCY,
|
|
"FAIR_TANGENCY",
|
|
0,
|
|
"Fair Tangency",
|
|
"Creates a smooth as possible geometry patch from the Face Set minimizing changes in "
|
|
"vertex tangents",
|
|
},
|
|
{0, nullptr, 0, nullptr, nullptr},
|
|
};
|
|
|
|
static void sculpt_face_set_grow(Object *ob,
|
|
SculptSession *ss,
|
|
const int *prev_face_sets,
|
|
const int active_face_set_id,
|
|
const bool modify_hidden)
|
|
{
|
|
Mesh *mesh = BKE_mesh_from_object(ob);
|
|
const MPoly *polys = BKE_mesh_polys(mesh);
|
|
const MLoop *loops = BKE_mesh_loops(mesh);
|
|
|
|
for (int p = 0; p < mesh->totpoly; p++) {
|
|
if (!modify_hidden && prev_face_sets[p] <= 0) {
|
|
continue;
|
|
}
|
|
const MPoly *c_poly = &polys[p];
|
|
for (int l = 0; l < c_poly->totloop; l++) {
|
|
const MLoop *c_loop = &loops[c_poly->loopstart + l];
|
|
const MeshElemMap *vert_map = &ss->pmap[c_loop->v];
|
|
for (int i = 0; i < vert_map->count; i++) {
|
|
const int neighbor_face_index = vert_map->indices[i];
|
|
if (neighbor_face_index == p) {
|
|
continue;
|
|
}
|
|
if (abs(prev_face_sets[neighbor_face_index]) == active_face_set_id) {
|
|
ss->face_sets[p] = active_face_set_id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void sculpt_face_set_shrink(Object *ob,
|
|
SculptSession *ss,
|
|
const int *prev_face_sets,
|
|
const int active_face_set_id,
|
|
const bool modify_hidden)
|
|
{
|
|
Mesh *mesh = BKE_mesh_from_object(ob);
|
|
const MPoly *polys = BKE_mesh_polys(mesh);
|
|
const MLoop *loops = BKE_mesh_loops(mesh);
|
|
for (int p = 0; p < mesh->totpoly; p++) {
|
|
if (!modify_hidden && prev_face_sets[p] <= 0) {
|
|
continue;
|
|
}
|
|
if (abs(prev_face_sets[p]) == active_face_set_id) {
|
|
const MPoly *c_poly = &polys[p];
|
|
for (int l = 0; l < c_poly->totloop; l++) {
|
|
const MLoop *c_loop = &loops[c_poly->loopstart + l];
|
|
const MeshElemMap *vert_map = &ss->pmap[c_loop->v];
|
|
for (int i = 0; i < vert_map->count; i++) {
|
|
const int neighbor_face_index = vert_map->indices[i];
|
|
if (neighbor_face_index == p) {
|
|
continue;
|
|
}
|
|
if (abs(prev_face_sets[neighbor_face_index]) != active_face_set_id) {
|
|
ss->face_sets[p] = prev_face_sets[neighbor_face_index];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool check_single_face_set(SculptSession *ss,
|
|
const int *face_sets,
|
|
const bool check_visible_only)
|
|
{
|
|
if (face_sets == nullptr) {
|
|
return true;
|
|
}
|
|
int first_face_set = SCULPT_FACE_SET_NONE;
|
|
if (check_visible_only) {
|
|
for (int f = 0; f < ss->totfaces; f++) {
|
|
if (ss->hide_poly && ss->hide_poly[f]) {
|
|
continue;
|
|
}
|
|
first_face_set = face_sets[f];
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
first_face_set = face_sets[0];
|
|
}
|
|
|
|
if (first_face_set == SCULPT_FACE_SET_NONE) {
|
|
return true;
|
|
}
|
|
|
|
for (int f = 0; f < ss->totfaces; f++) {
|
|
if (check_visible_only && ss->hide_poly && ss->hide_poly[f]) {
|
|
continue;
|
|
}
|
|
if (face_sets[f] != first_face_set) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void sculpt_face_set_delete_geometry(Object *ob,
|
|
SculptSession *ss,
|
|
const int active_face_set_id,
|
|
const bool modify_hidden)
|
|
{
|
|
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
|
|
BMeshCreateParams create_params{};
|
|
create_params.use_toolflags = true;
|
|
BMesh *bm = BM_mesh_create(&allocsize, &create_params);
|
|
|
|
BMeshFromMeshParams convert_params{};
|
|
convert_params.calc_vert_normal = true;
|
|
convert_params.calc_face_normal = true;
|
|
BM_mesh_bm_from_me(bm, mesh, &convert_params);
|
|
|
|
BM_mesh_elem_table_init(bm, BM_FACE);
|
|
BM_mesh_elem_table_ensure(bm, BM_FACE);
|
|
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
|
|
BMIter iter;
|
|
BMFace *f;
|
|
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
|
|
const int face_index = BM_elem_index_get(f);
|
|
if (!modify_hidden && ss->hide_poly && ss->hide_poly[face_index]) {
|
|
continue;
|
|
}
|
|
BM_elem_flag_set(f, BM_ELEM_TAG, ss->face_sets[face_index] == active_face_set_id);
|
|
}
|
|
BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_FACES);
|
|
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
|
|
|
|
BMeshToMeshParams bmesh_to_mesh_params{};
|
|
bmesh_to_mesh_params.calc_object_remap = false;
|
|
BM_mesh_bm_to_me(nullptr, bm, mesh, &bmesh_to_mesh_params);
|
|
|
|
BM_mesh_free(bm);
|
|
}
|
|
|
|
static void sculpt_face_set_edit_fair_face_set(Object *ob,
|
|
const int active_face_set_id,
|
|
const eMeshFairingDepth fair_order)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
const int totvert = SCULPT_vertex_count_get(ss);
|
|
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
bool *fair_verts = static_cast<bool *>(
|
|
MEM_malloc_arrayN(totvert, sizeof(bool), "fair vertices"));
|
|
|
|
SCULPT_boundary_info_ensure(ob);
|
|
|
|
for (int i = 0; i < totvert; i++) {
|
|
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
|
|
|
|
fair_verts[i] = !SCULPT_vertex_is_boundary(ss, vertex) &&
|
|
SCULPT_vertex_has_face_set(ss, vertex, active_face_set_id) &&
|
|
SCULPT_vertex_has_unique_face_set(ss, vertex);
|
|
}
|
|
|
|
float(*positions)[3] = SCULPT_mesh_deformed_positions_get(ss);
|
|
BKE_mesh_prefair_and_fair_verts(mesh, positions, fair_verts, fair_order);
|
|
MEM_freeN(fair_verts);
|
|
}
|
|
|
|
static void sculpt_face_set_apply_edit(Object *ob,
|
|
const int active_face_set_id,
|
|
const int mode,
|
|
const bool modify_hidden)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
|
|
switch (mode) {
|
|
case SCULPT_FACE_SET_EDIT_GROW: {
|
|
int *prev_face_sets = static_cast<int *>(MEM_dupallocN(ss->face_sets));
|
|
sculpt_face_set_grow(ob, ss, prev_face_sets, active_face_set_id, modify_hidden);
|
|
MEM_SAFE_FREE(prev_face_sets);
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SET_EDIT_SHRINK: {
|
|
int *prev_face_sets = static_cast<int *>(MEM_dupallocN(ss->face_sets));
|
|
sculpt_face_set_shrink(ob, ss, prev_face_sets, active_face_set_id, modify_hidden);
|
|
MEM_SAFE_FREE(prev_face_sets);
|
|
break;
|
|
}
|
|
case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
|
|
sculpt_face_set_delete_geometry(ob, ss, active_face_set_id, modify_hidden);
|
|
break;
|
|
case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS:
|
|
sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_POSITION);
|
|
break;
|
|
case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY:
|
|
sculpt_face_set_edit_fair_face_set(ob, active_face_set_id, MESH_FAIRING_DEPTH_TANGENCY);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool sculpt_face_set_edit_is_operation_valid(SculptSession *ss,
|
|
const eSculptFaceSetEditMode mode,
|
|
const bool modify_hidden)
|
|
{
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_BMESH) {
|
|
/* Dyntopo is not supported. */
|
|
return false;
|
|
}
|
|
|
|
if (mode == SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY) {
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
|
/* Modification of base mesh geometry requires special remapping of multires displacement,
|
|
* which does not happen here.
|
|
* Disable delete operation. It can be supported in the future by doing similar displacement
|
|
* data remapping as what happens in the mesh edit mode. */
|
|
return false;
|
|
}
|
|
if (check_single_face_set(ss, ss->face_sets, !modify_hidden)) {
|
|
/* Cancel the operator if the mesh only contains one Face Set to avoid deleting the
|
|
* entire object. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (ELEM(mode, SCULPT_FACE_SET_EDIT_FAIR_POSITIONS, SCULPT_FACE_SET_EDIT_FAIR_TANGENCY)) {
|
|
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
|
|
/* TODO: Multires topology representation using grids and duplicates can't be used directly
|
|
* by the fair algorithm. Multires topology needs to be exposed in a different way or
|
|
* converted to a mesh for this operation. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void sculpt_face_set_edit_modify_geometry(bContext *C,
|
|
Object *ob,
|
|
const int active_face_set,
|
|
const eSculptFaceSetEditMode mode,
|
|
const bool modify_hidden,
|
|
wmOperator *op)
|
|
{
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
ED_sculpt_undo_geometry_begin(ob, op);
|
|
sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden);
|
|
ED_sculpt_undo_geometry_end(ob);
|
|
BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL);
|
|
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
|
|
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
|
|
}
|
|
|
|
static void face_set_edit_do_post_visibility_updates(Object *ob, PBVHNode **nodes, int totnode)
|
|
{
|
|
SculptSession *ss = ob->sculpt;
|
|
PBVH *pbvh = ss->pbvh;
|
|
Mesh *mesh = static_cast<Mesh *>(ob->data);
|
|
|
|
/* Sync face sets visibility and vertex visibility as now all Face Sets are visible. */
|
|
SCULPT_visibility_sync_all_from_faces(ob);
|
|
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_update_visibility(nodes[i]);
|
|
}
|
|
|
|
BKE_pbvh_update_vertex_data(ss->pbvh, PBVH_UpdateVisibility);
|
|
|
|
if (BKE_pbvh_type(pbvh) == PBVH_FACES) {
|
|
BKE_mesh_flush_hidden_from_verts(mesh);
|
|
}
|
|
}
|
|
|
|
static void sculpt_face_set_edit_modify_face_sets(Object *ob,
|
|
const int active_face_set,
|
|
const eSculptFaceSetEditMode mode,
|
|
const bool modify_hidden,
|
|
wmOperator *op)
|
|
{
|
|
PBVH *pbvh = ob->sculpt->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
|
|
if (!nodes) {
|
|
return;
|
|
}
|
|
SCULPT_undo_push_begin(ob, op);
|
|
for (const int i : blender::IndexRange(totnode)) {
|
|
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_FACE_SETS);
|
|
}
|
|
sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, modify_hidden);
|
|
SCULPT_undo_push_end(ob);
|
|
face_set_edit_do_post_visibility_updates(ob, nodes, totnode);
|
|
MEM_freeN(nodes);
|
|
}
|
|
|
|
static void sculpt_face_set_edit_modify_coordinates(bContext *C,
|
|
Object *ob,
|
|
const int active_face_set,
|
|
const eSculptFaceSetEditMode mode,
|
|
wmOperator *op)
|
|
{
|
|
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
|
|
SculptSession *ss = ob->sculpt;
|
|
PBVH *pbvh = ss->pbvh;
|
|
PBVHNode **nodes;
|
|
int totnode;
|
|
BKE_pbvh_search_gather(pbvh, nullptr, nullptr, &nodes, &totnode);
|
|
SCULPT_undo_push_begin(ob, op);
|
|
for (int i = 0; i < totnode; i++) {
|
|
BKE_pbvh_node_mark_update(nodes[i]);
|
|
SCULPT_undo_push_node(ob, nodes[i], SCULPT_UNDO_COORDS);
|
|
}
|
|
sculpt_face_set_apply_edit(ob, abs(active_face_set), mode, false);
|
|
|
|
if (ss->deform_modifiers_active || ss->shapekey_active) {
|
|
SCULPT_flush_stroke_deform(sd, ob, true);
|
|
}
|
|
SCULPT_flush_update_step(C, SCULPT_UPDATE_COORDS);
|
|
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_COORDS);
|
|
SCULPT_undo_push_end(ob);
|
|
MEM_freeN(nodes);
|
|
}
|
|
|
|
static int sculpt_face_set_edit_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|
{
|
|
Object *ob = CTX_data_active_object(C);
|
|
SculptSession *ss = ob->sculpt;
|
|
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
|
|
|
|
const eSculptFaceSetEditMode mode = static_cast<eSculptFaceSetEditMode>(
|
|
RNA_enum_get(op->ptr, "mode"));
|
|
const bool modify_hidden = RNA_boolean_get(op->ptr, "modify_hidden");
|
|
|
|
if (!sculpt_face_set_edit_is_operation_valid(ss, mode, modify_hidden)) {
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
ss->face_sets = BKE_sculpt_face_sets_ensure(BKE_mesh_from_object(ob));
|
|
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, false, false);
|
|
|
|
/* Update the current active Face Set and Vertex as the operator can be used directly from the
|
|
* tool without brush cursor. */
|
|
SculptCursorGeometryInfo sgi;
|
|
const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])};
|
|
if (!SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false)) {
|
|
/* The cursor is not over the mesh. Cancel to avoid editing the last updated Face Set ID. */
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
const int active_face_set = SCULPT_active_face_set_get(ss);
|
|
|
|
switch (mode) {
|
|
case SCULPT_FACE_SET_EDIT_DELETE_GEOMETRY:
|
|
sculpt_face_set_edit_modify_geometry(C, ob, active_face_set, mode, modify_hidden, op);
|
|
break;
|
|
case SCULPT_FACE_SET_EDIT_GROW:
|
|
case SCULPT_FACE_SET_EDIT_SHRINK:
|
|
sculpt_face_set_edit_modify_face_sets(ob, active_face_set, mode, modify_hidden, op);
|
|
break;
|
|
case SCULPT_FACE_SET_EDIT_FAIR_POSITIONS:
|
|
case SCULPT_FACE_SET_EDIT_FAIR_TANGENCY:
|
|
sculpt_face_set_edit_modify_coordinates(C, ob, active_face_set, mode, op);
|
|
break;
|
|
}
|
|
|
|
SCULPT_tag_update_overlays(C);
|
|
|
|
return OPERATOR_FINISHED;
|
|
}
|
|
|
|
void SCULPT_OT_face_sets_edit(struct wmOperatorType *ot)
|
|
{
|
|
/* Identifiers. */
|
|
ot->name = "Edit Face Set";
|
|
ot->idname = "SCULPT_OT_face_set_edit";
|
|
ot->description = "Edits the current active Face Set";
|
|
|
|
/* Api callbacks. */
|
|
ot->invoke = sculpt_face_set_edit_invoke;
|
|
ot->poll = SCULPT_mode_poll;
|
|
|
|
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
|
|
|
|
RNA_def_enum(
|
|
ot->srna, "mode", prop_sculpt_face_sets_edit_types, SCULPT_FACE_SET_EDIT_GROW, "Mode", "");
|
|
ot->prop = RNA_def_boolean(ot->srna,
|
|
"modify_hidden",
|
|
true,
|
|
"Modify Hidden",
|
|
"Apply the edit operation to hidden Face Sets");
|
|
}
|