tornavis/source/blender/editors/sculpt_paint/sculpt_ops.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1404 lines
45 KiB
C++
Raw Normal View History

/* SPDX-FileCopyrightText: 2006 by Nicholas Bishop. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edsculpt
* Implements the Sculpt Mode tools.
*/
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_gsqueue.h"
#include "BLI_task.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
#include "DNA_brush_types.h"
#include "DNA_customdata_types.h"
#include "DNA_listBase.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BKE_attribute.h"
#include "BKE_brush.hh"
#include "BKE_ccg.h"
#include "BKE_context.hh"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_mesh.hh"
#include "BKE_mesh_mirror.hh"
2023-11-14 09:30:40 +01:00
#include "BKE_modifier.hh"
#include "BKE_multires.hh"
#include "BKE_object.hh"
#include "BKE_paint.hh"
#include "BKE_pbvh_api.hh"
#include "BKE_report.h"
#include "BKE_scene.h"
#include "DEG_depsgraph.hh"
#include "IMB_colormanagement.h"
#include "WM_api.hh"
#include "WM_message.hh"
#include "WM_toolsystem.h"
#include "WM_types.hh"
#include "ED_image.hh"
#include "ED_object.hh"
#include "ED_screen.hh"
#include "ED_sculpt.hh"
#include "paint_intern.hh"
#include "sculpt_intern.hh"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "bmesh.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
/* Reset the copy of the mesh that is being sculpted on (currently just for the layer brush). */
static int sculpt_set_persistent_base_exec(bContext *C, wmOperator * /*op*/)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt: New attribute API New unified attribute API for sculpt code. = Basic Design = The sculpt attribute API can create temporary or permanent attributes (only supported in `PBVH_FACES` mode). Attributes are created via `BKE_sculpt_attribute_ensure.` Attributes can be explicit CustomData attributes or simple array-based pseudo-attributes (this is useful for PBVH_GRIDS and PBVH_BMESH). == `SculptAttributePointers` == There is a structure in `SculptSession` for convenience attribute pointers, `ss->attrs`. Standard attributes should assign these; the attribute API will automatically clear them when the associated attributes are released. For example, the automasking code stores its factor attribute layer in `ss->attrs.automasking_factor`. == Naming == Temporary attributes should use the SCULPT_ATTRIBUTE_NAME macro for naming, it takes an entry in `SculptAttributePointers` and builds a layer name. == `SculptAttribute` == Attributes are referenced by a special `SculptAttribute` structure, which holds all the info needed to look up elements of an attribute at run time. All of these structures live in a preallocated flat array in `SculptSession`, `ss->temp_attributes`. This is extremely important. Since any change to the `CustomData` layout can in principle invalidate every extant `SculptAttribute`, having them all in one block of memory whose location doesn't change allows us to update them transparently. This makes for much simpler code and eliminates bugs. To see why this is tricky to get right, imagine we want to create three attributes in PBVH_BMESH mode and we provide our own `SculptAttribute` structs for the API to fill in. Each new layer will invalidate the `CustomData` block offsets in the prior one, leading to memory corruption. Reviewed by: Brecht Van Lommel Differential Revision: https://developer.blender.org/D15496 Ref D15496
2022-09-16 21:20:28 +02:00
/* Do not allow in DynTopo just yet. */
if (!ss || (ss && ss->bm)) {
return OPERATOR_FINISHED;
}
SCULPT_vertex_random_access_ensure(ss);
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
Sculpt: New attribute API New unified attribute API for sculpt code. = Basic Design = The sculpt attribute API can create temporary or permanent attributes (only supported in `PBVH_FACES` mode). Attributes are created via `BKE_sculpt_attribute_ensure.` Attributes can be explicit CustomData attributes or simple array-based pseudo-attributes (this is useful for PBVH_GRIDS and PBVH_BMESH). == `SculptAttributePointers` == There is a structure in `SculptSession` for convenience attribute pointers, `ss->attrs`. Standard attributes should assign these; the attribute API will automatically clear them when the associated attributes are released. For example, the automasking code stores its factor attribute layer in `ss->attrs.automasking_factor`. == Naming == Temporary attributes should use the SCULPT_ATTRIBUTE_NAME macro for naming, it takes an entry in `SculptAttributePointers` and builds a layer name. == `SculptAttribute` == Attributes are referenced by a special `SculptAttribute` structure, which holds all the info needed to look up elements of an attribute at run time. All of these structures live in a preallocated flat array in `SculptSession`, `ss->temp_attributes`. This is extremely important. Since any change to the `CustomData` layout can in principle invalidate every extant `SculptAttribute`, having them all in one block of memory whose location doesn't change allows us to update them transparently. This makes for much simpler code and eliminates bugs. To see why this is tricky to get right, imagine we want to create three attributes in PBVH_BMESH mode and we provide our own `SculptAttribute` structs for the API to fill in. Each new layer will invalidate the `CustomData` block offsets in the prior one, leading to memory corruption. Reviewed by: Brecht Van Lommel Differential Revision: https://developer.blender.org/D15496 Ref D15496
2022-09-16 21:20:28 +02:00
SculptAttributeParams params = {0};
params.permanent = true;
ss->attrs.persistent_co = BKE_sculpt_attribute_ensure(
ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_co), &params);
ss->attrs.persistent_no = BKE_sculpt_attribute_ensure(
ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT3, SCULPT_ATTRIBUTE_NAME(persistent_no), &params);
ss->attrs.persistent_disp = BKE_sculpt_attribute_ensure(
ob, ATTR_DOMAIN_POINT, CD_PROP_FLOAT, SCULPT_ATTRIBUTE_NAME(persistent_disp), &params);
const int totvert = SCULPT_vertex_count_get(ss);
for (int i = 0; i < totvert; i++) {
PBVHVertRef vertex = BKE_pbvh_index_to_vertex(ss->pbvh, i);
Sculpt: New attribute API New unified attribute API for sculpt code. = Basic Design = The sculpt attribute API can create temporary or permanent attributes (only supported in `PBVH_FACES` mode). Attributes are created via `BKE_sculpt_attribute_ensure.` Attributes can be explicit CustomData attributes or simple array-based pseudo-attributes (this is useful for PBVH_GRIDS and PBVH_BMESH). == `SculptAttributePointers` == There is a structure in `SculptSession` for convenience attribute pointers, `ss->attrs`. Standard attributes should assign these; the attribute API will automatically clear them when the associated attributes are released. For example, the automasking code stores its factor attribute layer in `ss->attrs.automasking_factor`. == Naming == Temporary attributes should use the SCULPT_ATTRIBUTE_NAME macro for naming, it takes an entry in `SculptAttributePointers` and builds a layer name. == `SculptAttribute` == Attributes are referenced by a special `SculptAttribute` structure, which holds all the info needed to look up elements of an attribute at run time. All of these structures live in a preallocated flat array in `SculptSession`, `ss->temp_attributes`. This is extremely important. Since any change to the `CustomData` layout can in principle invalidate every extant `SculptAttribute`, having them all in one block of memory whose location doesn't change allows us to update them transparently. This makes for much simpler code and eliminates bugs. To see why this is tricky to get right, imagine we want to create three attributes in PBVH_BMESH mode and we provide our own `SculptAttribute` structs for the API to fill in. Each new layer will invalidate the `CustomData` block offsets in the prior one, leading to memory corruption. Reviewed by: Brecht Van Lommel Differential Revision: https://developer.blender.org/D15496 Ref D15496
2022-09-16 21:20:28 +02:00
copy_v3_v3((float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_co),
SCULPT_vertex_co_get(ss, vertex));
SCULPT_vertex_normal_get(
ss, vertex, (float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_no));
(*(float *)SCULPT_vertex_attr_get(vertex, ss->attrs.persistent_disp)) = 0.0f;
}
return OPERATOR_FINISHED;
}
static void SCULPT_OT_set_persistent_base(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Set Persistent Base";
ot->idname = "SCULPT_OT_set_persistent_base";
ot->description = "Reset the copy of the mesh that is being sculpted on";
/* API callbacks. */
ot->exec = sculpt_set_persistent_base_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/************************* SCULPT_OT_optimize *************************/
static int sculpt_optimize_exec(bContext *C, wmOperator * /*op*/)
{
Object *ob = CTX_data_active_object(C);
SCULPT_pbvh_clear(ob);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
/* The BVH gets less optimal more quickly with dynamic topology than
* regular sculpting. There is no doubt more clever stuff we can do to
* optimize it on the fly, but for now this gives the user a nicer way
* to recalculate it than toggling modes. */
static void SCULPT_OT_optimize(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Rebuild BVH";
ot->idname = "SCULPT_OT_optimize";
ot->description = "Recalculate the sculpt BVH to improve performance";
/* API callbacks. */
ot->exec = sculpt_optimize_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/********************* Dynamic topology symmetrize ********************/
static bool sculpt_no_multires_poll(bContext *C)
{
Object *ob = CTX_data_active_object(C);
if (SCULPT_mode_poll(C) && ob->sculpt && ob->sculpt->pbvh) {
return BKE_pbvh_type(ob->sculpt->pbvh) != PBVH_GRIDS;
}
return false;
}
static int sculpt_symmetrize_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Object *ob = CTX_data_active_object(C);
const Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
SculptSession *ss = ob->sculpt;
PBVH *pbvh = ss->pbvh;
const float dist = RNA_float_get(op->ptr, "merge_tolerance");
if (!pbvh) {
return OPERATOR_CANCELLED;
}
switch (BKE_pbvh_type(pbvh)) {
case PBVH_BMESH: {
/* Dyntopo Symmetrize. */
/* To simplify undo for symmetrize, all BMesh elements are logged
* as deleted, then after symmetrize operation all BMesh elements
* are logged as added (as opposed to attempting to store just the
* parts that symmetrize modifies). */
SCULPT_undo_push_begin(ob, op);
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_SYMMETRIZE);
BM_log_before_all_removed(ss->bm, ss->bm_log);
BM_mesh_toolflags_set(ss->bm, true);
/* Symmetrize and re-triangulate. */
BMO_op_callf(ss->bm,
(BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
"symmetrize input=%avef direction=%i dist=%f use_shapekey=%b",
sd->symmetrize_direction,
dist,
true);
SCULPT_dynamic_topology_triangulate(ss->bm);
/* Bisect operator flags edges (keep tags clean for edge queue). */
BM_mesh_elem_hflag_disable_all(ss->bm, BM_EDGE, BM_ELEM_TAG, false);
BM_mesh_toolflags_set(ss->bm, false);
/* Finish undo. */
BM_log_all_added(ss->bm, ss->bm_log);
SCULPT_undo_push_end(ob);
break;
}
case PBVH_FACES: {
/* Mesh Symmetrize. */
ED_sculpt_undo_geometry_begin(ob, op);
Mesh *mesh = static_cast<Mesh *>(ob->data);
BKE_mesh_mirror_apply_mirror_on_axis(bmain, mesh, sd->symmetrize_direction, dist);
ED_sculpt_undo_geometry_end(ob);
BKE_mesh_batch_cache_dirty_tag(mesh, BKE_MESH_BATCH_DIRTY_ALL);
break;
}
case PBVH_GRIDS:
return OPERATOR_CANCELLED;
}
SCULPT_topology_islands_invalidate(ss);
/* Redraw. */
SCULPT_pbvh_clear(ob);
WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
return OPERATOR_FINISHED;
}
static void SCULPT_OT_symmetrize(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Symmetrize";
ot->idname = "SCULPT_OT_symmetrize";
ot->description = "Symmetrize the topology modifications";
/* API callbacks. */
ot->exec = sculpt_symmetrize_exec;
ot->poll = sculpt_no_multires_poll;
PropertyRNA *prop = RNA_def_float(ot->srna,
"merge_tolerance",
0.0005f,
0.0f,
FLT_MAX,
"Merge Distance",
"Distance within which symmetrical vertices are merged",
0.0f,
1.0f);
RNA_def_property_ui_range(prop, 0.0, FLT_MAX, 0.001, 5);
}
/**** Toggle operator for turning sculpt mode on or off ****/
static void sculpt_init_session(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
/* Create persistent sculpt mode data. */
BKE_sculpt_toolsettings_data_ensure(scene);
/* Create sculpt mode session data. */
if (ob->sculpt != nullptr) {
BKE_sculptsession_free(ob);
}
ob->sculpt = MEM_new<SculptSession>(__func__);
ob->sculpt->mode_type = OB_MODE_SCULPT;
/* Trigger evaluation of modifier stack to ensure
* multires modifier sets .runtime.ccg in
* the evaluated mesh.
*/
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
BKE_scene_graph_evaluated_ensure(depsgraph, bmain);
/* This function expects a fully evaluated depsgraph. */
BKE_sculpt_update_object_for_edit(depsgraph, ob, false, false, false);
SculptSession *ss = ob->sculpt;
if (ss->face_sets) {
/* Here we can detect geometry that was just added to Sculpt Mode as it has the
* SCULPT_FACE_SET_NONE assigned, so we can create a new Face Set for it. */
/* In sculpt mode all geometry that is assigned to SCULPT_FACE_SET_NONE is considered as not
* initialized, which is used is some operators that modify the mesh topology to perform
* certain actions in the new faces. After these operations are finished, all faces should have
* a valid face set ID assigned (different from SCULPT_FACE_SET_NONE) to manage their
* visibility correctly. */
/* TODO(pablodp606): Based on this we can improve the UX in future tools for creating new
* objects, like moving the transform pivot position to the new area or masking existing
* geometry. */
const int new_face_set = SCULPT_face_set_next_available_get(ss);
for (int i = 0; i < ss->totfaces; i++) {
if (ss->face_sets[i] == SCULPT_FACE_SET_NONE) {
ss->face_sets[i] = new_face_set;
}
}
}
}
void SCULPT_ensure_valid_pivot(const Object *ob, Scene *scene)
{
UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
const SculptSession *ss = ob->sculpt;
/* Account for the case where no objects are evaluated. */
if (!ss->pbvh) {
return;
}
/* No valid pivot? Use bounding box center. */
if (ups->average_stroke_counter == 0 || !ups->last_stroke_valid) {
float location[3], max[3];
BKE_pbvh_bounding_box(ss->pbvh, location, max);
interp_v3_v3v3(location, location, max, 0.5f);
mul_m4_v3(ob->object_to_world, location);
copy_v3_v3(ups->average_stroke_accum, location);
ups->average_stroke_counter = 1;
/* Update last stroke position. */
ups->last_stroke_valid = true;
}
}
void ED_object_sculptmode_enter_ex(Main *bmain,
Depsgraph *depsgraph,
Scene *scene,
Object *ob,
const bool force_dyntopo,
ReportList *reports)
{
const int mode_flag = OB_MODE_SCULPT;
Mesh *me = BKE_mesh_from_object(ob);
/* Enter sculpt mode. */
ob->mode |= mode_flag;
sculpt_init_session(bmain, depsgraph, scene, ob);
if (!(fabsf(ob->scale[0] - ob->scale[1]) < 1e-4f && fabsf(ob->scale[1] - ob->scale[2]) < 1e-4f))
{
BKE_report(
reports, RPT_WARNING, "Object has non-uniform scale, sculpting may be unpredictable");
}
else if (is_negative_m4(ob->object_to_world)) {
BKE_report(reports, RPT_WARNING, "Object has negative scale, sculpting may be unpredictable");
}
Paint *paint = BKE_paint_get_active_from_paintmode(scene, PAINT_MODE_SCULPT);
BKE_paint_init(bmain, scene, PAINT_MODE_SCULPT, PAINT_CURSOR_SCULPT);
ED_paint_cursor_start(paint, SCULPT_mode_poll_view3d);
/* Check dynamic-topology flag; re-enter dynamic-topology mode when changing modes,
* As long as no data was added that is not supported. */
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
const char *message_unsupported = nullptr;
if (me->totloop != me->faces_num * 3) {
message_unsupported = TIP_("non-triangle face");
}
else if (mmd != nullptr) {
message_unsupported = TIP_("multi-res modifier");
}
else {
enum eDynTopoWarnFlag flag = SCULPT_dynamic_topology_check(scene, ob);
if (flag == 0) {
/* pass */
}
else if (flag & DYNTOPO_WARN_VDATA) {
message_unsupported = TIP_("vertex data");
}
else if (flag & DYNTOPO_WARN_EDATA) {
message_unsupported = TIP_("edge data");
}
else if (flag & DYNTOPO_WARN_LDATA) {
message_unsupported = TIP_("face data");
}
else if (flag & DYNTOPO_WARN_MODIFIER) {
message_unsupported = TIP_("constructive modifier");
}
else {
BLI_assert(0);
}
}
if ((message_unsupported == nullptr) || force_dyntopo) {
/* Needed because we may be entering this mode before the undo system loads. */
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
bool has_undo = wm->undo_stack != nullptr;
/* Undo push is needed to prevent memory leak. */
if (has_undo) {
SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable");
}
2023-07-29 05:47:53 +02:00
SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob);
if (has_undo) {
SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN);
SCULPT_undo_push_end(ob);
}
}
else {
BKE_reportf(
reports, RPT_WARNING, "Dynamic Topology found: %s, disabled", message_unsupported);
me->flag &= ~ME_SCULPT_DYNAMIC_TOPOLOGY;
}
}
SCULPT_ensure_valid_pivot(ob, scene);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
void ED_object_sculptmode_enter(bContext *C, Depsgraph *depsgraph, ReportList *reports)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, reports);
}
void ED_object_sculptmode_exit_ex(Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob)
{
const int mode_flag = OB_MODE_SCULPT;
Mesh *me = BKE_mesh_from_object(ob);
multires_flush_sculpt_updates(ob);
/* Not needed for now. */
#if 0
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
const int flush_recalc = ed_object_sculptmode_flush_recalc_flag(scene, ob, mmd);
#endif
/* Always for now, so leaving sculpt mode always ensures scene is in
* a consistent state. */
if (true || /* flush_recalc || */ (ob->sculpt && ob->sculpt->bm)) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
if (me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) {
/* Dynamic topology must be disabled before exiting sculpt
* mode to ensure the undo stack stays in a consistent
* state. */
sculpt_dynamic_topology_disable_with_undo(bmain, depsgraph, scene, ob);
/* Store so we know to re-enable when entering sculpt mode. */
me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY;
}
/* Leave sculpt mode. */
ob->mode &= ~mode_flag;
BKE_sculptsession_free(ob);
paint_cursor_delete_textures();
/* Never leave derived meshes behind. */
BKE_object_free_derived_caches(ob);
/* Flush object mode. */
DEG_id_tag_update(&ob->id, ID_RECALC_COPY_ON_WRITE);
}
void ED_object_sculptmode_exit(bContext *C, Depsgraph *depsgraph)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
}
static int sculpt_mode_toggle_exec(bContext *C, wmOperator *op)
{
wmMsgBus *mbus = CTX_wm_message_bus(C);
Main *bmain = CTX_data_main(C);
Depsgraph *depsgraph = CTX_data_depsgraph_on_load(C);
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = scene->toolsettings;
ViewLayer *view_layer = CTX_data_view_layer(C);
ViewLayer: Lazy sync of scene data. When a change happens which invalidates view layers the syncing will be postponed until the first usage. This will improve importing or adding many objects in a single operation/script. `BKE_view_layer_need_resync_tag` is used to tag the view layer to be out of sync. Before accessing `BKE_view_layer_active_base_get`, `BKE_view_layer_active_object_get`, `BKE_view_layer_active_collection` or `BKE_view_layer_object_bases` the caller should call `BKE_view_layer_synced_ensure`. Having two functions ensures that partial syncing could be added as smaller patches in the future. Tagging a view layer out of sync could be replaced with a partial sync. Eventually the number of full resyncs could be reduced. After all tagging has been replaced with partial syncs the ensure_sync could be phased out. This patch has been added to discuss the details and consequences of the current approach. For clarity the call to BKE_view_layer_ensure_sync is placed close to the getters. In the future this could be placed in more strategical places to reduce the number of calls or improve performance. Finding those strategical places isn't that clear. When multiple operations are grouped in a single script you might want to always check for resync. Some areas found that can be improved. This list isn't complete. These areas aren't addressed by this patch as these changes would be hard to detect to the reviewer. The idea is to add changes to these areas as a separate patch. It might be that the initial commit would reduce performance compared to master, but will be fixed by the additional patches. **Object duplication** During object duplication the syncing is temporarily disabled. With this patch this isn't useful as when disabled the view_layer is accessed to locate bases. This can be improved by first locating the source bases, then duplicate and sync and locate the new bases. Will be solved in a separate patch for clarity reasons ({D15886}). **Object add** `BKE_object_add` not only adds a new object, but also selects and activates the new base. This requires the view_layer to be resynced. Some callers reverse the selection and activation (See `get_new_constraint_target`). We should make the selection and activation optional. This would make it possible to add multiple objects without having to resync per object. **Postpone Activate Base** Setting the basact is done in many locations. They follow a rule as after an action find the base and set the basact. Finding the base could require a resync. The idea is to store in the view_layer the object which base will be set in the basact during the next sync, reducing the times resyncing needs to happen. Reviewed By: mont29 Maniphest Tasks: T73411 Differential Revision: https://developer.blender.org/D15885
2022-09-14 21:33:51 +02:00
BKE_view_layer_synced_ensure(scene, view_layer);
Object *ob = BKE_view_layer_active_object_get(view_layer);
const int mode_flag = OB_MODE_SCULPT;
const bool is_mode_set = (ob->mode & mode_flag) != 0;
if (!is_mode_set) {
if (!ED_object_mode_compat_set(C, ob, eObjectMode(mode_flag), op->reports)) {
return OPERATOR_CANCELLED;
}
}
if (is_mode_set) {
ED_object_sculptmode_exit_ex(bmain, depsgraph, scene, ob);
}
else {
if (depsgraph) {
depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
}
ED_object_sculptmode_enter_ex(bmain, depsgraph, scene, ob, false, op->reports);
BKE_paint_toolslots_brush_validate(bmain, &ts->sculpt->paint);
if (ob->mode & mode_flag) {
Mesh *me = static_cast<Mesh *>(ob->data);
/* Dyntopo adds its own undo step. */
if ((me->flag & ME_SCULPT_DYNAMIC_TOPOLOGY) == 0) {
/* Without this the memfile undo step is used,
* while it works it causes lag when undoing the first undo step, see #71564. */
wmWindowManager *wm = CTX_wm_manager(C);
if (wm->op_undo_depth <= 1) {
SCULPT_undo_push_begin(ob, op);
SCULPT_undo_push_end(ob);
}
}
}
}
WM_event_add_notifier(C, NC_SCENE | ND_MODE, scene);
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
WM_toolsystem_update_from_context_view3d(C);
return OPERATOR_FINISHED;
}
static void SCULPT_OT_sculptmode_toggle(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Sculpt Mode";
ot->idname = "SCULPT_OT_sculptmode_toggle";
ot->description = "Toggle sculpt mode in 3D view";
/* API callbacks. */
ot->exec = sculpt_mode_toggle_exec;
ot->poll = ED_operator_object_active_editable_mesh;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
void SCULPT_geometry_preview_lines_update(bContext *C, SculptSession *ss, float radius)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
ss->preview_vert_count = 0;
int totpoints = 0;
/* This function is called from the cursor drawing code, so the PBVH may not be build yet. */
if (!ss->pbvh) {
return;
}
if (!ss->deform_modifiers_active) {
return;
}
if (BKE_pbvh_type(ss->pbvh) == PBVH_GRIDS) {
return;
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
float brush_co[3];
copy_v3_v3(brush_co, SCULPT_active_vertex_co_get(ss));
BLI_bitmap *visited_verts = BLI_BITMAP_NEW(SCULPT_vertex_count_get(ss), "visited_verts");
/* Assuming an average of 6 edges per vertex in a triangulated mesh. */
const int max_preview_verts = SCULPT_vertex_count_get(ss) * 3 * 2;
if (ss->preview_vert_list == nullptr) {
ss->preview_vert_list = MEM_cnew_array<PBVHVertRef>(max_preview_verts, __func__);
}
GSQueue *non_visited_verts = BLI_gsqueue_new(sizeof(PBVHVertRef));
PBVHVertRef active_v = SCULPT_active_vertex_get(ss);
BLI_gsqueue_push(non_visited_verts, &active_v);
while (!BLI_gsqueue_is_empty(non_visited_verts)) {
PBVHVertRef from_v;
BLI_gsqueue_pop(non_visited_verts, &from_v);
SculptVertexNeighborIter ni;
SCULPT_VERTEX_NEIGHBORS_ITER_BEGIN (ss, from_v, ni) {
if (totpoints + (ni.size * 2) < max_preview_verts) {
PBVHVertRef to_v = ni.vertex;
int to_v_i = ni.index;
ss->preview_vert_list[totpoints] = from_v;
totpoints++;
ss->preview_vert_list[totpoints] = to_v;
totpoints++;
if (BLI_BITMAP_TEST(visited_verts, to_v_i)) {
continue;
}
BLI_BITMAP_ENABLE(visited_verts, to_v_i);
const float *co = SCULPT_vertex_co_for_grab_active_get(ss, to_v);
if (len_squared_v3v3(brush_co, co) < radius * radius) {
BLI_gsqueue_push(non_visited_verts, &to_v);
}
}
}
SCULPT_VERTEX_NEIGHBORS_ITER_END(ni);
}
BLI_gsqueue_free(non_visited_verts);
MEM_freeN(visited_verts);
ss->preview_vert_count = totpoints;
}
static int sculpt_sample_color_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Brush *brush = BKE_paint_brush(&sd->paint);
SculptSession *ss = ob->sculpt;
PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss);
float active_vertex_color[4];
if (!SCULPT_handles_colors_report(ss, op->reports)) {
return OPERATOR_CANCELLED;
}
BKE_sculpt_update_object_for_edit(CTX_data_depsgraph_pointer(C), ob, true, false, false);
/* No color attribute? Set color to white. */
if (!SCULPT_has_colors(ss)) {
copy_v4_fl(active_vertex_color, 1.0f);
}
else {
SCULPT_vertex_color_get(ss, active_vertex, active_vertex_color);
}
float color_srgb[3];
IMB_colormanagement_scene_linear_to_srgb_v3(color_srgb, active_vertex_color);
BKE_brush_color_set(scene, brush, color_srgb);
WM_event_add_notifier(C, NC_BRUSH | NA_EDITED, brush);
return OPERATOR_FINISHED;
}
static void SCULPT_OT_sample_color(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Sample Color";
ot->idname = "SCULPT_OT_sample_color";
ot->description = "Sample the vertex color of the active vertex";
/* api callbacks */
ot->invoke = sculpt_sample_color_invoke;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_DEPENDS_ON_CURSOR;
}
/**
* #sculpt_mask_by_color_delta_get returns values in the (0,1) range that are used to generate the
* mask based on the difference between two colors (the active color and the color of any other
* vertex). Ideally, a threshold of 0 should mask only the colors that are equal to the active
* color and threshold of 1 should mask all colors. In order to avoid artifacts and produce softer
* falloffs in the mask, the MASK_BY_COLOR_SLOPE defines the size of the transition values between
* masked and unmasked vertices. The smaller this value is, the sharper the generated mask is going
* to be.
*/
#define MASK_BY_COLOR_SLOPE 0.25f
static float sculpt_mask_by_color_delta_get(const float *color_a,
const float *color_b,
const float threshold,
const bool invert)
{
float len = len_v3v3(color_a, color_b);
/* Normalize len to the (0, 1) range. */
len = len / M_SQRT3;
if (len < threshold - MASK_BY_COLOR_SLOPE) {
len = 1.0f;
}
else if (len >= threshold) {
len = 0.0f;
}
else {
len = (-len + threshold) / MASK_BY_COLOR_SLOPE;
}
if (invert) {
return 1.0f - len;
}
return len;
}
static float sculpt_mask_by_color_final_mask_get(const float current_mask,
const float new_mask,
const bool invert,
const bool preserve_mask)
{
if (preserve_mask) {
if (invert) {
return min_ff(current_mask, new_mask);
}
return max_ff(current_mask, new_mask);
}
return new_mask;
}
struct MaskByColorContiguousFloodFillData {
float threshold;
bool invert;
float *new_mask;
float initial_color[3];
};
static void do_mask_by_color_contiguous_update_node(Object *ob,
const float *mask_by_color_floodfill,
const bool invert,
const bool preserve_mask,
const SculptMaskWriteInfo mask_write,
PBVHNode *node)
{
SculptSession *ss = ob->sculpt;
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_MASK);
bool update_node = false;
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
const float current_mask = vd.mask;
const float new_mask = mask_by_color_floodfill[vd.index];
const float mask = sculpt_mask_by_color_final_mask_get(
current_mask, new_mask, invert, preserve_mask);
if (current_mask == mask) {
continue;
}
SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, mask, vd);
update_node = true;
}
BKE_pbvh_vertex_iter_end;
if (update_node) {
BKE_pbvh_node_mark_update_mask(node);
}
}
static bool sculpt_mask_by_color_contiguous_floodfill(
SculptSession *ss, PBVHVertRef from_v, PBVHVertRef to_v, bool is_duplicate, void *userdata)
{
int from_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, from_v);
int to_v_i = BKE_pbvh_vertex_to_index(ss->pbvh, to_v);
MaskByColorContiguousFloodFillData *data = static_cast<MaskByColorContiguousFloodFillData *>(
userdata);
float current_color[4];
SCULPT_vertex_color_get(ss, to_v, current_color);
float new_vertex_mask = sculpt_mask_by_color_delta_get(
current_color, data->initial_color, data->threshold, data->invert);
data->new_mask[to_v_i] = new_vertex_mask;
if (is_duplicate) {
data->new_mask[to_v_i] = data->new_mask[from_v_i];
}
float len = len_v3v3(current_color, data->initial_color);
len = len / M_SQRT3;
return len <= data->threshold;
}
static void sculpt_mask_by_color_contiguous(Object *object,
const PBVHVertRef vertex,
const float threshold,
const bool invert,
const bool preserve_mask)
{
using namespace blender;
SculptSession *ss = object->sculpt;
const int totvert = SCULPT_vertex_count_get(ss);
float *new_mask = MEM_cnew_array<float>(totvert, __func__);
if (invert) {
for (int i = 0; i < totvert; i++) {
new_mask[i] = 1.0f;
}
}
SculptFloodFill flood;
SCULPT_floodfill_init(ss, &flood);
SCULPT_floodfill_add_initial(&flood, vertex);
MaskByColorContiguousFloodFillData ffd;
ffd.threshold = threshold;
ffd.invert = invert;
ffd.new_mask = new_mask;
float color[4];
SCULPT_vertex_color_get(ss, vertex, color);
copy_v3_v3(ffd.initial_color, color);
SCULPT_floodfill_execute(ss, &flood, sculpt_mask_by_color_contiguous_floodfill, &ffd);
SCULPT_floodfill_free(&flood);
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(ss->pbvh, {});
const SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ss);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
do_mask_by_color_contiguous_update_node(
object, new_mask, invert, preserve_mask, mask_write, nodes[i]);
}
});
MEM_freeN(new_mask);
}
static void do_mask_by_color_task(Object *ob,
const float threshold,
const bool invert,
const bool preserve_mask,
const PBVHVertRef mask_by_color_vertex,
const SculptMaskWriteInfo mask_write,
PBVHNode *node)
{
SculptSession *ss = ob->sculpt;
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_MASK);
bool update_node = false;
float active_color[4];
SCULPT_vertex_color_get(ss, mask_by_color_vertex, active_color);
PBVHVertexIter vd;
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
float col[4];
SCULPT_vertex_color_get(ss, vd.vertex, col);
const float current_mask = vd.mask;
const float new_mask = sculpt_mask_by_color_delta_get(active_color, col, threshold, invert);
const float mask = sculpt_mask_by_color_final_mask_get(
current_mask, new_mask, invert, preserve_mask);
if (current_mask == mask) {
continue;
}
SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, mask, vd);
update_node = true;
}
BKE_pbvh_vertex_iter_end;
if (update_node) {
BKE_pbvh_node_mark_update_mask(node);
}
}
static void sculpt_mask_by_color_full_mesh(Object *object,
const PBVHVertRef vertex,
const float threshold,
const bool invert,
const bool preserve_mask)
{
using namespace blender;
SculptSession *ss = object->sculpt;
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(ss->pbvh, {});
const SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ss);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
do_mask_by_color_task(
object, threshold, invert, preserve_mask, vertex, mask_write, nodes[i]);
}
});
}
static int sculpt_mask_by_color_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
{
View3D *v3d = CTX_wm_view3d(C);
if (v3d && v3d->shading.type == OB_SOLID) {
v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR;
}
}
2022-04-22 00:16:37 +02:00
/* Color data is not available in multi-resolution or dynamic topology. */
if (!SCULPT_handles_colors_report(ss, op->reports)) {
return OPERATOR_CANCELLED;
}
MultiresModifierData *mmd = BKE_sculpt_multires_active(CTX_data_scene(C), ob);
BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, mmd);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_vertex_random_access_ensure(ss);
/* Tools that are not brushes do not have the brush gizmo to update the vertex as the mouse move,
* so it needs to be updated here. */
SculptCursorGeometryInfo sgi;
const float mval_fl[2] = {float(event->mval[0]), float(event->mval[1])};
SCULPT_cursor_geometry_info_update(C, &sgi, mval_fl, false);
SCULPT_undo_push_begin(ob, op);
BKE_sculpt_color_layer_create_if_needed(ob);
const PBVHVertRef active_vertex = SCULPT_active_vertex_get(ss);
const float threshold = RNA_float_get(op->ptr, "threshold");
const bool invert = RNA_boolean_get(op->ptr, "invert");
const bool preserve_mask = RNA_boolean_get(op->ptr, "preserve_previous_mask");
if (SCULPT_has_loop_colors(ob)) {
BKE_pbvh_ensure_node_loops(ss->pbvh);
}
if (RNA_boolean_get(op->ptr, "contiguous")) {
sculpt_mask_by_color_contiguous(ob, active_vertex, threshold, invert, preserve_mask);
}
else {
sculpt_mask_by_color_full_mesh(ob, active_vertex, threshold, invert, preserve_mask);
}
BKE_pbvh_update_mask(ss->pbvh);
SCULPT_undo_push_end(ob);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
return OPERATOR_FINISHED;
}
static void SCULPT_OT_mask_by_color(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Mask by Color";
ot->idname = "SCULPT_OT_mask_by_color";
ot->description = "Creates a mask based on the active color attribute";
/* api callbacks */
ot->invoke = sculpt_mask_by_color_invoke;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER;
ot->prop = RNA_def_boolean(
ot->srna, "contiguous", false, "Contiguous", "Mask only contiguous color areas");
ot->prop = RNA_def_boolean(ot->srna, "invert", false, "Invert", "Invert the generated mask");
ot->prop = RNA_def_boolean(
ot->srna,
"preserve_previous_mask",
false,
"Preserve Previous Mask",
"Preserve the previous mask and add or subtract the new one generated by the colors");
RNA_def_float(ot->srna,
"threshold",
0.35f,
0.0f,
1.0f,
"Threshold",
"How much changes in color affect the mask generation",
0.0f,
1.0f);
}
enum CavityBakeMixMode {
AUTOMASK_BAKE_MIX,
AUTOMASK_BAKE_MULTIPLY,
AUTOMASK_BAKE_DIVIDE,
AUTOMASK_BAKE_ADD,
AUTOMASK_BAKE_SUBTRACT,
};
enum CavityBakeSettingsSource {
AUTOMASK_SETTINGS_OPERATOR,
AUTOMASK_SETTINGS_SCENE,
AUTOMASK_SETTINGS_BRUSH
};
static void sculpt_bake_cavity_exec_task(Object *ob,
AutomaskingCache *automasking,
const CavityBakeMixMode mode,
const float factor,
const SculptMaskWriteInfo mask_write,
PBVHNode *node)
{
SculptSession *ss = ob->sculpt;
PBVHVertexIter vd;
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_MASK);
AutomaskingNodeData automask_data;
SCULPT_automasking_node_begin(ob, automasking, &automask_data, node);
BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_UNIQUE) {
SCULPT_automasking_node_update(&automask_data, &vd);
float automask = SCULPT_automasking_factor_get(automasking, ss, vd.vertex, &automask_data);
float mask;
switch (mode) {
case AUTOMASK_BAKE_MIX:
mask = automask;
break;
case AUTOMASK_BAKE_MULTIPLY:
mask = vd.mask * automask;
break;
break;
case AUTOMASK_BAKE_DIVIDE:
mask = automask > 0.00001f ? vd.mask / automask : 0.0f;
break;
break;
case AUTOMASK_BAKE_ADD:
mask = vd.mask + automask;
break;
case AUTOMASK_BAKE_SUBTRACT:
mask = vd.mask - automask;
break;
}
mask = vd.mask + (mask - vd.mask) * factor;
CLAMP(mask, 0.0f, 1.0f);
SCULPT_mask_vert_set(BKE_pbvh_type(ss->pbvh), mask_write, mask, vd);
}
BKE_pbvh_vertex_iter_end;
BKE_pbvh_node_mark_update_mask(node);
}
static int sculpt_bake_cavity_exec(bContext *C, wmOperator *op)
{
using namespace blender;
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
Object *ob = CTX_data_active_object(C);
SculptSession *ss = ob->sculpt;
Sculpt *sd = CTX_data_tool_settings(C)->sculpt;
const Brush *brush = BKE_paint_brush(&sd->paint);
MultiresModifierData *mmd = BKE_sculpt_multires_active(CTX_data_scene(C), ob);
BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, mmd);
BKE_sculpt_update_object_for_edit(depsgraph, ob, true, true, false);
SCULPT_vertex_random_access_ensure(ss);
SCULPT_undo_push_begin(ob, op);
CavityBakeMixMode mode = CavityBakeMixMode(RNA_enum_get(op->ptr, "mix_mode"));
float factor = RNA_float_get(op->ptr, "mix_factor");
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(ss->pbvh, {});
/* Set up automasking settings. */
Sculpt sd2 = *sd;
CavityBakeSettingsSource src = (CavityBakeSettingsSource)RNA_enum_get(op->ptr,
"settings_source");
switch (src) {
case AUTOMASK_SETTINGS_OPERATOR:
if (RNA_boolean_get(op->ptr, "invert")) {
sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_INVERTED;
}
else {
sd2.automasking_flags = BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
if (RNA_boolean_get(op->ptr, "use_curve")) {
sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_USE_CURVE;
}
sd2.automasking_cavity_blur_steps = RNA_int_get(op->ptr, "blur_steps");
sd2.automasking_cavity_factor = RNA_float_get(op->ptr, "factor");
sd2.automasking_cavity_curve = sd->automasking_cavity_curve_op;
break;
case AUTOMASK_SETTINGS_BRUSH:
if (brush) {
sd2.automasking_flags = brush->automasking_flags;
sd2.automasking_cavity_factor = brush->automasking_cavity_factor;
sd2.automasking_cavity_curve = brush->automasking_cavity_curve;
sd2.automasking_cavity_blur_steps = brush->automasking_cavity_blur_steps;
/* Ensure only cavity masking is enabled. */
sd2.automasking_flags &= BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE;
}
else {
sd2.automasking_flags = 0;
BKE_report(op->reports, RPT_WARNING, "No active brush");
return OPERATOR_CANCELLED;
}
break;
case AUTOMASK_SETTINGS_SCENE:
/* Ensure only cavity masking is enabled. */
sd2.automasking_flags &= BRUSH_AUTOMASKING_CAVITY_ALL | BRUSH_AUTOMASKING_CAVITY_USE_CURVE;
break;
}
/* Ensure cavity mask is actually enabled. */
if (!(sd2.automasking_flags & BRUSH_AUTOMASKING_CAVITY_ALL)) {
sd2.automasking_flags |= BRUSH_AUTOMASKING_CAVITY_NORMAL;
}
/* Create copy of brush with cleared automasking settings. */
Brush brush2 = blender::dna::shallow_copy(*brush);
brush2.automasking_flags = 0;
brush2.automasking_boundary_edges_propagation_steps = 1;
brush2.automasking_cavity_curve = sd2.automasking_cavity_curve;
SCULPT_stroke_id_next(ob);
AutomaskingCache *automasking = SCULPT_automasking_cache_init(&sd2, &brush2, ob);
const SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ss);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
sculpt_bake_cavity_exec_task(ob, automasking, mode, factor, mask_write, nodes[i]);
}
});
SCULPT_automasking_cache_free(automasking);
BKE_pbvh_update_mask(ss->pbvh);
SCULPT_undo_push_end(ob);
SCULPT_flush_update_done(C, ob, SCULPT_UPDATE_MASK);
SCULPT_tag_update_overlays(C);
return OPERATOR_FINISHED;
}
static void cavity_bake_ui(bContext *C, wmOperator *op)
{
uiLayout *layout = op->layout;
Scene *scene = CTX_data_scene(C);
Sculpt *sd = scene->toolsettings ? scene->toolsettings->sculpt : nullptr;
uiLayoutSetPropSep(layout, true);
uiLayoutSetPropDecorate(layout, false);
CavityBakeSettingsSource source = (CavityBakeSettingsSource)RNA_enum_get(op->ptr,
"settings_source");
if (!sd) {
source = AUTOMASK_SETTINGS_OPERATOR;
}
switch (source) {
case AUTOMASK_SETTINGS_OPERATOR: {
uiItemR(layout, op->ptr, "mix_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "mix_factor", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "settings_source", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "factor", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "blur_steps", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "invert", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "use_curve", UI_ITEM_NONE, nullptr, ICON_NONE);
if (sd && RNA_boolean_get(op->ptr, "use_curve")) {
PointerRNA sculpt_ptr = RNA_pointer_create(&scene->id, &RNA_Sculpt, sd);
uiTemplateCurveMapping(
layout, &sculpt_ptr, "automasking_cavity_curve_op", 'v', false, false, false, false);
}
break;
}
case AUTOMASK_SETTINGS_BRUSH:
case AUTOMASK_SETTINGS_SCENE:
uiItemR(layout, op->ptr, "mix_mode", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "mix_factor", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(layout, op->ptr, "settings_source", UI_ITEM_NONE, nullptr, ICON_NONE);
break;
}
}
static void SCULPT_OT_mask_from_cavity(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Mask From Cavity";
ot->idname = "SCULPT_OT_mask_from_cavity";
ot->description = "Creates a mask based on the curvature of the surface";
ot->ui = cavity_bake_ui;
static EnumPropertyItem mix_modes[] = {
{AUTOMASK_BAKE_MIX, "MIX", ICON_NONE, "Mix", ""},
{AUTOMASK_BAKE_MULTIPLY, "MULTIPLY", ICON_NONE, "Multiply", ""},
{AUTOMASK_BAKE_DIVIDE, "DIVIDE", ICON_NONE, "Divide", ""},
{AUTOMASK_BAKE_ADD, "ADD", ICON_NONE, "Add", ""},
{AUTOMASK_BAKE_SUBTRACT, "SUBTRACT", ICON_NONE, "Subtract", ""},
{0, nullptr, 0, nullptr, nullptr},
};
/* api callbacks */
ot->exec = sculpt_bake_cavity_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_enum(ot->srna, "mix_mode", mix_modes, AUTOMASK_BAKE_MIX, "Mode", "Mix mode");
RNA_def_float(ot->srna, "mix_factor", 1.0f, 0.0f, 5.0f, "Mix Factor", "", 0.0f, 1.0f);
static EnumPropertyItem settings_sources[] = {
{AUTOMASK_SETTINGS_OPERATOR,
"OPERATOR",
ICON_NONE,
"Operator",
"Use settings from operator properties"},
{AUTOMASK_SETTINGS_BRUSH, "BRUSH", ICON_NONE, "Brush", "Use settings from brush"},
{AUTOMASK_SETTINGS_SCENE, "SCENE", ICON_NONE, "Scene", "Use settings from scene"},
{0, nullptr, 0, nullptr, nullptr}};
RNA_def_enum(ot->srna,
"settings_source",
settings_sources,
AUTOMASK_SETTINGS_OPERATOR,
"Settings",
"Use settings from here");
2022-09-29 01:46:46 +02:00
RNA_def_float(ot->srna,
"factor",
0.5f,
0.0f,
5.0f,
"Factor",
2022-09-29 01:46:46 +02:00
"The contrast of the cavity mask",
0.0f,
1.0f);
RNA_def_int(ot->srna,
"blur_steps",
2,
0,
25,
"Blur",
2022-09-29 01:46:46 +02:00
"The number of times the cavity mask is blurred",
0,
25);
RNA_def_boolean(ot->srna, "use_curve", false, "Custom Curve", "");
RNA_def_boolean(ot->srna, "invert", false, "Cavity (Inverted)", "");
}
static int sculpt_reveal_all_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);
if (!ss->pbvh) {
return OPERATOR_CANCELLED;
}
bool with_bmesh = BKE_pbvh_type(ss->pbvh) == PBVH_BMESH;
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(ss->pbvh, {});
if (nodes.is_empty()) {
return OPERATOR_CANCELLED;
}
/* Propagate face hide state to verts for undo. */
SCULPT_visibility_sync_all_from_faces(ob);
SCULPT_undo_push_begin(ob, op);
for (PBVHNode *node : nodes) {
BKE_pbvh_node_mark_update_visibility(node);
if (!with_bmesh) {
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_HIDDEN);
}
}
SCULPT_topology_islands_invalidate(ss);
if (!with_bmesh) {
/* As an optimization, free the hide attribute when making all geometry visible. This allows
* reduced memory usage without manually clearing it later, and allows sculpt operations to
* avoid checking element's hide status. */
CustomData_free_layer_named(&mesh->face_data, ".hide_poly", mesh->faces_num);
ss->hide_poly = nullptr;
}
else {
SCULPT_undo_push_node(ob, nodes[0], SCULPT_UNDO_HIDDEN);
BMIter iter;
BMFace *f;
BMVert *v;
const int cd_mask = CustomData_get_offset_named(&ss->bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) {
BM_log_vert_before_modified(ss->bm_log, v, cd_mask);
}
BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) {
BM_log_face_modified(ss->bm_log, f);
}
SCULPT_face_visibility_all_set(ss, true);
}
SCULPT_visibility_sync_all_from_faces(ob);
2022-10-10 02:21:53 +02:00
/* NOTE: #SCULPT_visibility_sync_all_from_faces may have deleted
* `pbvh->hide_vert` if hide_poly did not exist, which is why
* we call #BKE_pbvh_update_hide_attributes_from_mesh here instead of
* after #CustomData_free_layer_named above. */
if (!with_bmesh) {
BKE_pbvh_update_hide_attributes_from_mesh(ss->pbvh);
}
BKE_pbvh_update_visibility(ss->pbvh);
SCULPT_undo_push_end(ob);
SCULPT_tag_update_overlays(C);
DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
ED_region_tag_redraw(CTX_wm_region(C));
return OPERATOR_FINISHED;
}
2022-10-04 04:54:01 +02:00
static void SCULPT_OT_reveal_all(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Reveal All";
ot->idname = "SCULPT_OT_reveal_all";
ot->description = "Unhide all geometry";
/* Api callbacks. */
ot->exec = sculpt_reveal_all_exec;
ot->poll = SCULPT_mode_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
void ED_operatortypes_sculpt()
{
WM_operatortype_append(SCULPT_OT_brush_stroke);
WM_operatortype_append(SCULPT_OT_sculptmode_toggle);
WM_operatortype_append(SCULPT_OT_set_persistent_base);
WM_operatortype_append(SCULPT_OT_dynamic_topology_toggle);
WM_operatortype_append(SCULPT_OT_optimize);
WM_operatortype_append(SCULPT_OT_symmetrize);
WM_operatortype_append(SCULPT_OT_detail_flood_fill);
WM_operatortype_append(SCULPT_OT_sample_detail_size);
WM_operatortype_append(SCULPT_OT_set_detail_size);
WM_operatortype_append(SCULPT_OT_mesh_filter);
WM_operatortype_append(SCULPT_OT_mask_filter);
WM_operatortype_append(SCULPT_OT_set_pivot_position);
WM_operatortype_append(SCULPT_OT_face_sets_create);
WM_operatortype_append(SCULPT_OT_face_set_change_visibility);
WM_operatortype_append(SCULPT_OT_face_sets_invert_visibility);
WM_operatortype_append(SCULPT_OT_face_sets_randomize_colors);
WM_operatortype_append(SCULPT_OT_face_sets_init);
WM_operatortype_append(SCULPT_OT_cloth_filter);
WM_operatortype_append(SCULPT_OT_face_sets_edit);
WM_operatortype_append(SCULPT_OT_face_set_lasso_gesture);
WM_operatortype_append(SCULPT_OT_face_set_box_gesture);
WM_operatortype_append(SCULPT_OT_trim_box_gesture);
WM_operatortype_append(SCULPT_OT_trim_lasso_gesture);
WM_operatortype_append(SCULPT_OT_project_line_gesture);
WM_operatortype_append(SCULPT_OT_sample_color);
WM_operatortype_append(SCULPT_OT_color_filter);
WM_operatortype_append(SCULPT_OT_mask_by_color);
WM_operatortype_append(SCULPT_OT_dyntopo_detail_size_edit);
WM_operatortype_append(SCULPT_OT_mask_init);
WM_operatortype_append(SCULPT_OT_expand);
WM_operatortype_append(SCULPT_OT_mask_from_cavity);
WM_operatortype_append(SCULPT_OT_reveal_all);
}
void ED_keymap_sculpt(wmKeyConfig *keyconf)
{
filter_mesh_modal_keymap(keyconf);
}