Sculpt-dev: New normal automasking modes

Two new automasking modes: "Brush Normal"
and "View Normal."

Brush Normal compares vertex normals to
the initial sculpt normal, while View
Normal of course compares with the
view vector.

Each of these modes have an angular limit
and a falloff.  There's also an "original
normal" option, which needs a better name;
"original normal" is actually already taken,
but "automasking original normal" is a lot of
characters.  Not sure what to do here.
This commit is contained in:
Joseph Eagar 2022-01-07 12:03:40 -08:00
parent 7e1c56d909
commit c8aedd75d7
13 changed files with 374 additions and 19 deletions

View File

@ -2105,11 +2105,51 @@ def brush_settings_advanced(layout, context, brush, popover=False):
context,
brush,
"automasking_boundary_edges_propagation_steps")
UnifiedPaintPanel.channel_unified(layout.column(),
flags = UnifiedPaintPanel.get_channel_value(context, brush, "automasking")
if "CONCAVITY" in flags:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"concave_mask_factor",
slider=True)
enable_orig_normal = False
if "BRUSH_NORMAL" in flags:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"normal_mask_limit",
slider=True)
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"normal_mask_falloff",
slider=True)
enable_orig_normal = True
if "VIEW_NORMAL" in flags:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"view_normal_mask_limit",
slider=True)
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"view_normal_mask_falloff",
slider=True)
enable_orig_normal = True
sub = layout.row()
sub.enabled = enable_orig_normdal
UnifiedPaintPanel.channel_unified(sub,
context,
brush,
"concave_mask_factor",
slider=True)
"automasking_use_original_normal")
"""
col = layout.column(heading="Auto-Masking", align=True)

View File

@ -120,6 +120,8 @@ ToolDef = namedtuple(
"draw_cursor",
# Various options, see: `bpy.types.WorkSpaceTool.setup` options argument.
"options",
#get_enabled(ctx, idname) whether tool should be grayed out
"get_enabled"
)
)
del namedtuple
@ -143,6 +145,7 @@ def from_dict(kw_args):
"operator": None,
"draw_settings": None,
"draw_cursor": None,
"get_enabled": None
}
kw.update(kw_args)
@ -725,9 +728,14 @@ class ToolSelectPanelHelper:
icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(item.icon)
sub = ui_gen.send(False)
sub2 = sub
if item.get_enabled is not None:
sub2 = sub.row(align=False)
sub2.enabled = item.get_enabled(context, item.idname)
if use_menu:
sub.operator_menu_hold(
sub2.operator_menu_hold(
"wm.tool_set_by_id",
text=item.label if show_text else "",
depress=is_active,
@ -735,7 +743,7 @@ class ToolSelectPanelHelper:
icon_value=icon_value,
).name = item.idname
else:
sub.operator(
sub2.operator(
"wm.tool_set_by_id",
text=item.label if show_text else "",
depress=is_active,

View File

@ -1211,6 +1211,83 @@ class _defs_particle:
attr="tool",)
def generate_from_enum_ex(_context, *,
idname_prefix,
icon_prefix,
type,
attr,
cursor='DEFAULT',
tooldef_keywords={},
exclude_filter={},
combine_map={}):
"""
combine_map combines items, takes the form of a dict:
combine_map = {
"PARENT KEY" : (
"CHILD KEY"
)
}
"""
combinekeys = {}
parentmap = {}
for k, v in combine_map.items():
combinekeys[k] = []
for child in v:
parentmap[child] = k
tool_defs = []
#build combine key owners first
for enum in type.bl_rna.properties[attr].enum_items_static:
name = enum.name
idname = enum.identifier
if idname in exclude_filter:
continue
if idname in combinekeys:
combinekeys[idname].append(ToolDef.from_dict(dict(idname=idname_prefix + name,
label=name,
icon=icon_prefix + idname.lower(),
cursor=cursor,
data_block=idname,
**tooldef_keywords,)))
for enum in type.bl_rna.properties[attr].enum_items_static:
name = enum.name
idname = enum.identifier
if idname in exclude_filter:
continue
if idname in combinekeys:
tool_defs.append(combinekeys[idname])
elif idname in parentmap:
parentkey = parentmap[idname]
combinekeys[parentkey].append(ToolDef.from_dict(dict(idname=idname_prefix + name,
label=name,
icon=icon_prefix + idname.lower(),
cursor=cursor,
data_block=idname,
**tooldef_keywords,)))
else:
tool_defs.append(ToolDef.from_dict(dict(idname=idname_prefix + name,
label=name,
icon=icon_prefix + idname.lower(),
cursor=cursor,
data_block=idname,
**tooldef_keywords,)))
# finalize combined keys
for k, v in combinekeys.items():
tool_defs[tool_defs.index(v)] = tuple(v)
return tuple(tool_defs)
class _defs_sculpt:
@staticmethod
@ -1229,12 +1306,32 @@ class _defs_sculpt:
"SMOOTH" : ("ENHANCE_DETAILS",)
}
def get_enabled(context, idname):
if "multires" in idname.lower() or idname.lower() == "builtin_brush.displacement heal":
print("IDNAME", idname)
have_multires = False;
ob = context.object
for mod in ob.modifiers:
ok = mod.type == "MULTIRES"
ok = ok and mod.sculpt_levels > 0
ok = ok and mod.show_viewport
if ok:
have_multires = True
break
return have_multires
return True
return generate_from_enum_ex(context,
idname_prefix="builtin_brush.",
icon_prefix="brush.sculpt.",
type=bpy.types.Brush,
attr="sculpt_tool",
exclude_filter=exclude_filter,
tooldef_keywords={"get_enabled" : get_enabled},
combine_map=combine_map)
@ToolDef.from_fn

View File

@ -1361,11 +1361,50 @@ class VIEW3D_PT_sculpt_automasking(Panel, View3DPaintPanel):
brush,
"automasking_boundary_edges_propagation_steps",
ui_editing=False)
UnifiedPaintPanel.channel_unified(layout.column(),
if "CONCAVITY" in sculpt.channels["automasking"].value:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"concave_mask_factor",
ui_editing=False, slider=True, show_mappings=True)
enable_orig_normal = False
if "BRUSH_NORMAL" in sculpt.channels["automasking"].value:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"normal_mask_limit",
ui_editing=False, slider=True, show_mappings=True)
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"normal_mask_falloff",
ui_editing=False, slider=True, show_mappings=True)
enable_orig_normal = True
if "VIEW_NORMAL" in sculpt.channels["automasking"].value:
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"view_normal_mask_limit",
ui_editing=False, slider=True, show_mappings=True)
UnifiedPaintPanel.channel_unified(layout.column(),
context,
brush,
"view_normal_mask_falloff",
ui_editing=False, slider=True, show_mappings=True)
enable_orig_normal = True
sub = layout.row()
sub.enabled = enable_orig_normal
UnifiedPaintPanel.channel_unified(sub,
context,
brush,
"concave_mask_factor",
ui_editing=False, slider=True, show_mappings=True)
"automasking_use_original_normal",
ui_editing=False, show_mappings=False)
class VIEW3D_PT_sculpt_options_gravity(Panel, View3DPaintPanel):

View File

@ -270,9 +270,19 @@ MAKE_ENUM(blend,"Blending Mode","Brush blending mode",IMB_BLEND_MIX,{\
{BRUSH_AUTOMASKING_INVERT_CONCAVITY, "INVERT_CONCAVITY", "NONE", "Invert Cavity", "Invert Cavity Map"},
{BRUSH_AUTOMASKING_FACE_SETS, "FACE_SETS", "NONE", "Face Sets", ""},
{BRUSH_AUTOMASKING_TOPOLOGY, "TOPOLOGY", "NONE", "Topology", ""},
{BRUSH_AUTOMASKING_BRUSH_NORMAL, "BRUSH_NORMAL", "NONE", "Brush Normal", "Mask using normal at center of brush"},
{BRUSH_AUTOMASKING_VIEW_NORMAL, "VIEW_NORMAL", "NONE", "View Normal", "Mask using view normal"},
{-1},
})
MAKE_FLOAT(normal_mask_limit, "Brush Normal Limit", "", M_PI*0.5f, 0.0001f, M_PI)
MAKE_FLOAT(normal_mask_falloff, "Brush Normal Falloff", "", 0.1, 0.0, 1.0)
MAKE_FLOAT(view_normal_mask_limit, "View Limit", "", M_PI*0.5f, 0.0001f, M_PI)
MAKE_FLOAT(view_normal_mask_falloff, "View Falloff", "", 0.1, 0.0, 1.0)
MAKE_BOOL_EX(automasking_use_original_normal, "Original Normal", "Use original normal for automasking", true, BRUSH_CHANNEL_NO_MAPPINGS)
MAKE_BOOL_EX(dyntopo_disabled,"Disable Dyntopo","",false,BRUSH_CHANNEL_NO_MAPPINGS)
MAKE_FLAGS_EX(dyntopo_mode,"Dyntopo Operators","",DYNTOPO_COLLAPSE | DYNTOPO_CLEANUP | DYNTOPO_SUBDIVIDE,BRUSH_CHANNEL_INHERIT,{\
{DYNTOPO_COLLAPSE, "COLLAPSE", "NONE", "Collapse", ""},

View File

@ -264,7 +264,9 @@ static bool check_builtin_init()
//}
SUBTYPE_SET(smooth_stroke_radius, BRUSH_CHANNEL_PIXEL);
SUBTYPE_SET(normal_mask_limit, BRUSH_CHANNEL_ANGLE);
SUBTYPE_SET(view_normal_mask_limit, BRUSH_CHANNEL_ANGLE);
SUBTYPE_SET(jitter_absolute, BRUSH_CHANNEL_PIXEL);
SUBTYPE_SET(radius, BRUSH_CHANNEL_PIXEL);
@ -306,6 +308,11 @@ static bool check_builtin_init()
SETCAT(concave_mask_factor, "Automasking");
SETCAT(automasking, "Automasking");
SETCAT(automasking_boundary_edges_propagation_steps, "Automasking");
SETCAT(normal_mask_limit, "Automasking");
SETCAT(view_normal_mask_limit, "Automasking");
SETCAT(normal_mask_falloff, "Automasking");
SETCAT(view_normal_mask_falloff, "Automasking");
SETCAT(automasking_use_original_normal, "Automasking");
def = GETDEF(concave_mask_factor);
def->mappings.pressure.inv = true;
@ -1144,6 +1151,11 @@ void BKE_brush_builtin_patch(Brush *brush, int tool)
ADDCH(automasking);
ADDCH(automasking_boundary_edges_propagation_steps);
ADDCH(concave_mask_factor);
ADDCH(normal_mask_limit);
ADDCH(automasking_use_original_normal);
ADDCH(view_normal_mask_limit);
ADDCH(normal_mask_falloff);
ADDCH(view_normal_mask_falloff);
ADDCH(dyntopo_disabled);
ADDCH(dyntopo_disable_smooth);
@ -2045,6 +2057,12 @@ void BKE_brush_check_toolsettings(Sculpt *sd)
ADDCH(automasking_boundary_edges_propagation_steps);
ADDCH(concave_mask_factor);
ADDCH(automasking);
ADDCH(normal_mask_limit);
ADDCH(automasking_use_original_normal);
ADDCH(view_normal_mask_limit);
ADDCH(normal_mask_falloff);
ADDCH(view_normal_mask_falloff);
ADDCH(topology_rake_mode);
ADDCH(plane_offset);

View File

@ -574,12 +574,34 @@ static DRW_MeshCDMask mesh_cd_calc_used_gpu_layers(const Mesh *me,
if (layer == -1) {
layer = CustomData_get_named_layer(cd_vdata, CD_PROP_COLOR, name);
type = CD_PROP_COLOR;
if (layer != -1) {
type = CD_PROP_COLOR;
domain = ATTR_DOMAIN_POINT;
}
}
if (layer == -1) {
layer = CustomData_get_named_layer(cd_ldata, CD_PROP_COLOR, name);
if (layer != -1) {
type = CD_PROP_COLOR;
domain = ATTR_DOMAIN_CORNER;
}
}
if (layer == -1) {
layer = CustomData_get_named_layer(cd_vdata, CD_MLOOPCOL, name);
if (layer != -1) {
type = CD_MLOOPCOL;
domain = ATTR_DOMAIN_POINT;
}
}
if (layer == -1) {
layer = CustomData_get_named_layer(cd_ldata, CD_MLOOPCOL, name);
type = CD_MCOL;
if (layer != -1) {
type = CD_MLOOPCOL;
domain = ATTR_DOMAIN_CORNER;
}
}
#if 0 /* Tangents are always from UV's - this will never happen. */
@ -656,7 +678,10 @@ static DRW_MeshCDMask mesh_cd_calc_used_gpu_layers(const Mesh *me,
break;
}
case CD_MCOL:
/* note that attr->type will always be CD_PROP_COLOR event for
CD_MLOOPCOL layers, see node_shader_gpu_vertex_color in
node_shader_vertex_color.cc
*/
case CD_MLOOPCOL:
case CD_PROP_COLOR: {
const AttributeRef *render = &me->attr_color_render;
@ -667,15 +692,28 @@ static DRW_MeshCDMask mesh_cd_calc_used_gpu_layers(const Mesh *me,
if (layer == -1 && name[0] != '\0') {
layer = CustomData_get_named_layer_index(cd_ldata, type, name);
domain = layer != -1 ? ATTR_DOMAIN_CORNER : domain;
if (layer != -1) {
domain = ATTR_DOMAIN_CORNER;
}
else {
if (layer == -1) {
layer = CustomData_get_named_layer_index(cd_vdata, type, name);
domain = layer != -1 ? ATTR_DOMAIN_POINT : domain;
}
if (layer == -1) {
layer = CustomData_get_named_layer_index(cd_ldata, CD_MLOOPCOL, name);
if (layer != -1) {
domain = ATTR_DOMAIN_CORNER;
type = CD_MLOOPCOL;
}
}
if (layer == -1) {
layer = CustomData_get_named_layer_index(cd_vdata, CD_MLOOPCOL, name);
if (layer != -1) {
domain = ATTR_DOMAIN_POINT;
type = CD_MLOOPCOL;
}
}

View File

@ -56,6 +56,7 @@
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_view3d_types.h"
#include "BKE_attribute.h"
#include "BKE_brush.h"
@ -2944,6 +2945,8 @@ static int sculpt_brush_needs_normal(const SculptSession *ss, const Brush *brush
return ((SCULPT_TOOL_HAS_NORMAL_WEIGHT(SCULPT_get_tool(ss, brush)) &&
(ss->cache->normal_weight > 0.0f)) ||
SCULPT_automasking_needs_normal(ss, brush) ||
ELEM(SCULPT_get_tool(ss, brush),
SCULPT_TOOL_BLOB,
SCULPT_TOOL_CREASE,
@ -8436,6 +8439,13 @@ static bool sculpt_stroke_test_start(bContext *C, struct wmOperator *op, const f
Brush *brush = BKE_paint_brush(&sd->paint);
Object *ob = CTX_data_active_object(C);
if (SCULPT_TOOL_NEEDS_COLOR(brush->sculpt_tool)) {
View3D *v3d = CTX_wm_view3d(C);
if (v3d) {
v3d->shading.color_type = V3D_SHADING_VERTEX_COLOR;
}
}
if (brush && brush->channels) {
int tool = RNA_enum_get(op->ptr, "tool_override");
BrushChannelSet *channels = brush->channels;

View File

@ -142,6 +142,50 @@ static bool SCULPT_automasking_needs_factors_cache(SculptSession *ss,
return false;
}
bool SCULPT_automasking_needs_normal(const SculptSession *ss, const Brush *brush)
{
int flags = SCULPT_get_int(ss, automasking, NULL, brush);
return flags & (BRUSH_AUTOMASKING_BRUSH_NORMAL | BRUSH_AUTOMASKING_VIEW_NORMAL);
}
static float sculpt_automasking_normal_calc(AutomaskingCache *automasking,
SculptSession *ss,
SculptVertRef vert,
float normal[3],
float limit,
float falloff)
{
float normal_v[3];
if (automasking->settings.original_normal) {
SCULPT_vertex_check_origdata(ss, vert);
copy_v3_v3(normal_v, SCULPT_vertex_origno_get(ss, vert));
}
else {
SCULPT_vertex_normal_get(ss, vert, normal_v);
}
float angle = saacos(dot_v3v3(normal, normal_v)) / M_PI;
falloff *= 0.5;
/* note that limit is pre-divided by M_PI */
if (angle > limit - falloff && angle < limit + falloff) {
float t = 1.0f - (angle - (limit - falloff)) / (2.0 * falloff);
/* smoothstep */
t = t * t * (3.0 - 2.0 * t);
return t;
}
else if (angle > limit) {
return 0.0f;
}
return 1.0f;
}
float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
SculptSession *ss,
SculptVertRef vert)
@ -195,6 +239,32 @@ float SCULPT_automasking_factor_get(AutomaskingCache *automasking,
}
}
if (ss->cache && (automasking->settings.flags & BRUSH_AUTOMASKING_BRUSH_NORMAL)) {
float normal[3];
copy_v3_v3(normal, ss->cache->initial_normal);
mask *= sculpt_automasking_normal_calc(automasking,
ss,
vert,
normal,
automasking->settings.normal_limit,
automasking->settings.normal_falloff);
}
if (ss->cache && (automasking->settings.flags & BRUSH_AUTOMASKING_VIEW_NORMAL)) {
float normal[3];
copy_v3_v3(normal, ss->cache->view_normal);
mask *= sculpt_automasking_normal_calc(automasking,
ss,
vert,
normal,
automasking->settings.view_normal_limit,
automasking->settings.view_normal_falloff);
}
return mask;
}
@ -394,6 +464,19 @@ static void SCULPT_automasking_cache_settings_update(AutomaskingCache *automaski
automasking->settings.initial_face_set = SCULPT_active_face_set_get(ss);
automasking->settings.concave_factor = SCULPT_get_float(ss, concave_mask_factor, sd, brush);
/* pre-divide by M_PI */
automasking->settings.normal_limit = SCULPT_get_float(ss, normal_mask_limit, sd, brush) / M_PI;
automasking->settings.normal_falloff = SCULPT_get_float(ss, normal_mask_falloff, sd, brush);
automasking->settings.view_normal_limit = SCULPT_get_float(
ss, view_normal_mask_limit, sd, brush) /
M_PI;
automasking->settings.view_normal_falloff = SCULPT_get_float(
ss, view_normal_mask_falloff, sd, brush);
automasking->settings.original_normal = SCULPT_get_bool(
ss, automasking_use_original_normal, sd, brush);
}
void SCULPT_automasking_step_update(AutomaskingCache *automasking,

View File

@ -577,8 +577,9 @@ void SCULPT_pbvh_clear(Object *ob);
float SCULPT_automasking_factor_get(struct AutomaskingCache *automasking,
SculptSession *ss,
SculptVertRef vert);
bool SCULPT_automasking_needs_normal(const SculptSession *ss, const Brush *brush);
/* Returns the automasking cache depending on the active tool. Used for code that can run both for
/* Returns the automasking cache depending on the active tool. Used for code that can run both for
* brushes and filter. */
struct AutomaskingCache *SCULPT_automasking_active_cache_get(SculptSession *ss);
@ -1297,6 +1298,9 @@ typedef struct AutomaskingSettings {
int initial_face_set;
int current_face_set; // used by faceset draw tool
float concave_factor;
float normal_limit, normal_falloff;
float view_normal_limit, view_normal_falloff;
bool original_normal;
} AutomaskingSettings;
typedef struct AutomaskingCache {
@ -1995,6 +1999,8 @@ void SCULPT_ensure_persistent_layers(SculptSession *ss, struct Object *ob);
// these tools don't support dynamic pbvh splitting during the stroke
#define DYNTOPO_HAS_DYNAMIC_SPLIT(tool) true
#define SCULPT_TOOL_NEEDS_COLOR(tool) ELEM(tool, SCULPT_TOOL_PAINT, SCULPT_TOOL_SMEAR)
/*get current symmetry pass index inclusive of both
mirror and radial symmetry*/
int SCULPT_get_symmetry_pass(const SculptSession *ss);

View File

@ -344,7 +344,9 @@ typedef enum eAutomasking_flag {
BRUSH_AUTOMASKING_BOUNDARY_EDGES = (1 << 2),
BRUSH_AUTOMASKING_BOUNDARY_FACE_SETS = (1 << 3),
BRUSH_AUTOMASKING_CONCAVITY = (1 << 4),
BRUSH_AUTOMASKING_INVERT_CONCAVITY = (1 << 5)
BRUSH_AUTOMASKING_INVERT_CONCAVITY = (1 << 5),
BRUSH_AUTOMASKING_BRUSH_NORMAL = (1<<6),
BRUSH_AUTOMASKING_VIEW_NORMAL = (1 << 7),
} eAutomasking_flag;
typedef enum ePaintBrush_flag {

View File

@ -156,6 +156,7 @@ enum {
BRUSH_CHANNEL_COLOR,
BRUSH_CHANNEL_FACTOR,
BRUSH_CHANNEL_PERCENT,
BRUSH_CHANNEL_PIXEL
BRUSH_CHANNEL_PIXEL,
BRUSH_CHANNEL_ANGLE
};
/* clang-format on */

View File

@ -125,6 +125,9 @@ struct StructRNA *rna_BrushChannel_refine(PointerRNA *ptr)
case BRUSH_CHANNEL_PIXEL:
subtype = PROP_PIXEL;
break;
case BRUSH_CHANNEL_ANGLE:
subtype = PROP_ANGLE;
break;
default:
subtype = PROP_NONE;
break;