WIP: Brush assets project #106303

Draft
Julian Eisel wants to merge 358 commits from brush-assets-project into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
99 changed files with 3198 additions and 684 deletions
Showing only changes of commit 1217e35359 - Show all commits

View File

@ -4699,6 +4699,58 @@ def km_grease_pencil_sculpt_mode(params):
return keymap
def km_grease_pencil_weight_paint(params):
# NOTE: This keymap falls through to "Pose" when an armature modifying the GP object
# is selected in weight paint mode. When editing the key-map take care that pose operations
# (such as transforming bones) is not impacted.
items = []
keymap = (
"Grease Pencil Weight Paint",
{"space_type": 'EMPTY', "region_type": 'WINDOW'},
{"items": items},
)
items.extend([
# Paint weight
("grease_pencil.weight_brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS'}, None),
("grease_pencil.weight_brush_stroke", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True},
{"properties": [("mode", 'INVERT')]}),
# Increase/Decrease brush size
("brush.scale_size", {"type": 'LEFT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 0.9)]}),
("brush.scale_size", {"type": 'RIGHT_BRACKET', "value": 'PRESS', "repeat": True},
{"properties": [("scalar", 1.0 / 0.9)]}),
# Radial controls
*_template_paint_radial_control("gpencil_weight_paint"),
("wm.radial_control", {"type": 'F', "value": 'PRESS', "ctrl": True},
radial_control_properties("gpencil_weight_paint", 'weight', 'use_unified_weight')),
# Toggle Add/Subtract for weight draw tool
("grease_pencil.weight_toggle_direction", {"type": 'D', "value": 'PRESS'}, None),
# Sample weight
("grease_pencil.weight_sample", {"type": 'X', "value": 'PRESS', "shift": True}, None),
# Context menu
*_template_items_context_panel("VIEW3D_PT_gpencil_weight_context_menu", params.context_menu_event),
# Show/hide layer
*_template_items_hide_reveal_actions("grease_pencil.layer_hide", "grease_pencil.layer_reveal"),
])
if params.select_mouse == 'LEFTMOUSE':
# Bone selection for combined weight paint + pose mode (Alt).
items.extend([
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "alt": True}, None),
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "shift": True, "alt": True},
{"properties": [("toggle", True)]}),
# Ctrl-Shift-LMB is needed for MMB emulation (which conflicts with Alt).
# NOTE: this works reasonably well for pose-mode where typically selecting a single bone is sufficient.
# For selecting faces/vertices, this is less useful. Selection tools are needed in this case.
("view3d.select", {"type": 'LEFTMOUSE', "value": 'PRESS', "ctrl": True, "shift": True}, None),
])
return keymap
# ------------------------------------------------------------------------------
# Object/Pose Modes
@ -8101,6 +8153,7 @@ def km_3d_view_tool_paint_weight_sample_weight(params):
{"space_type": 'VIEW_3D', "region_type": 'WINDOW'},
{"items": [
("paint.weight_sample", {"type": params.tool_mouse, "value": 'PRESS'}, None),
("grease_pencil.weight_sample", {"type": params.tool_mouse, "value": 'PRESS'}, None),
]},
)
@ -8795,6 +8848,7 @@ def generate_keymaps(params=None):
km_grease_pencil_paint_mode(params),
km_grease_pencil_edit_mode(params),
km_grease_pencil_sculpt_mode(params),
km_grease_pencil_weight_paint(params),
# Object mode.
km_object_mode(params),
km_object_non_modal(params),

View File

@ -3,8 +3,16 @@
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from bpy.props import StringProperty
from bpy.types import (
FileHandler,
Operator,
OperatorFileListElement,
)
from bpy.props import (
BoolProperty,
CollectionProperty,
StringProperty,
)
from bpy.app.translations import pgettext_rpt as rpt_
@ -191,8 +199,121 @@ class ProjectApply(Operator):
return {'FINISHED'}
bl_file_extensions_image_movie = (*bpy.path.extensions_image, *bpy.path.extensions_movie)
class IMAGE_OT_open_images(Operator):
bl_idname = "image.open_images"
bl_label = "Open Images"
bl_options = {'REGISTER', 'UNDO'}
directory: StringProperty(
subtype='FILE_PATH',
options={'SKIP_SAVE', 'HIDDEN'},
)
files: CollectionProperty(
type=OperatorFileListElement,
options={'SKIP_SAVE', 'HIDDEN'},
)
relative_path: BoolProperty(
name="Use relative path",
default=True,
)
use_sequence_detection: BoolProperty(
name="Use sequence detection",
default=True,
)
use_udim_detection: BoolProperty(
name="Use UDIM detection",
default=True,
)
@classmethod
def poll(cls, context):
return context.area and context.area.ui_type == 'IMAGE_EDITOR'
def execute(self, context):
if not self.directory or len(self.files) == 0:
return {'CANCELLED'}
# List of files that are not part of an image sequence or UDIM group.
files = []
# Groups of files that may be part of an image sequence or a UDIM group.
sequences = []
import re
regex_extension = re.compile(
"(" + "|".join([re.escape(ext) for ext in bl_file_extensions_image_movie]) + ")$",
re.IGNORECASE,
)
regex_sequence = re.compile("(\\d+)(\\.[\\w\\d]+)$")
for file in self.files:
# Filter by extension
if not regex_extension.search(file.name):
continue
match = regex_sequence.search(file.name)
if not (match and (self.use_sequence_detection or self.use_udim_detection)):
files.append(file.name)
continue
seq = {
"prefix": file.name[:len(file.name) - len(match.group(0))],
"ext": match.group(2),
"frame_size": len(match.group(1)),
"files": [file.name]
}
for test_seq in sequences:
if (
(test_seq["prefix"] == seq["prefix"]) and
(test_seq["ext"] == seq["ext"]) and
(test_seq["frame_size"] == seq["frame_size"])
):
test_seq["files"].append(file.name)
seq = None
break
if seq:
sequences.append(seq)
import os
for file in files:
filepath = os.path.join(self.directory, file)
bpy.ops.image.open(filepath=filepath, relative_path=self.relative_path)
for seq in sequences:
seq["files"].sort()
filepath = os.path.join(self.directory, seq["files"][0])
files = [{"name": file} for file in seq["files"]]
bpy.ops.image.open(
filepath=filepath,
directory=self.directory,
files=files,
use_sequence_detection=self.use_sequence_detection,
use_udim_detecting=self.use_udim_detection,
relative_path=self.relative_path,
)
is_tiled = context.edit_image.source == 'TILED'
if len(files) > 1 and self.use_sequence_detection and not is_tiled:
context.edit_image.name = "%s%s%s" % (seq["prefix"], ("#" * seq["frame_size"]), seq["ext"])
return {'FINISHED'}
class IMAGE_FH_drop_handler(FileHandler):
bl_idname = "IMAGE_FH_drop_handler"
bl_label = "Open images"
bl_import_operator = "image.open_images"
bl_file_extensions = ";".join(bl_file_extensions_image_movie)
@classmethod
def poll_drop(cls, context):
return (
(context.area is not None) and
(context.area.ui_type == 'IMAGE_EDITOR') and
(context.region is not None) and
(context.region.type == 'WINDOW')
)
classes = (
EditExternally,
ProjectApply,
IMAGE_OT_open_images,
IMAGE_FH_drop_handler,
ProjectEdit,
)

View File

@ -84,7 +84,7 @@ class GreasePencilDisplayPanel:
settings = tool_settings.gpencil_paint
elif context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -102,7 +102,7 @@ class GreasePencilDisplayPanel:
settings = tool_settings.gpencil_paint
elif context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -156,7 +156,7 @@ class GreasePencilBrushFalloff:
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -931,7 +931,7 @@ class GreasePencilFlipTintColors(Operator):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -945,7 +945,7 @@ class GreasePencilFlipTintColors(Operator):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint

View File

@ -86,6 +86,8 @@ class UnifiedPaintPanel:
return tool_settings.gpencil_paint
elif mode == 'SCULPT_GREASE_PENCIL':
return tool_settings.gpencil_sculpt_paint
elif mode == 'WEIGHT_GREASE_PENCIL':
return tool_settings.gpencil_weight_paint
return None
@staticmethod
@ -1619,6 +1621,46 @@ def brush_basic_gpencil_vertex_settings(layout, _context, brush, *, compact=Fals
row.prop(gp_settings, "vertex_mode", text="Mode")
def brush_basic_grease_pencil_weight_settings(layout, context, brush, *, compact=False):
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"size",
pressure_name="use_pressure_size",
unified_name="use_unified_size",
text="Radius",
slider=True,
header=compact,
)
capabilities = brush.sculpt_capabilities
pressure_name = "use_pressure_strength" if capabilities.has_strength_pressure else None
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"strength",
pressure_name=pressure_name,
unified_name="use_unified_strength",
text="Strength",
header=compact,
)
if brush.gpencil_weight_tool in {'WEIGHT'}:
UnifiedPaintPanel.prop_unified(
layout,
context,
brush,
"weight",
unified_name="use_unified_weight",
text="Weight",
slider=True,
header=compact,
)
layout.prop(brush, "direction", expand=True, text="" if compact else "Direction")
classes = (
VIEW3D_MT_tools_projectpaint_clone,
)

View File

@ -2731,9 +2731,9 @@ class _defs_grease_pencil_weight:
type=bpy.types.Brush,
# Uses GPv2 tool settings
attr="gpencil_weight_tool",
# tooldef_keywords=dict(
# operator="grease_pencil.weight_paint",
# ),
tooldef_keywords=dict(
operator="grease_pencil.weight_brush_stroke",
),
)

View File

@ -12,6 +12,7 @@ from bl_ui.properties_paint_common import (
UnifiedPaintPanel,
brush_basic_texpaint_settings,
brush_basic_gpencil_weight_settings,
brush_basic_grease_pencil_weight_settings,
)
from bl_ui.properties_grease_pencil_common import (
AnnotationDataPanel,
@ -128,7 +129,7 @@ class VIEW3D_HT_tool_header(Header):
if tool in {'SMOOTH', 'RANDOMIZE'}:
layout.popover("VIEW3D_PT_tools_grease_pencil_sculpt_brush_popover")
layout.popover("VIEW3D_PT_tools_grease_pencil_sculpt_appearance")
elif tool_mode == 'WEIGHT_GPENCIL':
elif tool_mode == 'WEIGHT_GPENCIL' or tool_mode == 'WEIGHT_GREASE_PENCIL':
if is_valid_context:
layout.popover("VIEW3D_PT_tools_grease_pencil_weight_appearance")
elif tool_mode == 'VERTEX_GPENCIL':
@ -496,6 +497,25 @@ class _draw_tool_settings_context_mode:
return True
@staticmethod
def WEIGHT_GREASE_PENCIL(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
return False
paint = context.tool_settings.gpencil_weight_paint
layout.template_ID_preview(paint, "brush", rows=3, cols=8, hide_buttons=True)
brush = paint.brush
if brush is None:
return False
brush_basic_grease_pencil_weight_settings(layout, context, brush, compact=True)
layout.popover("VIEW3D_PT_tools_grease_pencil_weight_options", text="Options")
layout.popover("VIEW3D_PT_tools_grease_pencil_brush_weight_falloff", text="Falloff")
return True
@staticmethod
def VERTEX_GPENCIL(context, layout, tool):
if (tool is None) or (not tool.has_datablock):
@ -725,7 +745,7 @@ class VIEW3D_HT_header(Header):
else:
if (object_mode not in {
'SCULPT', 'SCULPT_CURVES', 'VERTEX_PAINT', 'WEIGHT_PAINT', 'TEXTURE_PAINT',
'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL', 'PAINT_GREASE_PENCIL',
'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL',
}) or has_pose_mode:
show_snap = True
else:
@ -870,15 +890,15 @@ class VIEW3D_HT_header(Header):
depress=(tool_settings.gpencil_selectmode_edit == 'STROKE'),
).mode = 'STROKE'
if object_mode == 'PAINT_GREASE_PENCIL':
if object_mode == 'PAINT_GPENCIL':
row = layout.row(align=True)
row.prop(tool_settings, "use_gpencil_draw_additive", text="", icon='FREEZE')
if object_mode in {'PAINT_GREASE_PENCIL', 'EDIT', 'WEIGHT_PAINT'}:
if object_mode in {'PAINT_GPENCIL', 'EDIT', 'WEIGHT_GPENCIL'}:
row = layout.row(align=True)
row.prop(tool_settings, "use_grease_pencil_multi_frame_editing", text="")
if object_mode in {'EDIT', 'WEIGHT_PAINT'}:
if object_mode in {'EDIT', 'WEIGHT_GPENCIL'}:
sub = row.row(align=True)
sub.enabled = tool_settings.use_grease_pencil_multi_frame_editing
sub.popover(
@ -961,9 +981,9 @@ class VIEW3D_HT_header(Header):
layout.separator_spacer()
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL'}:
# Grease pencil
if object_mode in {'PAINT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
if object_mode == 'PAINT_GPENCIL':
sub = layout.row(align=True)
sub.prop_with_popover(
tool_settings,
@ -972,7 +992,7 @@ class VIEW3D_HT_header(Header):
panel="VIEW3D_PT_gpencil_origin",
)
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'PAINT_GREASE_PENCIL'}:
if object_mode in {'PAINT_GPENCIL', 'SCULPT_GPENCIL'}:
sub = layout.row(align=True)
sub.active = tool_settings.gpencil_stroke_placement_view3d != 'SURFACE'
sub.prop_with_popover(
@ -985,7 +1005,8 @@ class VIEW3D_HT_header(Header):
if object_mode == 'PAINT_GPENCIL':
# FIXME: this is bad practice!
# Tool options are to be displayed in the top-bar.
if context.workspace.tools.from_space_view3d_mode(object_mode).idname == "builtin_brush.Draw":
tool = context.workspace.tools.from_space_view3d_mode(object_mode)
if tool and tool.idname == "builtin_brush.Draw":
settings = tool_settings.gpencil_sculpt.guide
row = layout.row(align=True)
row.prop(settings, "use_guide", text="", icon='GRID')
@ -1166,7 +1187,7 @@ class VIEW3D_MT_editor_menus(Menu):
obj = context.active_object
mode_string = context.mode
edit_object = context.edit_object
gp_edit = obj and obj.mode in {
gp_edit = obj and obj.type == 'GPENCIL' and obj.mode in {
'EDIT_GPENCIL', 'PAINT_GPENCIL', 'SCULPT_GPENCIL', 'WEIGHT_GPENCIL', 'VERTEX_GPENCIL',
}
tool_settings = context.tool_settings
@ -7927,6 +7948,7 @@ class VIEW3D_PT_overlay_grease_pencil_options(Panel):
layout.label(text={
'PAINT_GREASE_PENCIL': iface_("Draw Grease Pencil"),
'EDIT_GREASE_PENCIL': iface_("Edit Grease Pencil"),
'WEIGHT_GREASE_PENCIL': iface_("Weight Grease Pencil"),
'OBJECT': iface_("Grease Pencil"),
}[context.mode], translate=False)
@ -8477,7 +8499,11 @@ class VIEW3D_PT_gpencil_weight_context_menu(Panel):
layout = self.layout
# Weight settings
brush_basic_gpencil_weight_settings(layout, context, brush)
if context.mode == 'WEIGHT_GPENCIL':
brush_basic_gpencil_weight_settings(layout, context, brush)
else:
# Grease Pencil v3
brush_basic_grease_pencil_weight_settings(layout, context, brush)
# Layers
draw_gpencil_layer_active(context, layout)

View File

@ -72,7 +72,7 @@ class VIEW3D_MT_brush_gpencil_context_menu(Menu):
settings = tool_settings.gpencil_paint
if context.mode == 'SCULPT_GPENCIL':
settings = tool_settings.gpencil_sculpt_paint
elif context.mode == 'WEIGHT_GPENCIL':
elif context.mode == 'WEIGHT_GPENCIL' or context.mode == 'WEIGHT_GREASE_PENCIL':
settings = tool_settings.gpencil_weight_paint
elif context.mode == 'VERTEX_GPENCIL':
settings = tool_settings.gpencil_vertex_paint
@ -2106,6 +2106,9 @@ class GreasePencilWeightPanel:
@classmethod
def poll(cls, context):
if context.space_data.type in {'VIEW_3D', 'PROPERTIES'}:
if context.object and context.object.type == 'GREASEPENCIL' and context.mode == 'WEIGHT_GREASE_PENCIL':
return True
if context.gpencil_data is None:
return False
@ -2132,7 +2135,7 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_select(View3DPanel, Panel, Grea
col = row.column()
col.menu("VIEW3D_MT_brush_gpencil_context_menu", icon='DOWNARROW_HLT', text="")
if context.mode == 'WEIGHT_GPENCIL':
if context.mode in {'WEIGHT_GPENCIL', 'WEIGHT_GREASE_PENCIL'}:
brush = tool_settings.gpencil_weight_paint.brush
if brush is not None:
col.prop(brush, "use_custom_icon", toggle=True, icon='FILE_IMAGE', text="")
@ -2156,10 +2159,17 @@ class VIEW3D_PT_tools_grease_pencil_weight_paint_settings(Panel, View3DPanel, Gr
settings = tool_settings.gpencil_weight_paint
brush = settings.brush
from bl_ui.properties_paint_common import (
brush_basic_gpencil_weight_settings,
)
brush_basic_gpencil_weight_settings(layout, context, brush)
if context.mode == 'WEIGHT_GPENCIL':
from bl_ui.properties_paint_common import (
brush_basic_gpencil_weight_settings,
)
brush_basic_gpencil_weight_settings(layout, context, brush)
else:
# Grease Pencil v3
from bl_ui.properties_paint_common import (
brush_basic_grease_pencil_weight_settings,
)
brush_basic_grease_pencil_weight_settings(layout, context, brush)
class VIEW3D_PT_tools_grease_pencil_brush_weight_falloff(GreasePencilBrushFalloff, Panel, View3DPaintPanel):

View File

@ -16,6 +16,9 @@ namespace blender::bke::greasepencil {
/** Make sure drawings only contain vertex groups of the #GreasePencil. */
void validate_drawing_vertex_groups(GreasePencil &grease_pencil);
/** Find or create a vertex group in a drawing. */
int ensure_vertex_group(const StringRef name, ListBase &vertex_group_names);
/** Assign selected vertices to the vertex group. */
void assign_to_vertex_group(GreasePencil &grease_pencil, StringRef name, float weight);

View File

@ -143,7 +143,7 @@ struct LibraryIDLinkCallbackData {
*
* \return a set of flags to control further iteration (0 to keep going).
*/
using LibraryIDLinkCallback = int (*)(LibraryIDLinkCallbackData *cb_data);
using LibraryIDLinkCallback = int(LibraryIDLinkCallbackData *cb_data);
/* Flags for the foreach function itself. */
enum {
@ -271,8 +271,11 @@ void BKE_lib_query_idpropertiesForeachIDLink_callback(IDProperty *id_prop, void
/**
* Loop over all of the ID's this data-block links to.
*/
void BKE_library_foreach_ID_link(
Main *bmain, ID *id, LibraryIDLinkCallback callback, void *user_data, int flag);
void BKE_library_foreach_ID_link(Main *bmain,
ID *id,
blender::FunctionRef<LibraryIDLinkCallback> callback,
void *user_data,
int flag);
/**
* Re-usable function, use when replacing ID's.
*/

View File

@ -87,6 +87,7 @@ extern const uchar PAINT_CURSOR_VERTEX_PAINT[3];
extern const uchar PAINT_CURSOR_WEIGHT_PAINT[3];
extern const uchar PAINT_CURSOR_TEXTURE_PAINT[3];
extern const uchar PAINT_CURSOR_SCULPT_CURVES[3];
extern const uchar PAINT_CURSOR_PAINT_GREASE_PENCIL[3];
extern const uchar PAINT_CURSOR_SCULPT_GREASE_PENCIL[3];
enum class PaintMode : int8_t {

View File

@ -1964,16 +1964,17 @@ static bool nla_combine_quaternion_get_inverted_strip_values(const float lower_v
/* ---------------------- */
/* Assert necs and necs->channel is nonNull. */
static void nlaevalchan_assert_nonNull(NlaEvalChannelSnapshot *necs)
static void nlaevalchan_assert_nonNull(const NlaEvalChannelSnapshot *necs)
{
UNUSED_VARS_NDEBUG(necs);
BLI_assert(necs != nullptr && necs->channel != nullptr);
}
/* Assert that the channels given can be blended or combined together. */
static void nlaevalchan_assert_blendOrcombine_compatible(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
NlaEvalChannelSnapshot *blended_necs)
static void nlaevalchan_assert_blendOrcombine_compatible(
const NlaEvalChannelSnapshot *lower_necs,
const NlaEvalChannelSnapshot *upper_necs,
const NlaEvalChannelSnapshot *blended_necs)
{
UNUSED_VARS_NDEBUG(lower_necs, upper_necs, blended_necs);
BLI_assert(!ELEM(nullptr, lower_necs, blended_necs));
@ -2011,7 +2012,7 @@ static void nlaevalchan_assert_blendOrcombine_compatible_quaternion(
BLI_assert(lower_necs->length == 4);
}
static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelSnapshot *src)
static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, const NlaEvalChannelSnapshot *src)
{
memcpy(dst->values, src->values, src->length * sizeof(float));
}
@ -2020,10 +2021,11 @@ static void nlaevalchan_copy_values(NlaEvalChannelSnapshot *dst, NlaEvalChannelS
* Copies from lower necs to blended necs if upper necs is nullptr or has zero influence.
* \return true if copied.
*/
static bool nlaevalchan_blendOrcombine_try_copy_from_lower(NlaEvalChannelSnapshot *lower_necs,
NlaEvalChannelSnapshot *upper_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
static bool nlaevalchan_blendOrcombine_try_copy_from_lower(
const NlaEvalChannelSnapshot *lower_necs,
const NlaEvalChannelSnapshot *upper_necs,
const float upper_influence,
NlaEvalChannelSnapshot *r_blended_necs)
{
const bool has_influence = !IS_EQF(upper_influence, 0.0f);
if (upper_necs != nullptr && has_influence) {

View File

@ -1183,7 +1183,12 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit,
return CTX_MODE_PARTICLE;
}
if (object_mode & OB_MODE_PAINT_GPENCIL_LEGACY) {
return CTX_MODE_PAINT_GPENCIL_LEGACY;
if (ob->type == OB_GPENCIL_LEGACY) {
return CTX_MODE_PAINT_GPENCIL_LEGACY;
}
if (ob->type == OB_GREASE_PENCIL) {
return CTX_MODE_PAINT_GREASE_PENCIL;
}
}
if (object_mode & OB_MODE_EDIT_GPENCIL_LEGACY) {
return CTX_MODE_EDIT_GPENCIL_LEGACY;
@ -1210,9 +1215,6 @@ enum eContextObjectMode CTX_data_mode_enum_ex(const Object *obedit,
if (object_mode & OB_MODE_SCULPT_CURVES) {
return CTX_MODE_SCULPT_CURVES;
}
if (object_mode & OB_MODE_PAINT_GREASE_PENCIL) {
return CTX_MODE_PAINT_GREASE_PENCIL;
}
}
}

View File

@ -1128,7 +1128,7 @@ static ExprPyLike_Parsed *driver_compile_simple_expr_impl(ChannelDriver *driver)
return BLI_expr_pylike_parse(driver->expression, names, names_len + VAR_INDEX_CUSTOM);
}
static bool driver_check_simple_expr_depends_on_time(ExprPyLike_Parsed *expr)
static bool driver_check_simple_expr_depends_on_time(const ExprPyLike_Parsed *expr)
{
/* Check if the 'frame' parameter is actually used. */
return BLI_expr_pylike_is_using_param(expr, VAR_INDEX_FRAME);

View File

@ -52,6 +52,19 @@ void validate_drawing_vertex_groups(GreasePencil &grease_pencil)
}
}
int ensure_vertex_group(const StringRef name, ListBase &vertex_group_names)
{
int def_nr = BLI_findstringindex(&vertex_group_names, name.data(), offsetof(bDeformGroup, name));
if (def_nr < 0) {
bDeformGroup *defgroup = MEM_cnew<bDeformGroup>(__func__);
STRNCPY(defgroup->name, name.data());
BLI_addtail(&vertex_group_names, defgroup);
def_nr = BLI_listbase_count(&vertex_group_names) - 1;
BLI_assert(def_nr >= 0);
}
return def_nr;
}
void assign_to_vertex_group(GreasePencil &grease_pencil, const StringRef name, const float weight)
{
for (GreasePencilDrawingBase *base : grease_pencil.drawings()) {

View File

@ -52,7 +52,7 @@ struct LibraryForeachIDData {
/* Function to call for every ID pointers of current processed data, and its opaque user data
* pointer. */
LibraryIDLinkCallback callback;
blender::FunctionRef<LibraryIDLinkCallback> callback;
void *user_data;
/** Store the returned value from the callback, to decide how to continue the processing of ID
* pointers for current data. */
@ -132,7 +132,7 @@ int BKE_lib_query_foreachid_process_callback_flag_override(LibraryForeachIDData
static bool library_foreach_ID_link(Main *bmain,
ID *owner_id,
ID *id,
LibraryIDLinkCallback callback,
blender::FunctionRef<LibraryIDLinkCallback> callback,
void *user_data,
int flag,
LibraryForeachIDData *inherit_data);
@ -198,7 +198,7 @@ static void library_foreach_ID_data_cleanup(LibraryForeachIDData *data)
static bool library_foreach_ID_link(Main *bmain,
ID *owner_id,
ID *id,
LibraryIDLinkCallback callback,
blender::FunctionRef<LibraryIDLinkCallback> callback,
void *user_data,
int flag,
LibraryForeachIDData *inherit_data)
@ -379,8 +379,11 @@ static bool library_foreach_ID_link(Main *bmain,
#undef CALLBACK_INVOKE
}
void BKE_library_foreach_ID_link(
Main *bmain, ID *id, LibraryIDLinkCallback callback, void *user_data, int flag)
void BKE_library_foreach_ID_link(Main *bmain,
ID *id,
blender::FunctionRef<LibraryIDLinkCallback> callback,
void *user_data,
int flag)
{
library_foreach_ID_link(bmain, nullptr, id, callback, user_data, flag, nullptr);
}

View File

@ -715,9 +715,21 @@ bool BKE_object_defgroup_check_lock_relative_multi(int defbase_tot,
bool BKE_object_defgroup_active_is_locked(const Object *ob)
{
Mesh *mesh = static_cast<Mesh *>(ob->data);
bDeformGroup *dg = static_cast<bDeformGroup *>(
BLI_findlink(&mesh->vertex_group_names, mesh->vertex_group_active_index - 1));
bDeformGroup *dg;
switch (ob->type) {
case OB_GREASE_PENCIL: {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
dg = static_cast<bDeformGroup *>(BLI_findlink(&grease_pencil->vertex_group_names,
grease_pencil->vertex_group_active_index - 1));
break;
}
default: {
Mesh *mesh = static_cast<Mesh *>(ob->data);
dg = static_cast<bDeformGroup *>(
BLI_findlink(&mesh->vertex_group_names, mesh->vertex_group_active_index - 1));
break;
}
}
return dg->flag & DG_LOCK_WEIGHT;
}

View File

@ -255,6 +255,7 @@ const uchar PAINT_CURSOR_VERTEX_PAINT[3] = {255, 255, 255};
const uchar PAINT_CURSOR_WEIGHT_PAINT[3] = {200, 200, 255};
const uchar PAINT_CURSOR_TEXTURE_PAINT[3] = {255, 255, 255};
const uchar PAINT_CURSOR_SCULPT_CURVES[3] = {255, 100, 100};
const uchar PAINT_CURSOR_PAINT_GREASE_PENCIL[3] = {255, 100, 100};
const uchar PAINT_CURSOR_SCULPT_GREASE_PENCIL[3] = {255, 100, 100};
static ePaintOverlayControlFlags overlay_flags = (ePaintOverlayControlFlags)0;
@ -478,8 +479,6 @@ Paint *BKE_paint_get_active(Scene *sce, ViewLayer *view_layer)
return &ts->gp_weightpaint->paint;
case OB_MODE_SCULPT_CURVES:
return &ts->curves_sculpt->paint;
case OB_MODE_PAINT_GREASE_PENCIL:
return &ts->gp_paint->paint;
case OB_MODE_EDIT:
return ts->uvsculpt ? &ts->uvsculpt->paint : nullptr;
default:
@ -561,6 +560,8 @@ PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
return PaintMode::SculptGreasePencil;
}
return PaintMode::Invalid;
case OB_MODE_PAINT_GPENCIL_LEGACY:
return PaintMode::GPencil;
case OB_MODE_WEIGHT_GPENCIL_LEGACY:
return PaintMode::WeightGPencil;
case OB_MODE_VERTEX_PAINT:
@ -573,8 +574,6 @@ PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
return PaintMode::SculptUV;
case OB_MODE_SCULPT_CURVES:
return PaintMode::SculptCurves;
case OB_MODE_PAINT_GREASE_PENCIL:
return PaintMode::GPencil;
default:
return PaintMode::Texture2D;
}
@ -840,12 +839,7 @@ static void paint_runtime_init(const ToolSettings *ts, Paint *paint)
paint->runtime.ob_mode = OB_MODE_EDIT;
}
else if (ts->gp_paint && paint == &ts->gp_paint->paint) {
if (U.experimental.use_grease_pencil_version3) {
paint->runtime.ob_mode = OB_MODE_PAINT_GREASE_PENCIL;
}
else {
paint->runtime.ob_mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
paint->runtime.ob_mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
else if (ts->gp_vertexpaint && paint == &ts->gp_vertexpaint->paint) {
paint->runtime.ob_mode = OB_MODE_VERTEX_GPENCIL_LEGACY;
@ -1225,7 +1219,7 @@ eObjectMode BKE_paint_object_mode_from_paintmode(const PaintMode mode)
case PaintMode::SculptCurves:
return OB_MODE_SCULPT_CURVES;
case PaintMode::GPencil:
return OB_MODE_PAINT_GREASE_PENCIL;
return OB_MODE_PAINT_GPENCIL_LEGACY;
case PaintMode::SculptGreasePencil:
return OB_MODE_SCULPT_GPENCIL_LEGACY;
case PaintMode::Invalid:

View File

@ -300,10 +300,10 @@ bool indexed_data_equal(const Span<T> all_values, const Span<int> indices, const
{
for (const int i : indices.index_range()) {
if (all_values[indices[i]] != values[i]) {
return true;
return false;
}
}
return false;
return true;
}
bool indices_are_range(Span<int> indices, IndexRange range);

View File

@ -787,6 +787,7 @@ void BLI_path_rel(char path[FILE_MAX], const char *basepath)
/* Don't copy the slash at the beginning. */
r += BLI_strncpy_rlen(r, q + 1, sizeof(res) - (r - res));
UNUSED_VARS(r);
#ifdef WIN32
BLI_string_replace_char(res + 2, '/', '\\');

View File

@ -293,6 +293,7 @@ char *BLI_vsprintfN_with_buffer(char *fixed_buf,
char *result = MEM_mallocN(sizeof(char) * size, __func__);
retval = vsnprintf(result, size, format, args);
BLI_assert((size_t)(retval + 1) == size);
UNUSED_VARS_NDEBUG(retval);
return result;
}

View File

@ -329,7 +329,7 @@ LinkNode *BLO_blendhandle_get_linkable_groups(BlendHandle *bh);
*
* \param bh: The handle to close.
*/
void BLO_blendhandle_close(BlendHandle *bh);
void BLO_blendhandle_close(BlendHandle *bh) ATTR_NONNULL(1);
/**
* Mark the given Main (and the 'root' local one in case of lib-split Mains) as invalid, and

View File

@ -1316,63 +1316,60 @@ FileData *blo_filedata_from_memfile(MemFile *memfile,
void blo_filedata_free(FileData *fd)
{
if (fd) {
/* Free all BHeadN data blocks */
/* Free all BHeadN data blocks */
#ifndef NDEBUG
BLI_freelistN(&fd->bhead_list);
BLI_freelistN(&fd->bhead_list);
#else
/* Sanity check we're not keeping memory we don't need. */
LISTBASE_FOREACH_MUTABLE (BHeadN *, new_bhead, &fd->bhead_list) {
if (fd->file->seek != nullptr && BHEAD_USE_READ_ON_DEMAND(&new_bhead->bhead)) {
BLI_assert(new_bhead->has_data == 0);
}
MEM_freeN(new_bhead);
/* Sanity check we're not keeping memory we don't need. */
LISTBASE_FOREACH_MUTABLE (BHeadN *, new_bhead, &fd->bhead_list) {
if (fd->file->seek != nullptr && BHEAD_USE_READ_ON_DEMAND(&new_bhead->bhead)) {
BLI_assert(new_bhead->has_data == 0);
}
MEM_freeN(new_bhead);
}
#endif
fd->file->close(fd->file);
fd->file->close(fd->file);
if (fd->filesdna) {
DNA_sdna_free(fd->filesdna);
}
if (fd->compflags) {
MEM_freeN((void *)fd->compflags);
}
if (fd->reconstruct_info) {
DNA_reconstruct_info_free(fd->reconstruct_info);
}
if (fd->filesdna) {
DNA_sdna_free(fd->filesdna);
}
if (fd->compflags) {
MEM_freeN((void *)fd->compflags);
}
if (fd->reconstruct_info) {
DNA_reconstruct_info_free(fd->reconstruct_info);
}
if (fd->datamap) {
oldnewmap_free(fd->datamap);
}
if (fd->globmap) {
oldnewmap_free(fd->globmap);
}
if (fd->packedmap) {
oldnewmap_free(fd->packedmap);
}
if (fd->libmap && !(fd->flags & FD_FLAGS_NOT_MY_LIBMAP)) {
oldnewmap_free(fd->libmap);
}
if (fd->old_idmap_uid != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap_uid);
}
if (fd->new_idmap_uid != nullptr) {
BKE_main_idmap_destroy(fd->new_idmap_uid);
}
blo_cache_storage_end(fd);
if (fd->bheadmap) {
MEM_freeN(fd->bheadmap);
}
if (fd->datamap) {
oldnewmap_free(fd->datamap);
}
if (fd->globmap) {
oldnewmap_free(fd->globmap);
}
if (fd->packedmap) {
oldnewmap_free(fd->packedmap);
}
if (fd->libmap && !(fd->flags & FD_FLAGS_NOT_MY_LIBMAP)) {
oldnewmap_free(fd->libmap);
}
if (fd->old_idmap_uid != nullptr) {
BKE_main_idmap_destroy(fd->old_idmap_uid);
}
if (fd->new_idmap_uid != nullptr) {
BKE_main_idmap_destroy(fd->new_idmap_uid);
}
blo_cache_storage_end(fd);
if (fd->bheadmap) {
MEM_freeN(fd->bheadmap);
}
#ifdef USE_GHASH_BHEAD
if (fd->bhead_idname_hash) {
BLI_ghash_free(fd->bhead_idname_hash, nullptr, nullptr);
}
if (fd->bhead_idname_hash) {
BLI_ghash_free(fd->bhead_idname_hash, nullptr, nullptr);
}
#endif
MEM_freeN(fd);
}
MEM_freeN(fd);
}
/** \} */
@ -1383,31 +1380,28 @@ void blo_filedata_free(FileData *fd)
BlendThumbnail *BLO_thumbnail_from_file(const char *filepath)
{
FileData *fd;
BlendThumbnail *data = nullptr;
const int *fd_data;
fd = blo_filedata_from_file_minimal(filepath);
fd_data = fd ? read_file_thumbnail(fd) : nullptr;
if (fd_data) {
const int width = fd_data[0];
const int height = fd_data[1];
if (BLEN_THUMB_MEMSIZE_IS_VALID(width, height)) {
const size_t data_size = BLEN_THUMB_MEMSIZE(width, height);
data = static_cast<BlendThumbnail *>(MEM_mallocN(data_size, __func__));
if (data) {
BLI_assert((data_size - sizeof(*data)) ==
(BLEN_THUMB_MEMSIZE_FILE(width, height) - (sizeof(*fd_data) * 2)));
data->width = width;
data->height = height;
memcpy(data->rect, &fd_data[2], data_size - sizeof(*data));
FileData *fd = blo_filedata_from_file_minimal(filepath);
if (fd) {
if (const int *fd_data = read_file_thumbnail(fd)) {
const int width = fd_data[0];
const int height = fd_data[1];
if (BLEN_THUMB_MEMSIZE_IS_VALID(width, height)) {
const size_t data_size = BLEN_THUMB_MEMSIZE(width, height);
data = static_cast<BlendThumbnail *>(MEM_mallocN(data_size, __func__));
if (data) {
BLI_assert((data_size - sizeof(*data)) ==
(BLEN_THUMB_MEMSIZE_FILE(width, height) - (sizeof(*fd_data) * 2)));
data->width = width;
data->height = height;
memcpy(data->rect, &fd_data[2], data_size - sizeof(*data));
}
}
}
blo_filedata_free(fd);
}
blo_filedata_free(fd);
return data;
}
@ -4794,6 +4788,7 @@ static void *blo_verify_data_address(void *new_address,
* or might be passed the size of a base struct with inheritance. */
BLI_assert_msg(MEM_allocN_len(new_address) >= expected_size,
"Corrupt .blend file, unexpected data size.");
UNUSED_VARS_NDEBUG(expected_size);
}
return new_address;

View File

@ -147,7 +147,7 @@ struct FileData {
void blo_join_main(ListBase *mainlist);
void blo_split_main(ListBase *mainlist, Main *main);
BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath);
BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath) ATTR_NONNULL(1, 2);
/**
* On each new library added, it now checks for the current #FileData and expands relativeness
@ -160,29 +160,30 @@ FileData *blo_filedata_from_memfile(MemFile *memfile,
const BlendFileReadParams *params,
BlendFileReadReport *reports);
void blo_make_packed_pointer_map(FileData *fd, Main *oldmain);
void blo_make_packed_pointer_map(FileData *fd, Main *oldmain) ATTR_NONNULL(1, 2);
/**
* Set old main packed data to zero if it has been restored
* this works because freeing old main only happens after this call.
*/
void blo_end_packed_pointer_map(FileData *fd, Main *oldmain);
void blo_end_packed_pointer_map(FileData *fd, Main *oldmain) ATTR_NONNULL(1, 2);
/**
* Build a #GSet of old main (we only care about local data here,
* so we can do that after #blo_split_main() call.
*/
void blo_make_old_idmap_from_main(FileData *fd, Main *bmain);
void blo_make_old_idmap_from_main(FileData *fd, Main *bmain) ATTR_NONNULL(1, 2);
BHead *blo_read_asset_data_block(FileData *fd, BHead *bhead, AssetMetaData **r_asset_data);
BHead *blo_read_asset_data_block(FileData *fd, BHead *bhead, AssetMetaData **r_asset_data)
ATTR_NONNULL(1, 2);
void blo_cache_storage_init(FileData *fd, Main *bmain);
void blo_cache_storage_old_bmain_clear(FileData *fd, Main *bmain_old);
void blo_cache_storage_end(FileData *fd);
void blo_cache_storage_init(FileData *fd, Main *bmain) ATTR_NONNULL(1, 2);
void blo_cache_storage_old_bmain_clear(FileData *fd, Main *bmain_old) ATTR_NONNULL(1, 2);
void blo_cache_storage_end(FileData *fd) ATTR_NONNULL(1);
void blo_filedata_free(FileData *fd);
void blo_filedata_free(FileData *fd) ATTR_NONNULL(1);
BHead *blo_bhead_first(FileData *fd);
BHead *blo_bhead_next(FileData *fd, BHead *thisblock);
BHead *blo_bhead_prev(FileData *fd, BHead *thisblock);
BHead *blo_bhead_first(FileData *fd) ATTR_NONNULL(1);
BHead *blo_bhead_next(FileData *fd, BHead *thisblock) ATTR_NONNULL(1);
BHead *blo_bhead_prev(FileData *fd, BHead *thisblock) ATTR_NONNULL(1, 2);
/**
* Warning! Caller's responsibility to ensure given bhead **is** an ID one!
@ -254,8 +255,8 @@ void do_versions_after_setup(Main *new_bmain, BlendFileReadReport *reports);
* \note This is rather unfortunate to have to expose this here,
* but better use that nasty hack in do_version than readfile itself.
*/
void *blo_read_get_new_globaldata_address(FileData *fd, const void *adr);
void *blo_read_get_new_globaldata_address(FileData *fd, const void *adr) ATTR_NONNULL(1);
/* Mark the Main data as invalid (.blend file reading should be aborted ASAP, and the already read
* data should be discarded). Also add an error report to `fd` including given `message`. */
void blo_readfile_invalidate(FileData *fd, Main *bmain, const char *message);
void blo_readfile_invalidate(FileData *fd, Main *bmain, const char *message) ATTR_NONNULL(1, 2, 3);

View File

@ -23,6 +23,11 @@ void main()
float depth = texelFetch(hiz_tx, texel, 0).r;
GBufferReader gbuf = gbuffer_read(gbuf_header_tx, gbuf_closure_tx, gbuf_normal_tx, texel);
/* Bias the shading point position because of depth buffer precision.
* Constant is taken from https://www.terathon.com/gdc07_lengyel.pdf. */
const float bias = 2.4e-7;
depth -= bias;
vec3 P = drw_point_screen_to_world(vec3(uvcoordsvar.xy, depth));
vec3 Ng = gbuf.surface_N;
vec3 V = drw_world_incident_vector(P);

View File

@ -59,7 +59,7 @@ void main()
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
render_view_buf[drw_view_id].clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@ -59,6 +59,6 @@ void main()
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
render_view_buf[drw_view_id].clip_distance_inv);
#endif
}

View File

@ -41,7 +41,7 @@ void main()
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
render_view_buf[drw_view_id].clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@ -53,7 +53,7 @@ void main()
#ifdef MAT_SHADOW
shadow_clip.vector = shadow_clip_vector_get(drw_point_world_to_view(interp.P),
render_view_buf[drw_view_id]);
render_view_buf[drw_view_id].clip_distance_inv);
#endif
gl_Position = drw_point_world_to_homogenous(interp.P);

View File

@ -165,9 +165,8 @@ void shadow_viewport_layer_set(int view_id, int lod)
/* In order to support physical clipping, we pass a vector to the fragment shader that then clips
* each fragment using a unit sphere test. This allows to support both point light and area light
* clipping at the same time. */
vec3 shadow_clip_vector_get(vec3 view_position, ShadowRenderView shadow_view)
vec3 shadow_clip_vector_get(vec3 view_position, float clip_distance_inv)
{
float clip_distance_inv = shadow_view.clip_distance_inv;
if (clip_distance_inv == 0.0) {
/* No clipping. */
return vec3(2.0);

View File

@ -781,9 +781,11 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
const VArray<int> stroke_materials = *attributes.lookup_or_default<int>(
"material_index", bke::AttrDomain::Curve, 0);
const bool only_lines =
!ELEM(ob->mode, OB_MODE_PAINT_GREASE_PENCIL, OB_MODE_WEIGHT_PAINT, OB_MODE_VERTEX_PAINT) &&
info.frame_number != pd->cfra && pd->use_multiedit_lines_only;
const bool only_lines = !ELEM(ob->mode,
OB_MODE_PAINT_GPENCIL_LEGACY,
OB_MODE_WEIGHT_GPENCIL_LEGACY,
OB_MODE_VERTEX_GPENCIL_LEGACY) &&
info.frame_number != pd->cfra && pd->use_multiedit_lines_only;
const bool is_onion = info.onion_id != 0;
visible_strokes.foreach_index([&](const int stroke_i) {

View File

@ -1394,6 +1394,7 @@ namespace curve_type_set {
static int exec(bContext *C, wmOperator *op)
{
const CurveType dst_type = CurveType(RNA_enum_get(op->ptr, "type"));
const bool use_handles = RNA_boolean_get(op->ptr, "use_handles");
for (Curves *curves_id : get_unique_editable_curves(*C)) {
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
@ -1403,7 +1404,13 @@ static int exec(bContext *C, wmOperator *op)
continue;
}
curves = geometry::convert_curves(curves, selection, dst_type, {});
geometry::ConvertCurvesOptions options;
options.convert_bezier_handles_to_poly_points = use_handles;
options.convert_bezier_handles_to_catmull_rom_points = use_handles;
options.keep_bezier_shape_as_nurbs = use_handles;
options.keep_catmull_rom_shape_as_nurbs = use_handles;
curves = geometry::convert_curves(curves, selection, dst_type, {}, options);
DEG_id_tag_update(&curves_id->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GEOM | ND_DATA, curves_id);
@ -1426,6 +1433,12 @@ static void CURVES_OT_curve_type_set(wmOperatorType *ot)
ot->prop = RNA_def_enum(
ot->srna, "type", rna_enum_curves_type_items, CURVE_TYPE_POLY, "Type", "Curve type");
RNA_def_boolean(ot->srna,
"use_handles",
false,
"Handles",
"Take handle information into account in the conversion");
}
namespace switch_direction {

View File

@ -339,10 +339,22 @@ static bool gpencil_paintmode_toggle_poll(bContext *C)
{
/* if using gpencil object, use this gpd */
Object *ob = CTX_data_active_object(C);
if ((ob) && (ob->type == OB_GPENCIL_LEGACY)) {
if ((ob) && (ELEM(ob->type, OB_GPENCIL_LEGACY, OB_GREASE_PENCIL))) {
return ob->data != nullptr;
}
return ED_gpencil_data_get_active(C) != nullptr;
return false;
}
static bool gpencil_paint_poll_view3d(bContext *C)
{
const Object *ob = CTX_data_active_object(C);
if (ob == nullptr || (ob->mode & OB_MODE_PAINT_GPENCIL_LEGACY) == 0) {
return false;
}
if (CTX_wm_region_view3d(C) == nullptr) {
return false;
}
return true;
}
static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
@ -351,7 +363,6 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
wmMsgBus *mbus = CTX_wm_message_bus(C);
Main *bmain = CTX_data_main(C);
bGPdata *gpd = ED_gpencil_data_get_active(C);
ToolSettings *ts = CTX_data_tool_settings(C);
bool is_object = false;
@ -359,28 +370,48 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
/* if using a gpencil object, use this datablock */
Object *ob = CTX_data_active_object(C);
if ((ob) && (ob->type == OB_GPENCIL_LEGACY)) {
gpd = static_cast<bGPdata *>(ob->data);
bGPdata *gpd = static_cast<bGPdata *>(ob->data);
if (gpd == nullptr) {
return OPERATOR_CANCELLED;
}
/* Just toggle paintmode flag... */
gpd->flag ^= GP_DATA_STROKE_PAINTMODE;
/* set mode */
if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
else {
mode = OB_MODE_OBJECT;
}
is_object = true;
}
if (gpd == nullptr) {
return OPERATOR_CANCELLED;
}
/* Just toggle paintmode flag... */
gpd->flag ^= GP_DATA_STROKE_PAINTMODE;
/* set mode */
if (gpd->flag & GP_DATA_STROKE_PAINTMODE) {
mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
else {
mode = OB_MODE_OBJECT;
if ((ob) && (ob->type == OB_GREASE_PENCIL)) {
const bool is_mode_set = (ob->mode & OB_MODE_PAINT_GPENCIL_LEGACY) != 0;
if (!is_mode_set) {
Scene *scene = CTX_data_scene(C);
BKE_paint_init(bmain, scene, PaintMode::GPencil, PAINT_CURSOR_PAINT_GREASE_PENCIL);
Paint *paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::GPencil);
ED_paint_cursor_start(paint, gpencil_paint_poll_view3d);
mode = OB_MODE_PAINT_GPENCIL_LEGACY;
}
else {
mode = OB_MODE_OBJECT;
}
is_object = true;
}
if (is_object) {
/* try to back previous mode */
if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) {
mode = ob->restore_mode;
if (ob->type == OB_GPENCIL_LEGACY) {
bGPdata *gpd = static_cast<bGPdata *>(ob->data);
if ((ob->restore_mode) && ((gpd->flag & GP_DATA_STROKE_PAINTMODE) == 0) && (back == 1)) {
mode = ob->restore_mode;
}
}
if (ob->type == OB_GREASE_PENCIL) {
if ((ob->restore_mode) && ((ob->mode & OB_MODE_PAINT_GPENCIL_LEGACY) == 0) && (back == 1)) {
mode = ob->restore_mode;
}
}
ob->restore_mode = ob->mode;
ob->mode = mode;
@ -406,10 +437,17 @@ static int gpencil_paintmode_toggle_exec(bContext *C, wmOperator *op)
BKE_paint_brush_validate(bmain, &ts->gp_paint->paint);
}
/* setup other modes */
ED_gpencil_setup_modes(C, gpd, mode);
/* set cache as dirty */
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
if (ob->type == OB_GPENCIL_LEGACY) {
bGPdata *gpd = static_cast<bGPdata *>(ob->data);
/* setup other modes */
ED_gpencil_setup_modes(C, gpd, mode);
/* set cache as dirty */
DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
if (ob->type == OB_GREASE_PENCIL) {
GreasePencil *grease_pencil = static_cast<GreasePencil *>(ob->data);
DEG_id_tag_update(&grease_pencil->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
}
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | ND_GPENCIL_EDITMODE, nullptr);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr);
@ -597,6 +635,13 @@ void GPENCIL_OT_sculptmode_toggle(wmOperatorType *ot)
/* Stroke Weight Paint Mode Management */
static bool grease_pencil_poll_weight_cursor(bContext *C)
{
Object *ob = CTX_data_active_object(C);
return ob && (ob->mode & OB_MODE_WEIGHT_GPENCIL_LEGACY) && (ob->type == OB_GREASE_PENCIL) &&
CTX_wm_region_view3d(C) && WM_toolsystem_active_tool_is_brush(C);
}
static bool gpencil_weightmode_toggle_poll(bContext *C)
{
/* if using gpencil object, use this gpd */
@ -610,6 +655,7 @@ static bool gpencil_weightmode_toggle_poll(bContext *C)
static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ToolSettings *ts = CTX_data_tool_settings(C);
const bool back = RNA_boolean_get(op->ptr, "back");
@ -664,12 +710,17 @@ static int gpencil_weightmode_toggle_exec(bContext *C, wmOperator *op)
if (mode == OB_MODE_WEIGHT_GPENCIL_LEGACY) {
/* Be sure we have brushes. */
BKE_paint_ensure(bmain, ts, (Paint **)&ts->gp_weightpaint);
Paint *weight_paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::WeightGPencil);
BKE_paint_ensure(bmain, ts, &weight_paint);
const bool reset_mode = (BKE_paint_brush(&ts->gp_weightpaint->paint) == nullptr);
if (ob->type == OB_GREASE_PENCIL) {
ED_paint_cursor_start(weight_paint, grease_pencil_poll_weight_cursor);
}
const bool reset_mode = (BKE_paint_brush(weight_paint) == nullptr);
BKE_brush_gpencil_weight_presets(bmain, ts, reset_mode);
BKE_paint_brush_validate(bmain, &ts->gp_weightpaint->paint);
BKE_paint_brush_validate(bmain, weight_paint);
}
/* setup other modes */

View File

@ -34,6 +34,7 @@ set(SRC
intern/grease_pencil_select.cc
intern/grease_pencil_undo.cc
intern/grease_pencil_utils.cc
intern/grease_pencil_weight_paint.cc
)
set(LIB

View File

@ -72,7 +72,7 @@ bool grease_pencil_painting_poll(bContext *C)
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_PAINT_GREASE_PENCIL) == 0) {
if ((object->mode & OB_MODE_PAINT_GPENCIL_LEGACY) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
@ -98,6 +98,22 @@ bool grease_pencil_sculpting_poll(bContext *C)
return true;
}
bool grease_pencil_weight_painting_poll(bContext *C)
{
if (!active_grease_pencil_poll(C)) {
return false;
}
Object *object = CTX_data_active_object(C);
if ((object->mode & OB_MODE_WEIGHT_GPENCIL_LEGACY) == 0) {
return false;
}
ToolSettings *ts = CTX_data_tool_settings(C);
if (!ts || !ts->gp_weightpaint) {
return false;
}
return true;
}
static void keymap_grease_pencil_edit_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
@ -119,6 +135,13 @@ static void keymap_grease_pencil_sculpt_mode(wmKeyConfig *keyconf)
keymap->poll = grease_pencil_sculpting_poll;
}
static void keymap_grease_pencil_weight_paint_mode(wmKeyConfig *keyconf)
{
wmKeyMap *keymap = WM_keymap_ensure(
keyconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
keymap->poll = grease_pencil_weight_painting_poll;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil()
@ -130,6 +153,7 @@ void ED_operatortypes_grease_pencil()
ED_operatortypes_grease_pencil_edit();
ED_operatortypes_grease_pencil_material();
ED_operatortypes_grease_pencil_primitives();
ED_operatortypes_grease_pencil_weight_paint();
}
void ED_operatormacros_grease_pencil()
@ -163,5 +187,6 @@ void ED_keymap_grease_pencil(wmKeyConfig *keyconf)
keymap_grease_pencil_edit_mode(keyconf);
keymap_grease_pencil_paint_mode(keyconf);
keymap_grease_pencil_sculpt_mode(keyconf);
keymap_grease_pencil_weight_paint_mode(keyconf);
ED_primitivetool_modal_keymap(keyconf);
}

View File

@ -454,6 +454,91 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &
return editable_drawings;
}
Array<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
const Scene &scene, GreasePencil &grease_pencil)
{
using namespace blender::bke::greasepencil;
int current_frame = scene.r.cfra;
const ToolSettings *toolsettings = scene.toolsettings;
const bool use_multi_frame_editing = (toolsettings->gpencil_flags &
GP_USE_MULTI_FRAME_EDITING) != 0;
const bool use_multi_frame_falloff = use_multi_frame_editing &&
(toolsettings->gp_sculpt.flag &
GP_SCULPT_SETT_FLAG_FRAME_FALLOFF) != 0;
if (use_multi_frame_falloff) {
BKE_curvemapping_init(toolsettings->gp_sculpt.cur_falloff);
}
/* Get a set of unique frame numbers with editable drawings on them. */
VectorSet<int> selected_frames;
int frame_min = current_frame, frame_max = current_frame;
Span<const Layer *> layers = grease_pencil.layers();
if (use_multi_frame_editing) {
for (const int layer_i : layers.index_range()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_editable()) {
continue;
}
for (const auto [frame_number, frame] : layer.frames().items()) {
if (frame_number != current_frame && frame.is_selected()) {
selected_frames.add(frame_number);
frame_min = math::min(frame_min, frame_number);
frame_max = math::max(frame_max, frame_number);
}
}
}
}
selected_frames.add(current_frame);
/* Get multi frame falloff factor per selected frame. */
Array<float> falloff_per_selected_frame(selected_frames.size(), 1.0f);
if (use_multi_frame_falloff) {
int frame_group = 0;
for (const int frame_number : selected_frames) {
falloff_per_selected_frame[frame_group] = get_multi_frame_falloff(
frame_number, current_frame, frame_min, frame_max, toolsettings->gp_sculpt.cur_falloff);
frame_group++;
}
}
/* Get drawings grouped per frame. */
Array<Vector<MutableDrawingInfo>> drawings_grouped_per_frame(selected_frames.size());
Set<int> added_drawings;
for (const int layer_i : layers.index_range()) {
const Layer &layer = *layers[layer_i];
if (!layer.is_editable()) {
continue;
}
/* In multi frame editing mode, add drawings at selected frames. */
if (use_multi_frame_editing) {
for (const auto [frame_number, frame] : layer.frames().items()) {
if (!frame.is_selected() || added_drawings.contains(frame.drawing_index)) {
continue;
}
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, frame_number)) {
const int frame_group = selected_frames.index_of(frame_number);
drawings_grouped_per_frame[frame_group].append(
{*drawing, layer_i, frame_number, falloff_per_selected_frame[frame_group]});
added_drawings.add(frame.drawing_index);
}
}
}
/* Add drawing at current frame. */
const int drawing_index_current_frame = layer.drawing_index_at(current_frame);
if (!added_drawings.contains(drawing_index_current_frame)) {
if (Drawing *drawing = grease_pencil.get_editable_drawing_at(layer, current_frame)) {
const int frame_group = selected_frames.index_of(current_frame);
drawings_grouped_per_frame[frame_group].append(
{*drawing, layer_i, current_frame, falloff_per_selected_frame[frame_group]});
added_drawings.add(drawing_index_current_frame);
}
}
}
return drawings_grouped_per_frame;
}
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene,
GreasePencil &grease_pencil,
@ -688,6 +773,39 @@ IndexMask retrieve_visible_strokes(Object &object,
});
}
IndexMask retrieve_visible_points(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory)
{
/* Get all the hidden material indices. */
VectorSet<int> hidden_material_indices = get_hidden_material_indices(object);
if (hidden_material_indices.is_empty()) {
return drawing.strokes().points_range();
}
const bke::CurvesGeometry &curves = drawing.strokes();
const IndexRange points_range = curves.points_range();
const bke::AttributeAccessor attributes = curves.attributes();
/* Propagate the material index to the points. */
const VArray<int> materials = *attributes.lookup_or_default<int>(
"material_index", bke::AttrDomain::Point, 0);
if (const std::optional<int> single_material = materials.get_if_single()) {
if (!hidden_material_indices.contains(*single_material)) {
return points_range;
}
return {};
}
/* Get all the points that are part of a stroke with a visible material. */
return IndexMask::from_predicate(
points_range, GrainSize(4096), memory, [&](const int64_t point_i) {
const int material_index = materials[point_i];
return !hidden_material_indices.contains(material_index);
});
}
IndexMask retrieve_editable_and_selected_strokes(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory)

View File

@ -0,0 +1,365 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edgreasepencil
*/
#include "BKE_brush.hh"
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_modifier.hh"
#include "BKE_paint.hh"
#include "DNA_meshdata_types.h"
#include "ED_curves.hh"
#include "ED_view3d.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_grease_pencil.hh"
namespace blender::ed::greasepencil {
Set<std::string> get_bone_deformed_vertex_group_names(const Object &object)
{
/* Get all vertex group names in the object. */
const ListBase *defbase = BKE_object_defgroup_list(&object);
Set<std::string> defgroups;
LISTBASE_FOREACH (bDeformGroup *, dg, defbase) {
defgroups.add(dg->name);
}
/* Inspect all armature modifiers in the object. */
Set<std::string> bone_deformed_vgroups;
VirtualModifierData virtual_modifier_data;
ModifierData *md = BKE_modifiers_get_virtual_modifierlist(&object, &virtual_modifier_data);
for (; md; md = md->next) {
if (!(md->mode & (eModifierMode_Realtime | eModifierMode_Virtual)) ||
md->type != eModifierType_Armature)
{
continue;
}
ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
if (!amd->object || !amd->object->pose) {
continue;
}
bPose *pose = amd->object->pose;
LISTBASE_FOREACH (bPoseChannel *, channel, &pose->chanbase) {
if (channel->bone->flag & BONE_NO_DEFORM) {
continue;
}
/* When a vertex group name matches the bone name, it is bone-deformed. */
if (defgroups.contains(channel->name)) {
bone_deformed_vgroups.add(channel->name);
}
}
}
return bone_deformed_vgroups;
}
/* Normalize the weights of vertex groups deformed by bones so that the sum is 1.0f.
* Returns false when the normalization failed due to too many locked vertex groups. In that case a
* second pass can be done with the active vertex group unlocked.
*/
static bool normalize_vertex_weights_try(MDeformVert &dvert,
const int vertex_groups_num,
const Span<bool> vertex_group_is_bone_deformed,
const FunctionRef<bool(int)> vertex_group_is_locked)
{
/* Nothing to normalize when there are less than two vertex group weights. */
if (dvert.totweight <= 1) {
return true;
}
/* Get the sum of weights of bone-deformed vertex groups. */
float sum_weights_total = 0.0f;
float sum_weights_locked = 0.0f;
float sum_weights_unlocked = 0.0f;
int locked_num = 0;
int unlocked_num = 0;
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
/* Auto-normalize is only applied on bone-deformed vertex groups that have weight already. */
if (dw.def_nr >= vertex_groups_num || !vertex_group_is_bone_deformed[dw.def_nr] ||
dw.weight <= FLT_EPSILON)
{
continue;
}
sum_weights_total += dw.weight;
if (vertex_group_is_locked(dw.def_nr)) {
locked_num++;
sum_weights_locked += dw.weight;
}
else {
unlocked_num++;
sum_weights_unlocked += dw.weight;
}
}
/* Already normalized? */
if (sum_weights_total == 1.0f) {
return true;
}
/* Any unlocked vertex group to normalize? */
if (unlocked_num == 0) {
/* We don't need a second pass when there is only one locked group (the active group). */
return (locked_num == 1);
}
/* Locked groups can make it impossible to fully normalize. */
if (sum_weights_locked >= 1.0f - VERTEX_WEIGHT_LOCK_EPSILON) {
/* Zero out the weights we are allowed to touch and return false, indicating a second pass is
* needed. */
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
!vertex_group_is_locked(dw.def_nr))
{
dw.weight = 0.0f;
}
}
return (sum_weights_locked == 1.0f);
}
/* When the sum of the unlocked weights isn't zero, we can use a multiplier to normalize them
* to 1.0f. */
if (sum_weights_unlocked != 0.0f) {
const float normalize_factor = (1.0f - sum_weights_locked) / sum_weights_unlocked;
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
dw.weight > FLT_EPSILON && !vertex_group_is_locked(dw.def_nr))
{
dw.weight = math::clamp(dw.weight * normalize_factor, 0.0f, 1.0f);
}
}
return true;
}
/* Spread out the remainder of the locked weights over the unlocked weights. */
const float weight_remainder = math::clamp(
(1.0f - sum_weights_locked) / unlocked_num, 0.0f, 1.0f);
for (const int i : IndexRange(dvert.totweight)) {
MDeformWeight &dw = dvert.dw[i];
if (dw.def_nr < vertex_groups_num && vertex_group_is_bone_deformed[dw.def_nr] &&
dw.weight > FLT_EPSILON && !vertex_group_is_locked(dw.def_nr))
{
dw.weight = weight_remainder;
}
}
return true;
}
void normalize_vertex_weights(MDeformVert &dvert,
const int active_vertex_group,
const Span<bool> vertex_group_is_locked,
const Span<bool> vertex_group_is_bone_deformed)
{
/* Try to normalize the weights with both active and explicitly locked vertex groups restricted
* from change. */
const auto active_vertex_group_is_locked = [&](const int vertex_group_index) {
return vertex_group_is_locked[vertex_group_index] || vertex_group_index == active_vertex_group;
};
const bool success = normalize_vertex_weights_try(dvert,
vertex_group_is_locked.size(),
vertex_group_is_bone_deformed,
active_vertex_group_is_locked);
if (success) {
return;
}
/* Do a second pass with the active vertex group unlocked. */
const auto active_vertex_group_is_unlocked = [&](const int vertex_group_index) {
return vertex_group_is_locked[vertex_group_index];
};
normalize_vertex_weights_try(dvert,
vertex_group_is_locked.size(),
vertex_group_is_bone_deformed,
active_vertex_group_is_unlocked);
}
struct ClosestGreasePencilDrawing {
const bke::greasepencil::Drawing *drawing = nullptr;
int active_defgroup_index;
ed::curves::FindClosestData elem = {};
};
static int weight_sample_invoke(bContext *C, wmOperator * /*op*/, const wmEvent *event)
{
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
ViewContext vc = ED_view3d_viewcontext_init(C, depsgraph);
/* Get the active vertex group. */
const int object_defgroup_nr = BKE_object_defgroup_active_index_get(vc.obact) - 1;
if (object_defgroup_nr == -1) {
return OPERATOR_CANCELLED;
}
const bDeformGroup *object_defgroup = static_cast<const bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(vc.obact), object_defgroup_nr));
/* Collect visible drawings. */
const Object *ob_eval = DEG_get_evaluated_object(vc.depsgraph, const_cast<Object *>(vc.obact));
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(vc.obact->data);
const Vector<DrawingInfo> drawings = retrieve_visible_drawings(*vc.scene, grease_pencil, false);
/* Find stroke points closest to mouse cursor position. */
const ClosestGreasePencilDrawing closest = threading::parallel_reduce(
drawings.index_range(),
1L,
ClosestGreasePencilDrawing(),
[&](const IndexRange range, const ClosestGreasePencilDrawing &init) {
ClosestGreasePencilDrawing new_closest = init;
for (const int i : range) {
DrawingInfo info = drawings[i];
const bke::greasepencil::Layer &layer = *grease_pencil.layers()[info.layer_index];
/* Skip drawing when it doesn't use the active vertex group. */
const int drawing_defgroup_nr = BLI_findstringindex(
&info.drawing.strokes().vertex_group_names,
object_defgroup->name,
offsetof(bDeformGroup, name));
if (drawing_defgroup_nr == -1) {
continue;
}
/* Get deformation by modifiers. */
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *vc.obact, info.layer_index, info.frame_number);
IndexMaskMemory memory;
const IndexMask points = retrieve_visible_points(*vc.obact, info.drawing, memory);
if (points.is_empty()) {
continue;
}
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(vc.rv3d,
layer_to_world);
std::optional<ed::curves::FindClosestData> new_closest_elem =
ed::curves::closest_elem_find_screen_space(vc,
info.drawing.strokes().points_by_curve(),
deformation.positions,
projection,
points,
bke::AttrDomain::Point,
event->mval,
new_closest.elem);
if (new_closest_elem) {
new_closest.elem = *new_closest_elem;
new_closest.drawing = &info.drawing;
new_closest.active_defgroup_index = drawing_defgroup_nr;
}
}
return new_closest;
},
[](const ClosestGreasePencilDrawing &a, const ClosestGreasePencilDrawing &b) {
return (a.elem.distance < b.elem.distance) ? a : b;
});
if (!closest.drawing) {
return OPERATOR_CANCELLED;
}
/* From the closest point found, get the vertex weight in the active vertex group. */
const VArray<float> point_weights = bke::varray_for_deform_verts(
closest.drawing->strokes().deform_verts(), closest.active_defgroup_index);
const float new_weight = math::clamp(point_weights[closest.elem.index], 0.0f, 1.0f);
/* Set the new brush weight. */
const ToolSettings *ts = vc.scene->toolsettings;
Brush *brush = BKE_paint_brush(&ts->wpaint->paint);
BKE_brush_weight_set(vc.scene, brush, new_weight);
/* Update brush settings in UI. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_weight_sample(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Weight Paint Sample Weight";
ot->idname = "GREASE_PENCIL_OT_weight_sample";
ot->description =
"Set the weight of the Draw tool to the weight of the vertex under the mouse cursor";
/* Callbacks. */
ot->poll = grease_pencil_weight_painting_poll;
ot->invoke = weight_sample_invoke;
/* Flags. */
ot->flag = OPTYPE_UNDO | OPTYPE_DEPENDS_ON_CURSOR;
}
static int toggle_weight_tool_direction(bContext *C, wmOperator * /*op*/)
{
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
/* Toggle direction flag. */
brush->flag ^= BRUSH_DIR_IN;
/* Update brush settings in UI. */
WM_main_add_notifier(NC_BRUSH | NA_EDITED, nullptr);
return OPERATOR_FINISHED;
}
static bool toggle_weight_tool_direction_poll(bContext *C)
{
if (!grease_pencil_weight_painting_poll(C)) {
return false;
}
Paint *paint = BKE_paint_get_active_from_context(C);
if (paint == nullptr) {
return false;
}
Brush *brush = BKE_paint_brush(paint);
if (brush == nullptr) {
return false;
}
return brush->gpencil_weight_tool == GPWEIGHT_TOOL_DRAW;
}
static void GREASE_PENCIL_OT_weight_toggle_direction(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Weight Paint Toggle Direction";
ot->idname = "GREASE_PENCIL_OT_weight_toggle_direction";
ot->description = "Toggle Add/Subtract for the weight paint draw tool";
/* Callbacks. */
ot->poll = toggle_weight_tool_direction_poll;
ot->exec = toggle_weight_tool_direction;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
} // namespace blender::ed::greasepencil
void ED_operatortypes_grease_pencil_weight_paint()
{
using namespace blender::ed::greasepencil;
WM_operatortype_append(GREASE_PENCIL_OT_weight_toggle_direction);
WM_operatortype_append(GREASE_PENCIL_OT_weight_sample);
}

View File

@ -13,6 +13,7 @@
#include "BLI_generic_span.hh"
#include "BLI_index_mask_fwd.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_set.hh"
#include "ED_keyframes_edit.hh"
@ -53,6 +54,7 @@ void ED_operatortypes_grease_pencil_select();
void ED_operatortypes_grease_pencil_edit();
void ED_operatortypes_grease_pencil_material();
void ED_operatortypes_grease_pencil_primitives();
void ED_operatortypes_grease_pencil_weight_paint();
void ED_operatormacros_grease_pencil();
void ED_keymap_grease_pencil(wmKeyConfig *keyconf);
void ED_primitivetool_modal_keymap(wmKeyConfig *keyconf);
@ -216,6 +218,7 @@ bool active_grease_pencil_layer_poll(bContext *C);
bool editable_grease_pencil_point_selection_poll(bContext *C);
bool grease_pencil_painting_poll(bContext *C);
bool grease_pencil_sculpting_poll(bContext *C);
bool grease_pencil_weight_painting_poll(bContext *C);
float opacity_from_input_sample(const float pressure,
const Brush *brush,
@ -249,6 +252,8 @@ Vector<MutableDrawingInfo> retrieve_editable_drawings(const Scene &scene,
GreasePencil &grease_pencil);
Vector<MutableDrawingInfo> retrieve_editable_drawings_with_falloff(const Scene &scene,
GreasePencil &grease_pencil);
Array<Vector<MutableDrawingInfo>> retrieve_editable_drawings_grouped_per_frame(
const Scene &scene, GreasePencil &grease_pencil);
Vector<MutableDrawingInfo> retrieve_editable_drawings_from_layer(
const Scene &scene, GreasePencil &grease_pencil, const bke::greasepencil::Layer &layer);
Vector<DrawingInfo> retrieve_visible_drawings(const Scene &scene,
@ -273,6 +278,9 @@ IndexMask retrieve_editable_elements(Object &object,
IndexMask retrieve_visible_strokes(Object &grease_pencil_object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory);
IndexMask retrieve_visible_points(Object &object,
const bke::greasepencil::Drawing &drawing,
IndexMaskMemory &memory);
IndexMask retrieve_editable_and_selected_strokes(Object &grease_pencil_object,
const bke::greasepencil::Drawing &drawing,
@ -363,4 +371,14 @@ Array<PointTransferData> compute_topology_change(
const Span<Vector<PointTransferData>> src_to_dst_points,
const bool keep_caps);
/** Returns a set of vertex group names that are deformed by a bone in an armature. */
Set<std::string> get_bone_deformed_vertex_group_names(const Object &object);
/** For a point in a stroke, normalize the weights of vertex groups deformed by bones so that the
* sum is 1.0f. */
void normalize_vertex_weights(MDeformVert &dvert,
int active_vertex_group,
Span<bool> vertex_group_is_locked,
Span<bool> vertex_group_is_bone_deformed);
} // namespace blender::ed::greasepencil

View File

@ -200,6 +200,13 @@ struct ImageFrameRange {
/* Temporary data. */
ListBase frames;
/** Sequences filename head. */
char filename_head[FILE_MAX];
/** Sequences digits size. */
unsigned short filename_digits;
/** Sequences filename tail. */
char filename_tail[FILE_MAX];
};
/**

View File

@ -2624,7 +2624,6 @@ int UI_icon_from_object_mode(const int mode)
return ICON_PARTICLEMODE;
case OB_MODE_POSE:
return ICON_POSE_HLT;
case OB_MODE_PAINT_GREASE_PENCIL:
case OB_MODE_PAINT_GPENCIL_LEGACY:
return ICON_GREASEPENCIL;
}

View File

@ -5487,9 +5487,9 @@ bool UI_block_apply_search_filter(uiBlock *block, const char *search_filter)
Panel *panel = block->panel;
if (panel != nullptr && panel->type->flag & PANEL_TYPE_NO_SEARCH) {
/* Panels for active blocks should always have a type, otherwise they wouldn't be created. */
BLI_assert(block->panel->type != nullptr);
if (panel != nullptr) {
/* Panels for active blocks should always have a valid `panel->type`,
* otherwise they wouldn't be created. */
if (panel->type->flag & PANEL_TYPE_NO_SEARCH) {
return false;
}

View File

@ -578,6 +578,8 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const eUSDAttrImportMode attr_import_mode = eUSDAttrImportMode(
RNA_enum_get(op->ptr, "attr_import_mode"));
const bool validate_meshes = RNA_boolean_get(op->ptr, "validate_meshes");
/* TODO(makowalski): Add support for sequences. */
const bool is_sequence = false;
int offset = 0;
@ -589,7 +591,6 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
blender::ed::object::mode_set(C, OB_MODE_EDIT);
}
const bool validate_meshes = false;
const bool use_instancing = false;
const eUSDTexImportMode import_textures_mode = eUSDTexImportMode(
@ -674,6 +675,7 @@ static void wm_usd_import_draw(bContext * /*C*/, wmOperator *op)
uiItemR(col, ptr, "read_mesh_uvs", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "read_mesh_colors", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "read_mesh_attributes", UI_ITEM_NONE, nullptr, ICON_NONE);
uiItemR(col, ptr, "validate_meshes", UI_ITEM_NONE, nullptr, ICON_NONE);
col = uiLayoutColumnWithHeading(box, true, IFACE_("Include"));
uiItemR(col, ptr, "import_subdiv", UI_ITEM_NONE, IFACE_("Subdivision"), ICON_NONE);
uiItemR(col, ptr, "support_scene_instancing", UI_ITEM_NONE, nullptr, ICON_NONE);
@ -884,6 +886,12 @@ void WM_OT_usd_import(wmOperatorType *ot)
USD_ATTR_IMPORT_ALL,
"Import Custom Properties",
"Behavior when importing USD attributes as Blender custom properties");
RNA_def_boolean(ot->srna,
"validate_meshes",
false,
"Validate Meshes",
"Check imported mesh objects for invalid data (slow)");
}
namespace blender::ed::io {

View File

@ -82,9 +82,6 @@ static const char *object_mode_op_string(eObjectMode mode)
if (mode == OB_MODE_EDIT_GPENCIL_LEGACY) {
return "GPENCIL_OT_editmode_toggle";
}
if (mode == OB_MODE_PAINT_GREASE_PENCIL) {
return "GREASE_PENCIL_OT_draw_mode_toggle";
}
if (mode == OB_MODE_PAINT_GPENCIL_LEGACY) {
return "GPENCIL_OT_paintmode_toggle";
}
@ -148,7 +145,7 @@ bool mode_compat_test(const Object *ob, eObjectMode mode)
}
break;
case OB_GREASE_PENCIL:
if (mode & (OB_MODE_EDIT | OB_MODE_PAINT_GREASE_PENCIL | OB_MODE_SCULPT_GPENCIL_LEGACY |
if (mode & (OB_MODE_EDIT | OB_MODE_PAINT_GPENCIL_LEGACY | OB_MODE_SCULPT_GPENCIL_LEGACY |
OB_MODE_WEIGHT_GPENCIL_LEGACY))
{
return true;
@ -307,10 +304,17 @@ static bool ed_object_mode_generic_exit_ex(
}
ED_object_gpencil_exit(bmain, ob);
}
else if (ob->mode & OB_MODE_PAINT_GREASE_PENCIL) {
ob->mode &= ~OB_MODE_PAINT_GREASE_PENCIL;
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY | ID_RECALC_SYNC_TO_EVAL);
WM_main_add_notifier(NC_SCENE | ND_MODE | NS_MODE_OBJECT, nullptr);
else if (ob->type == OB_GREASE_PENCIL) {
BLI_assert((ob->mode & OB_MODE_OBJECT) == 0);
if (only_test) {
return true;
}
ob->restore_mode = ob->mode;
ob->mode &= ~(OB_MODE_PAINT_GPENCIL_LEGACY | OB_MODE_EDIT | OB_MODE_SCULPT_GPENCIL_LEGACY |
OB_MODE_WEIGHT_GPENCIL_LEGACY | OB_MODE_VERTEX_GPENCIL_LEGACY);
/* Inform all evaluated versions that we changed the mode. */
DEG_id_tag_update_ex(bmain, &ob->id, ID_RECALC_SYNC_TO_EVAL);
}
else {
if (only_test) {

View File

@ -48,6 +48,10 @@ set(SRC
grease_pencil_erase.cc
grease_pencil_paint.cc
grease_pencil_tint.cc
grease_pencil_weight_average.cc
grease_pencil_weight_blur.cc
grease_pencil_weight_draw.cc
grease_pencil_weight_smear.cc
paint_canvas.cc
paint_cursor.cc
paint_curve.cc
@ -97,6 +101,7 @@ set(SRC
curves_sculpt_intern.hh
grease_pencil_intern.hh
grease_pencil_weight_paint.hh
paint_intern.hh
sculpt_intern.hh
)

View File

@ -3,7 +3,9 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_context.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil.hh"
#include "BKE_object_deform.h"
#include "BKE_report.hh"
#include "DEG_depsgraph_query.hh"
@ -326,86 +328,130 @@ static void GREASE_PENCIL_OT_sculpt_paint(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Toggle Draw Mode
/** \name Weight Brush Stroke Operator
* \{ */
static bool grease_pencil_mode_poll_paint_cursor(bContext *C)
static bool weight_stroke_test_start(bContext *C, wmOperator *op, const float mouse[2])
{
if (!grease_pencil_brush_stroke_poll(C)) {
InputSample start_sample;
start_sample.mouse_position = float2(mouse);
start_sample.pressure = 0.0f;
GreasePencilStrokeOperation *operation = nullptr;
Paint *paint = BKE_paint_get_active_from_context(C);
Brush *brush = BKE_paint_brush(paint);
const BrushStrokeMode brush_mode = BrushStrokeMode(RNA_enum_get(op->ptr, "mode"));
switch (eBrushGPWeightTool(brush->gpencil_weight_tool)) {
case GPWEIGHT_TOOL_DRAW:
operation = greasepencil::new_weight_paint_draw_operation(brush_mode).release();
break;
case GPWEIGHT_TOOL_BLUR:
operation = greasepencil::new_weight_paint_blur_operation().release();
break;
case GPWEIGHT_TOOL_AVERAGE:
operation = greasepencil::new_weight_paint_average_operation().release();
break;
case GPWEIGHT_TOOL_SMEAR:
operation = greasepencil::new_weight_paint_smear_operation().release();
break;
}
if (operation == nullptr) {
return false;
}
if (CTX_wm_region_view3d(C) == nullptr) {
PaintStroke *paint_stroke = static_cast<PaintStroke *>(op->customdata);
paint_stroke_set_mode_data(paint_stroke, operation);
operation->on_stroke_begin(*C, start_sample);
return true;
}
static int grease_pencil_weight_brush_stroke_invoke(bContext *C,
wmOperator *op,
const wmEvent *event)
{
const Scene *scene = CTX_data_scene(C);
const Object *object = CTX_data_active_object(C);
if (!object || object->type != OB_GREASE_PENCIL) {
return OPERATOR_CANCELLED;
}
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(object->data);
const Paint *paint = BKE_paint_get_active_from_context(C);
const Brush *brush = BKE_paint_brush_for_read(paint);
if (brush == nullptr) {
return OPERATOR_CANCELLED;
}
const Vector<ed::greasepencil::MutableDrawingInfo> drawings =
ed::greasepencil::retrieve_editable_drawings(*scene, grease_pencil);
if (drawings.is_empty()) {
BKE_report(op->reports, RPT_ERROR, "No Grease Pencil frame to draw weight on");
return OPERATOR_CANCELLED;
}
const int active_defgroup_nr = BKE_object_defgroup_active_index_get(object) - 1;
if (active_defgroup_nr >= 0 && BKE_object_defgroup_active_is_locked(object)) {
BKE_report(op->reports, RPT_WARNING, "Active group is locked, aborting");
return OPERATOR_CANCELLED;
}
op->customdata = paint_stroke_new(C,
op,
stroke_get_location,
weight_stroke_test_start,
stroke_update_step,
stroke_redraw,
stroke_done,
event->type);
const int return_value = op->type->modal(C, op, event);
if (return_value == OPERATOR_FINISHED) {
return OPERATOR_FINISHED;
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
static int grease_pencil_weight_brush_stroke_modal(bContext *C,
wmOperator *op,
const wmEvent *event)
{
return paint_stroke_modal(C, op, event, reinterpret_cast<PaintStroke **>(&op->customdata));
}
static void grease_pencil_weight_brush_stroke_cancel(bContext *C, wmOperator *op)
{
paint_stroke_cancel(C, op, static_cast<PaintStroke *>(op->customdata));
}
static bool grease_pencil_weight_brush_stroke_poll(bContext *C)
{
if (!ed::greasepencil::grease_pencil_weight_painting_poll(C)) {
return false;
}
if (!WM_toolsystem_active_tool_is_brush(C)) {
return false;
}
return true;
}
static void grease_pencil_draw_mode_enter(bContext *C)
static void GREASE_PENCIL_OT_weight_brush_stroke(wmOperatorType *ot)
{
Scene *scene = CTX_data_scene(C);
wmMsgBus *mbus = CTX_wm_message_bus(C);
ot->name = "Grease Pencil Paint Weight";
ot->idname = "GREASE_PENCIL_OT_weight_brush_stroke";
ot->description = "Draw weight on stroke points in the active Grease Pencil object";
Object *ob = CTX_data_active_object(C);
GpPaint *grease_pencil_paint = scene->toolsettings->gp_paint;
BKE_paint_ensure(CTX_data_main(C), scene->toolsettings, (Paint **)&grease_pencil_paint);
ot->poll = grease_pencil_weight_brush_stroke_poll;
ot->invoke = grease_pencil_weight_brush_stroke_invoke;
ot->modal = grease_pencil_weight_brush_stroke_modal;
ot->cancel = grease_pencil_weight_brush_stroke_cancel;
ob->mode = OB_MODE_PAINT_GREASE_PENCIL;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* TODO: Setup cursor color. BKE_paint_init() could be used, but creates an additional brush. */
ED_paint_cursor_start(&grease_pencil_paint->paint, grease_pencil_mode_poll_paint_cursor);
paint_init_pivot(ob, scene);
/* Necessary to change the object mode on the evaluated object. */
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr);
}
static void grease_pencil_draw_mode_exit(bContext *C)
{
Object *ob = CTX_data_active_object(C);
ob->mode = OB_MODE_OBJECT;
}
static int grease_pencil_draw_mode_toggle_exec(bContext *C, wmOperator *op)
{
Object *ob = CTX_data_active_object(C);
wmMsgBus *mbus = CTX_wm_message_bus(C);
const bool is_mode_set = ob->mode == OB_MODE_PAINT_GREASE_PENCIL;
if (is_mode_set) {
if (!object::mode_compat_set(C, ob, OB_MODE_PAINT_GREASE_PENCIL, op->reports)) {
return OPERATOR_CANCELLED;
}
}
if (is_mode_set) {
grease_pencil_draw_mode_exit(C);
}
else {
grease_pencil_draw_mode_enter(C);
}
WM_toolsystem_update_from_context_view3d(C);
/* Necessary to change the object mode on the evaluated object. */
DEG_id_tag_update(&ob->id, ID_RECALC_SYNC_TO_EVAL);
WM_msg_publish_rna_prop(mbus, &ob->id, ob, Object, mode);
WM_event_add_notifier(C, NC_SCENE | ND_MODE, nullptr);
return OPERATOR_FINISHED;
}
static void GREASE_PENCIL_OT_draw_mode_toggle(wmOperatorType *ot)
{
ot->name = "Grease Pencil Draw Mode Toggle";
ot->idname = "GREASE_PENCIL_OT_draw_mode_toggle";
ot->description = "Enter/Exit draw mode for grease pencil";
ot->exec = grease_pencil_draw_mode_toggle_exec;
ot->poll = ed::greasepencil::active_grease_pencil_poll;
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
paint_stroke_operator_properties(ot);
}
/** \} */
@ -421,7 +467,7 @@ void ED_operatortypes_grease_pencil_draw()
using namespace blender::ed::sculpt_paint;
WM_operatortype_append(GREASE_PENCIL_OT_brush_stroke);
WM_operatortype_append(GREASE_PENCIL_OT_sculpt_paint);
WM_operatortype_append(GREASE_PENCIL_OT_draw_mode_toggle);
WM_operatortype_append(GREASE_PENCIL_OT_weight_brush_stroke);
}
/** \} */

View File

@ -28,6 +28,11 @@ namespace greasepencil {
std::unique_ptr<GreasePencilStrokeOperation> new_paint_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_erase_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_tint_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
const BrushStrokeMode &brush_mode);
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation();
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation();
} // namespace greasepencil

View File

@ -0,0 +1,116 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class AverageWeightPaintOperation : public WeightPaintOperation {
/* Get the average weight of all points in the brush buffer. */
float get_average_weight_in_brush_buffer(const Span<DrawingWeightData> drawing_weights)
{
float average_sum = 0.0f;
float point_num = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
average_sum += drawing_weight.deform_weights[point.drawing_point_index];
point_num++;
}
}
if (point_num == 0) {
return 0.0f;
}
return math::clamp(average_sum / point_num, 0.0f, 1.0f);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Average on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and average them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Get the average weight of the points in the brush buffer. */
const float average_weight = this->get_average_weight_in_brush_buffer(drawing_weights);
/* Apply the Average tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_weight_to_point(point, average_weight, drawing_weight);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_average_operation()
{
return std::make_unique<AverageWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,139 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class BlurWeightPaintOperation : public WeightPaintOperation {
/* Apply the Blur tool to a point under the brush. */
void apply_blur_tool(const BrushPoint &point,
DrawingWeightData &drawing_weight,
PointsTouchedByBrush &touched_points)
{
/* Find the nearest neighbours of the to-be-blurred point. The point itself is included. */
KDTreeNearest_2d nearest_points[BLUR_NEIGHBOUR_NUM];
const int point_num = BLI_kdtree_2d_find_nearest_n(
touched_points.kdtree,
drawing_weight.point_positions[point.drawing_point_index],
nearest_points,
BLUR_NEIGHBOUR_NUM);
if (point_num <= 1) {
return;
}
/* Calculate the blurred weight for the point (A). For this we use a weighted average of the
* point weights, based on the distance of the neighbour point to A. So points closer to A
* contribute more to the average than points farther away from A. */
float distance_sum = 0.0f;
for (const int i : IndexRange(point_num)) {
distance_sum += nearest_points[i].dist;
}
if (distance_sum == 0.0f) {
return;
}
float blur_weight_sum = 0.0f;
for (const int i : IndexRange(point_num)) {
blur_weight_sum += (1.0f - nearest_points[i].dist / distance_sum) *
touched_points.weights[nearest_points[i].index];
}
const float blur_weight = blur_weight_sum / (point_num - 1);
apply_weight_to_point(point, blur_weight, drawing_weight);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Blur on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample, 1.3f);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and blur them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Create a KDTree with all stroke points touched by the brush during the weight paint
* operation. */
PointsTouchedByBrush touched_points = this->create_affected_points_kdtree(
drawing_weights);
/* Apply the Blur tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_blur_tool(point, drawing_weight, touched_points);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
BLI_kdtree_2d_free(touched_points.kdtree);
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_blur_operation()
{
return std::make_unique<BlurWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,106 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class DrawWeightPaintOperation : public WeightPaintOperation {
public:
DrawWeightPaintOperation(const BrushStrokeMode &brush_mode)
{
this->brush_mode = brush_mode;
}
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get the add/subtract mode of the draw tool. */
this->invert_brush_weight = (this->brush->flag & BRUSH_DIR_IN) != 0;
if (this->brush_mode == BRUSH_STROKE_INVERT) {
this->invert_brush_weight = !this->invert_brush_weight;
}
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and draw weight on them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Apply the Draw tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_weight_to_point(point, this->brush_weight, drawing_weight);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_draw_operation(
const BrushStrokeMode &brush_mode)
{
return std::make_unique<DrawWeightPaintOperation>(brush_mode);
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,315 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BKE_brush.hh"
#include "BKE_colortools.hh"
#include "BKE_context.hh"
#include "BKE_crazyspace.hh"
#include "BKE_curves.hh"
#include "BKE_deform.hh"
#include "BKE_grease_pencil_vertex_groups.hh"
#include "BKE_modifier.hh"
#include "BKE_object_deform.h"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
#include "BLI_kdtree.h"
#include "DNA_meshdata_types.h"
#include "ED_grease_pencil.hh"
#include "ED_view3d.hh"
#include "grease_pencil_intern.hh"
namespace blender::ed::sculpt_paint::greasepencil {
static constexpr float FIND_NEAREST_POINT_EPSILON = 1e-6f;
static constexpr int BLUR_NEIGHBOUR_NUM = 5;
static constexpr int SMEAR_NEIGHBOUR_NUM = 8;
class WeightPaintOperation : public GreasePencilStrokeOperation {
public:
struct BrushPoint {
float influence;
int drawing_point_index;
};
struct DrawingWeightData {
int active_vertex_group;
MutableSpan<MDeformVert> deform_verts;
VMutableArray<float> deform_weights;
float multi_frame_falloff;
Vector<bool> locked_vgroups;
Vector<bool> bone_deformed_vgroups;
Array<float2> point_positions;
/* Flag for all stroke points in a drawing: true when the point was touched by the brush during
* a #GreasePencilStrokeOperation. */
Array<bool> points_touched_by_brush;
int points_touched_by_brush_num;
/* Collected points under the brush in one #on_stroke_extended action. */
Vector<BrushPoint> points_in_brush;
};
struct PointsTouchedByBrush {
KDTree_2d *kdtree;
Array<float> weights;
};
Object *object;
GreasePencil *grease_pencil;
Brush *brush;
float initial_brush_radius;
float brush_radius;
float brush_radius_wide;
float initial_brush_strength;
float brush_strength;
float brush_weight;
float2 mouse_position;
float2 mouse_position_previous;
rctf brush_bbox;
/* Flag for Auto-normalize weights of bone deformed vertex groups. */
bool auto_normalize;
/* Brush mode: normal, invert or smooth. */
BrushStrokeMode brush_mode;
/* Add or subtract weight? */
bool invert_brush_weight;
/* Active vertex group in GP object. */
bDeformGroup *object_defgroup;
/* Weight paint data per editable drawing. Stored per frame group. */
Array<Array<DrawingWeightData>> drawing_weight_data;
/* Set of bone-deformed vertex groups (object level). */
Set<std::string> object_bone_deformed_defgroups;
/* Set of locked vertex groups (object level). */
Set<std::string> object_locked_defgroups;
~WeightPaintOperation() override {}
/* Apply a weight to a point under the brush. */
void apply_weight_to_point(const BrushPoint &point,
const float target_weight,
DrawingWeightData &drawing_weight)
{
/* Blend the current point weight with the target weight. */
const float old_weight = drawing_weight.deform_weights[point.drawing_point_index];
const float weight_delta = (this->invert_brush_weight ? (1.0f - target_weight) :
target_weight) -
old_weight;
drawing_weight.deform_weights.set(
point.drawing_point_index,
math::clamp(
old_weight + math::interpolate(0.0f, weight_delta, point.influence), 0.0f, 1.0f));
}
/* Get brush settings (radius, strength etc.) */
void get_brush_settings(const bContext &C, const InputSample &start_sample)
{
using namespace blender::ed::greasepencil;
const Scene *scene = CTX_data_scene(&C);
this->object = CTX_data_active_object(&C);
this->grease_pencil = static_cast<GreasePencil *>(this->object->data);
Paint *paint = BKE_paint_get_active_from_context(&C);
Brush *brush = BKE_paint_brush(paint);
this->brush = brush;
this->initial_brush_radius = BKE_brush_size_get(scene, brush);
this->initial_brush_strength = BKE_brush_alpha_get(scene, brush);
this->brush_weight = BKE_brush_weight_get(scene, brush);
this->mouse_position_previous = start_sample.mouse_position;
this->invert_brush_weight = false;
BKE_curvemapping_init(brush->curve);
/* Auto-normalize weights is only applied when the object is deformed by an armature. */
const ToolSettings *ts = CTX_data_tool_settings(&C);
this->auto_normalize = ts->auto_normalize &&
(BKE_modifiers_is_deformed_by_armature(this->object) != nullptr);
}
/* Get or create active vertex group in GP object. */
void ensure_active_vertex_group_in_object()
{
int object_defgroup_nr = BKE_object_defgroup_active_index_get(this->object) - 1;
if (object_defgroup_nr == -1) {
BKE_object_defgroup_add(this->object);
object_defgroup_nr = 0;
}
this->object_defgroup = static_cast<bDeformGroup *>(
BLI_findlink(BKE_object_defgroup_list(this->object), object_defgroup_nr));
}
/* Get locked and bone-deformed vertex groups in GP object. */
void get_locked_and_bone_deformed_vertex_groups()
{
const ListBase *defgroups = BKE_object_defgroup_list(this->object);
LISTBASE_FOREACH (bDeformGroup *, dg, defgroups) {
if ((dg->flag & DG_LOCK_WEIGHT) != 0) {
this->object_locked_defgroups.add(dg->name);
}
}
this->object_bone_deformed_defgroups = ed::greasepencil::get_bone_deformed_vertex_group_names(
*this->object);
}
/* For each drawing, retrieve pointers to the vertex weight data of the active vertex group,
* so that we can read and write to them later. And create buffers for points under the brush
* during one #on_stroke_extended action. */
void init_weight_data_for_drawings(const bContext &C,
const Span<ed::greasepencil::MutableDrawingInfo> &drawings,
const int frame_group)
{
const Depsgraph *depsgraph = CTX_data_depsgraph_pointer(&C);
const Object *ob_eval = DEG_get_evaluated_object(depsgraph, this->object);
const RegionView3D *rv3d = CTX_wm_region_view3d(&C);
const ARegion *region = CTX_wm_region(&C);
this->drawing_weight_data[frame_group].reinitialize(drawings.size());
threading::parallel_for(drawings.index_range(), 1, [&](const IndexRange range) {
for (const int drawing_index : range) {
const ed::greasepencil::MutableDrawingInfo &drawing_info = drawings[drawing_index];
bke::CurvesGeometry &curves = drawing_info.drawing.strokes_for_write();
/* Find or create the active vertex group in the drawing. */
DrawingWeightData &drawing_weight_data =
this->drawing_weight_data[frame_group][drawing_index];
drawing_weight_data.active_vertex_group = bke::greasepencil::ensure_vertex_group(
this->object_defgroup->name, curves.vertex_group_names);
drawing_weight_data.multi_frame_falloff = drawing_info.multi_frame_falloff;
drawing_weight_data.deform_verts = curves.deform_verts_for_write();
drawing_weight_data.deform_weights = bke::varray_for_mutable_deform_verts(
drawing_weight_data.deform_verts, drawing_weight_data.active_vertex_group);
/* Create boolean arrays indicating whether a vertex group is locked/bone deformed
* or not. */
if (this->auto_normalize) {
LISTBASE_FOREACH (bDeformGroup *, dg, &curves.vertex_group_names) {
drawing_weight_data.locked_vgroups.append(
this->object_locked_defgroups.contains(dg->name));
drawing_weight_data.bone_deformed_vgroups.append(
this->object_bone_deformed_defgroups.contains(dg->name));
}
}
/* Convert stroke points to screen space positions. */
const bke::greasepencil::Layer &layer =
*this->grease_pencil->layers()[drawing_info.layer_index];
const float4x4 layer_to_world = layer.to_world_space(*ob_eval);
const float4x4 projection = ED_view3d_ob_project_mat_get_from_obmat(rv3d, layer_to_world);
bke::crazyspace::GeometryDeformation deformation =
bke::crazyspace::get_evaluated_grease_pencil_drawing_deformation(
ob_eval, *this->object, drawing_info.layer_index, drawing_info.frame_number);
drawing_weight_data.point_positions.reinitialize(deformation.positions.size());
threading::parallel_for(curves.points_range(), 1024, [&](const IndexRange point_range) {
for (const int point : point_range) {
drawing_weight_data.point_positions[point] = ED_view3d_project_float_v2_m4(
region, deformation.positions[point], projection);
}
});
/* Initialize the flag for stroke points being touched by the brush. */
drawing_weight_data.points_touched_by_brush_num = 0;
drawing_weight_data.points_touched_by_brush = Array<bool>(deformation.positions.size(),
false);
}
});
}
/* Get mouse position and pressure. */
void get_mouse_input_sample(const InputSample &input_sample,
const float brush_widen_factor = 1.0f)
{
this->mouse_position = input_sample.mouse_position;
this->brush_radius = this->initial_brush_radius;
if (BKE_brush_use_size_pressure(this->brush)) {
this->brush_radius *= input_sample.pressure;
}
this->brush_strength = this->initial_brush_strength;
if (BKE_brush_use_alpha_pressure(this->brush)) {
this->brush_strength *= input_sample.pressure;
}
this->brush_radius_wide = this->brush_radius * brush_widen_factor;
BLI_rctf_init(&this->brush_bbox,
this->mouse_position.x - this->brush_radius_wide,
this->mouse_position.x + this->brush_radius_wide,
this->mouse_position.y - this->brush_radius_wide,
this->mouse_position.y + this->brush_radius_wide);
}
/* Add a point to the brush buffer when it is within the brush radius. */
void add_point_under_brush_to_brush_buffer(const float2 point_position,
DrawingWeightData &drawing_weight,
const int point_index)
{
if (!BLI_rctf_isect_pt_v(&this->brush_bbox, point_position)) {
return;
}
const float dist_point_to_brush_center = math::distance(point_position, this->mouse_position);
if (dist_point_to_brush_center > this->brush_radius_wide) {
return;
}
/* Point is touched by the (wide) brush, set flag for that. */
if (!drawing_weight.points_touched_by_brush[point_index]) {
drawing_weight.points_touched_by_brush_num++;
}
drawing_weight.points_touched_by_brush[point_index] = true;
if (dist_point_to_brush_center > this->brush_radius) {
return;
}
/* When the point is under the brush, add it to the brush buffer. */
const float influence = drawing_weight.multi_frame_falloff * this->brush_strength *
BKE_brush_curve_strength(
this->brush, dist_point_to_brush_center, this->brush_radius);
if (influence != 0.0f) {
drawing_weight.points_in_brush.append({influence, point_index});
}
}
/* Create KDTree for all stroke points touched by the brush during a weight paint operation. */
PointsTouchedByBrush create_affected_points_kdtree(const Span<DrawingWeightData> drawing_weights)
{
/* Get number of stroke points touched by the brush. */
int point_num = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
point_num += drawing_weight.points_touched_by_brush_num;
}
/* Create KDTree of stroke points touched by the brush. */
KDTree_2d *touched_points = BLI_kdtree_2d_new(point_num);
Array<float> touched_points_weights(point_num);
int kdtree_index = 0;
for (const DrawingWeightData &drawing_weight : drawing_weights) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
if (drawing_weight.points_touched_by_brush[point_index]) {
BLI_kdtree_2d_insert(
touched_points, kdtree_index, drawing_weight.point_positions[point_index]);
touched_points_weights[kdtree_index] = drawing_weight.deform_weights[point_index];
kdtree_index++;
}
}
}
BLI_kdtree_2d_balance(touched_points);
return {touched_points, touched_points_weights};
}
};
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "grease_pencil_weight_paint.hh"
namespace blender::ed::sculpt_paint::greasepencil {
class SmearWeightPaintOperation : public WeightPaintOperation {
/* Brush direction (angle) during a stroke movement. */
float2 brush_direction;
bool brush_direction_is_set;
/** Get the direction of the brush while the mouse is moving. The direction is given as a
* normalized XY vector. */
bool get_brush_direction()
{
this->brush_direction = this->mouse_position - this->mouse_position_previous;
/* Skip tiny changes in direction, we want the bigger movements only. */
if (math::length_squared(this->brush_direction) < 9.0f) {
return this->brush_direction_is_set;
}
this->brush_direction = math::normalize(this->brush_direction);
this->brush_direction_is_set = true;
this->mouse_position_previous = this->mouse_position;
return true;
}
/** Apply the Smear tool to a point under the brush. */
void apply_smear_tool(const BrushPoint &point,
DrawingWeightData &drawing_weight,
PointsTouchedByBrush &touched_points)
{
/* Find the nearest neighbours of the to-be-smeared point. */
KDTreeNearest_2d nearest_points[SMEAR_NEIGHBOUR_NUM];
const int point_num = BLI_kdtree_2d_find_nearest_n(
touched_points.kdtree,
drawing_weight.point_positions[point.drawing_point_index],
nearest_points,
SMEAR_NEIGHBOUR_NUM);
/* For smearing a weight to point A, we look for a point B in the trail of the mouse
* movement, matching the last known brush angle best and with the shortest distance to A. */
float point_dot_product[SMEAR_NEIGHBOUR_NUM];
float min_distance = FLT_MAX, max_distance = -FLT_MAX;
int smear_point_num = 0;
for (const int i : IndexRange(point_num)) {
/* Skip the point we are about to smear. */
if (nearest_points[i].dist < FIND_NEAREST_POINT_EPSILON) {
continue;
}
const float2 direction_nearest_to_point = math::normalize(
drawing_weight.point_positions[point.drawing_point_index] -
float2(nearest_points[i].co));
/* Match point direction with brush direction. */
point_dot_product[i] = math::dot(direction_nearest_to_point, this->brush_direction);
if (point_dot_product[i] <= 0.0f) {
continue;
}
smear_point_num++;
min_distance = math::min(min_distance, nearest_points[i].dist);
max_distance = math::max(max_distance, nearest_points[i].dist);
}
if (smear_point_num == 0) {
return;
}
/* Find best match in angle and distance. */
int best_match = -1;
float max_score = 0.0f;
const float distance_normalizer = (min_distance == max_distance) ?
1.0f :
(0.95f / (max_distance - min_distance));
for (const int i : IndexRange(point_num)) {
if (point_dot_product[i] <= 0.0f) {
continue;
}
const float score = point_dot_product[i] *
(1.0f - (nearest_points[i].dist - min_distance) * distance_normalizer);
if (score > max_score) {
max_score = score;
best_match = i;
}
}
if (best_match == -1) {
return;
}
const float smear_weight = touched_points.weights[nearest_points[best_match].index];
apply_weight_to_point(point, smear_weight, drawing_weight);
}
public:
void on_stroke_begin(const bContext &C, const InputSample &start_sample) override
{
using namespace blender::ed::greasepencil;
this->get_brush_settings(C, start_sample);
this->ensure_active_vertex_group_in_object();
this->get_locked_and_bone_deformed_vertex_groups();
/* Get editable drawings grouped per frame number. When multiframe editing is disabled, this
* is just one group for the current frame. When multiframe editing is enabled, the selected
* keyframes are grouped per frame number. This way we can use Smear on multiple layers
* together instead of on every layer individually. */
const Scene *scene = CTX_data_scene(&C);
Array<Vector<MutableDrawingInfo>> drawings_per_frame =
retrieve_editable_drawings_grouped_per_frame(*scene, *this->grease_pencil);
this->drawing_weight_data = Array<Array<DrawingWeightData>>(drawings_per_frame.size());
/* Get weight data for all drawings in this frame group. */
for (const int frame_group : drawings_per_frame.index_range()) {
const Vector<MutableDrawingInfo> &drawings = drawings_per_frame[frame_group];
this->init_weight_data_for_drawings(C, drawings, frame_group);
}
}
void on_stroke_extended(const bContext &C, const InputSample &extension_sample) override
{
using namespace blender::ed::greasepencil;
this->get_mouse_input_sample(extension_sample);
/* For the Smear tool, we use the direction of the brush during the stroke movement. The
* direction is derived from the current and previous mouse position. */
if (!this->get_brush_direction()) {
/* Abort when no direction is established yet. */
return;
}
/* Iterate over the drawings grouped per frame number. Collect all stroke points under the
* brush and smear them. */
std::atomic<bool> changed = false;
threading::parallel_for_each(
this->drawing_weight_data.index_range(), [&](const int frame_group) {
Array<DrawingWeightData> &drawing_weights = this->drawing_weight_data[frame_group];
std::atomic<bool> balance_kdtree = false;
/* For all layers at this key frame, collect the stroke points under the brush in a
* buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const int point_index : drawing_weight.point_positions.index_range()) {
const float2 &co = drawing_weight.point_positions[point_index];
/* When the point is under the brush, add it to the brush point buffer. */
this->add_point_under_brush_to_brush_buffer(co, drawing_weight, point_index);
}
});
/* Create a KDTree with all stroke points touched by the brush during the weight paint
* operation. */
PointsTouchedByBrush touched_points = this->create_affected_points_kdtree(
drawing_weights);
/* Apply the Smear tool to all points in the brush buffer. */
threading::parallel_for_each(drawing_weights, [&](DrawingWeightData &drawing_weight) {
for (const BrushPoint &point : drawing_weight.points_in_brush) {
this->apply_smear_tool(point, drawing_weight, touched_points);
/* Normalize weights of bone-deformed vertex groups to 1.0f. */
if (this->auto_normalize) {
normalize_vertex_weights(drawing_weight.deform_verts[point.drawing_point_index],
drawing_weight.active_vertex_group,
drawing_weight.locked_vgroups,
drawing_weight.bone_deformed_vgroups);
}
}
if (!drawing_weight.points_in_brush.is_empty()) {
changed = true;
drawing_weight.points_in_brush.clear();
}
});
BLI_kdtree_2d_free(touched_points.kdtree);
});
if (changed) {
DEG_id_tag_update(&this->grease_pencil->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(&C, NC_GEOM | ND_DATA, &grease_pencil);
}
}
void on_stroke_done(const bContext & /*C*/) override {}
};
std::unique_ptr<GreasePencilStrokeOperation> new_weight_paint_smear_operation()
{
return std::make_unique<SmearWeightPaintOperation>();
}
} // namespace blender::ed::sculpt_paint::greasepencil

View File

@ -1516,35 +1516,31 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
return;
}
Paint *paint = pcontext->paint;
Brush *brush = pcontext->brush;
if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
/* default radius and color */
float color[3] = {1.0f, 1.0f, 1.0f};
float darkcolor[3];
float radius = 2.0f;
float radius = BKE_brush_size_get(pcontext->scene, brush);
const int x = pcontext->x;
const int y = pcontext->y;
/* for paint use paint brush size and color */
if (pcontext->mode == PaintMode::GPencil) {
Paint *paint = pcontext->paint;
Brush *brush = pcontext->brush;
if ((brush == nullptr) || (brush->gpencil_settings == nullptr)) {
return;
}
if ((paint->flags & PAINT_SHOW_BRUSH) == 0) {
return;
}
/* Eraser has a special shape and use a different shader program. */
if (brush->gpencil_tool == GPAINT_TOOL_ERASE) {
grease_pencil_eraser_draw(pcontext);
return;
}
/* Note: For now, there is only as screen space sized cursor. */
radius = BKE_brush_size_get(pcontext->scene, brush);
/* Get current drawing material. */
Material *ma = BKE_grease_pencil_object_material_from_brush_get(object, brush);
if (ma) {
@ -1570,6 +1566,9 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
}
}
}
else if (pcontext->mode == PaintMode::WeightGPencil) {
copy_v3_v3(color, brush->add_col);
}
GPU_line_width(1.0f);
/* Inner Ring: Color from UI panel */
@ -1577,6 +1576,7 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
imm_draw_circle_wire_2d(pcontext->pos, x, y, radius, 32);
/* Outer Ring: Dark color for contrast on light backgrounds (e.g. gray on white) */
float darkcolor[3];
mul_v3_v3fl(darkcolor, color, 0.40f);
immUniformColor4f(darkcolor[0], darkcolor[1], darkcolor[2], 0.8f);
imm_draw_circle_wire_2d(pcontext->pos, x, y, radius + 1, 32);
@ -1604,7 +1604,8 @@ static void grease_pencil_brush_cursor_draw(PaintCursorContext *pcontext)
static void paint_draw_2D_view_brush_cursor(PaintCursorContext *pcontext)
{
switch (pcontext->mode) {
case PaintMode::GPencil: {
case PaintMode::GPencil:
case PaintMode::WeightGPencil: {
grease_pencil_brush_cursor_draw(pcontext);
break;
}

View File

@ -216,7 +216,7 @@ static void vert_hide_update(Object &object,
new_hide.reinitialize(verts.size());
array_utils::gather(hide_vert.span.as_span(), verts, new_hide.as_mutable_span());
calc_hide(verts, new_hide);
if (!array_utils::indexed_data_equal<bool>(hide_vert.span, verts, new_hide)) {
if (array_utils::indexed_data_equal<bool>(hide_vert.span, verts, new_hide)) {
continue;
}

View File

@ -517,7 +517,7 @@ static void face_sets_update(Object &object,
MutableSpan<int> new_face_sets = tls.new_face_sets;
array_utils::gather(face_sets.span.as_span(), faces, new_face_sets);
calc_face_sets(faces, new_face_sets);
if (!array_utils::indexed_data_equal<int>(face_sets.span, faces, new_face_sets)) {
if (array_utils::indexed_data_equal<int>(face_sets.span, faces, new_face_sets)) {
continue;
}
@ -1021,7 +1021,7 @@ static void face_hide_update(Object &object,
MutableSpan<bool> new_hide = tls.new_hide;
array_utils::gather(hide_poly.span.as_span(), faces, new_hide);
calc_hide(faces, new_hide);
if (!array_utils::indexed_data_equal<bool>(hide_poly.span, faces, new_hide)) {
if (array_utils::indexed_data_equal<bool>(hide_poly.span, faces, new_hide)) {
continue;
}

View File

@ -976,7 +976,7 @@ static void add_extrapolation_point_right(FCurve *fcu,
curve_vertices.append(vertex_position);
}
static blender::float2 calculate_pixels_per_unit(View2D *v2d)
static blender::float2 calculate_pixels_per_unit(View2D *v2d, const float unit_scale)
{
const int window_width = BLI_rcti_size_x(&v2d->mask);
const int window_height = BLI_rcti_size_y(&v2d->mask);
@ -984,7 +984,7 @@ static blender::float2 calculate_pixels_per_unit(View2D *v2d)
const float v2d_frame_range = BLI_rctf_size_x(&v2d->cur);
const float v2d_value_range = BLI_rctf_size_y(&v2d->cur);
const blender::float2 pixels_per_unit = {window_width / v2d_frame_range,
window_height / v2d_value_range};
(window_height / v2d_value_range) * unit_scale};
return pixels_per_unit;
}
@ -1040,7 +1040,7 @@ static void draw_fcurve_curve_keys(
curve_vertices.append(
{fcu->bezt[index_range.first()].vec[1][0], fcu->bezt[index_range.first()].vec[1][1]});
const blender::float2 pixels_per_unit = calculate_pixels_per_unit(v2d);
const float2 pixels_per_unit = calculate_pixels_per_unit(v2d, unit_scale);
const int window_width = BLI_rcti_size_x(&v2d->mask);
const float v2d_frame_range = BLI_rctf_size_x(&v2d->cur);
const float pixel_width = v2d_frame_range / window_width;

View File

@ -42,10 +42,7 @@ static void image_sequence_get_frame_ranges(wmOperator *op, ListBase *ranges)
{
char dir[FILE_MAXDIR];
const bool do_frame_range = RNA_boolean_get(op->ptr, "use_sequence_detection");
ImageFrameRange *range = nullptr;
int range_first_frame = 0;
/* Track when a new series of files are found that aren't compatible with the previous file. */
char base_head[FILE_MAX], base_tail[FILE_MAX];
RNA_string_get(op->ptr, "directory", dir);
RNA_BEGIN (op->ptr, itemptr, "files") {
@ -58,10 +55,19 @@ static void image_sequence_get_frame_ranges(wmOperator *op, ListBase *ranges)
frame->framenr = BLI_path_sequence_decode(
filename, head, sizeof(head), tail, sizeof(tail), &digits);
/* still in the same sequence */
if (do_frame_range && (range != nullptr) && STREQLEN(base_head, head, FILE_MAX) &&
STREQLEN(base_tail, tail, FILE_MAX))
{
/* Check if the image sequence is already initialized. */
ImageFrameRange *range = nullptr;
if (do_frame_range) {
LISTBASE_FOREACH (ImageFrameRange *, range_test, ranges) {
if ((digits == range_test->filename_digits) &&
(STREQ(head, range_test->filename_head) && STREQ(tail, range_test->filename_tail)))
{
range = range_test;
break;
}
}
}
if (range) {
/* Set filepath to first frame in the range. */
if (frame->framenr < range_first_frame) {
BLI_path_join(range->filepath, sizeof(range->filepath), dir, filename);
@ -73,9 +79,9 @@ static void image_sequence_get_frame_ranges(wmOperator *op, ListBase *ranges)
range = static_cast<ImageFrameRange *>(MEM_callocN(sizeof(*range), __func__));
BLI_path_join(range->filepath, sizeof(range->filepath), dir, filename);
BLI_addtail(ranges, range);
STRNCPY(base_head, head);
STRNCPY(base_tail, tail);
range->filename_digits = digits;
STRNCPY(range->filename_head, head);
STRNCPY(range->filename_tail, tail);
range_first_frame = frame->framenr;
}

View File

@ -262,35 +262,8 @@ static void image_keymap(wmKeyConfig *keyconf)
WM_keymap_ensure(keyconf, "Image", SPACE_IMAGE, RGN_TYPE_WINDOW);
}
/* dropboxes */
static bool image_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
ScrArea *area = CTX_wm_area(C);
if (ED_region_overlap_isect_any_xy(area, event->xy)) {
return false;
}
if (drag->type == WM_DRAG_PATH) {
const eFileSel_File_Types file_type = eFileSel_File_Types(WM_drag_get_path_file_type(drag));
if (ELEM(file_type, FILE_TYPE_IMAGE, FILE_TYPE_MOVIE)) {
return true;
}
}
return false;
}
static void image_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop)
{
/* copy drag path to properties */
RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag));
}
/* area+region dropbox definition */
static void image_dropboxes()
{
ListBase *lb = WM_dropboxmap_find("Image", SPACE_IMAGE, RGN_TYPE_WINDOW);
WM_dropbox_add(lb, "IMAGE_OT_open", image_drop_poll, image_drop_copy, nullptr, nullptr);
}
static void image_dropboxes() {}
/**
* \note take care not to get into feedback loop here,

View File

@ -1589,7 +1589,11 @@ static void create_inspection_string_for_default_socket_value(const bNodeSocket
if (!socket.is_input()) {
return;
}
if (socket.is_directly_linked()) {
if (socket.is_multi_input()) {
return;
}
const Span<const bNodeSocket *> connected_sockets = socket.directly_linked_sockets();
if (!connected_sockets.is_empty() && !connected_sockets[0]->owner_node().is_dangling_reroute()) {
return;
}
if (const nodes::SocketDeclaration *socket_decl = socket.runtime->declaration) {
@ -1769,6 +1773,65 @@ static std::optional<std::string> create_default_value_inspection_string(const b
return str;
}
static const bNodeSocket *target_for_reroute(const bNodeSocket &reroute_output)
{
const bNodeSocket *output = &reroute_output;
Set<const bNode *> visited_nodes;
visited_nodes.add(&reroute_output.owner_node());
while (true) {
const Span<const bNodeSocket *> linked_sockets = output->directly_linked_sockets();
if (linked_sockets.size() != 1) {
return nullptr;
}
const bNode &target_node = linked_sockets[0]->owner_node();
if (!visited_nodes.add(&target_node)) {
return nullptr;
}
if (!target_node.is_dangling_reroute()) {
return linked_sockets[0];
}
output = target_node.output_sockets()[0];
}
}
static std::optional<std::string> create_dangling_reroute_inspection_string(
const bNodeTree &ntree, const bNodeSocket &socket)
{
if (ntree.type != NTREE_GEOMETRY) {
return std::nullopt;
}
const bNode &node = socket.owner_node();
if (!node.is_dangling_reroute()) {
return std::nullopt;
}
const bNodeSocket &output_socket = *node.output_sockets()[0];
const bNodeSocket *target_socket = target_for_reroute(output_socket);
if (target_socket == nullptr) {
if (!output_socket.directly_linked_sockets().is_empty()) {
return TIP_("Dangling reroute is ignored by all targets");
}
return std::nullopt;
}
if (target_socket->is_multi_input()) {
return TIP_("Dangling reroute branch is ignored by multi input socket");
}
std::stringstream ss;
create_inspection_string_for_default_socket_value(*target_socket, ss);
if (ss.str().empty()) {
return TIP_("Dangling reroute is ignored");
}
ss << ".\n\n";
ss << TIP_("Dangling reroute is ignored, default value of target socket is used");
return ss.str();
return std::nullopt;
}
static std::string node_socket_get_tooltip(const SpaceNode *snode,
const bNodeTree &ntree,
const bNodeSocket &socket)
@ -1791,6 +1854,11 @@ static std::string node_socket_get_tooltip(const SpaceNode *snode,
if (std::optional<std::string> info = create_log_inspection_string(geo_tree_log, socket)) {
inspection_strings.append(std::move(*info));
}
else if (std::optional<std::string> info = create_dangling_reroute_inspection_string(ntree,
socket))
{
inspection_strings.append(std::move(*info));
}
else if (std::optional<std::string> info = create_default_value_inspection_string(socket)) {
inspection_strings.append(std::move(*info));
}

View File

@ -183,7 +183,7 @@ void sequencer_preview_add_sound(const bContext *C, Sequence *seq)
wmJob *wm_job;
PreviewJob *pj;
ScrArea *area = CTX_wm_area(C);
PreviewJobAudio *audiojob = MEM_cnew<PreviewJobAudio>("preview_audio");
wm_job = WM_jobs_get(CTX_wm_manager(C),
CTX_wm_window(C),
CTX_data_scene(C),
@ -221,6 +221,7 @@ void sequencer_preview_add_sound(const bContext *C, Sequence *seq)
WM_jobs_callbacks(wm_job, preview_startjob, nullptr, nullptr, preview_endjob);
}
PreviewJobAudio *audiojob = MEM_cnew<PreviewJobAudio>("preview_audio");
audiojob->bmain = CTX_data_main(C);
audiojob->sound = seq->sound;

View File

@ -224,14 +224,12 @@ static StripDrawContext strip_draw_context_get(TimelineDrawContext *ctx, Sequenc
strip_ctx.missing_media = media_presence_is_missing(scene, seq);
if (seq->type == SEQ_TYPE_META) {
const ListBase *seqbase = &seq->seqbase;
if (seqbase != nullptr) {
LISTBASE_FOREACH (const Sequence *, sub, seqbase) {
if (!SEQ_sequence_has_valid_data(sub)) {
strip_ctx.missing_data_block = true;
}
if (media_presence_is_missing(scene, sub)) {
strip_ctx.missing_media = true;
}
LISTBASE_FOREACH (const Sequence *, sub, seqbase) {
if (!SEQ_sequence_has_valid_data(sub)) {
strip_ctx.missing_data_block = true;
}
if (media_presence_is_missing(scene, sub)) {
strip_ctx.missing_media = true;
}
}
}

View File

@ -414,6 +414,10 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *region)
wm->defaultconf, "Grease Pencil Sculpt Mode", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
keymap = WM_keymap_ensure(
wm->defaultconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
/* Edit-font key-map swallows almost all (because of text input). */
keymap = WM_keymap_ensure(wm->defaultconf, "Font", SPACE_EMPTY, RGN_TYPE_WINDOW);
WM_event_add_keymap_handler(&region->handlers, keymap);
@ -1716,7 +1720,7 @@ void ED_view3d_buttons_region_layout_ex(const bContext *C,
ARRAY_SET_ITEMS(contexts, ".paint_common", ".grease_pencil_sculpt");
break;
case CTX_MODE_WEIGHT_GREASE_PENCIL:
ARRAY_SET_ITEMS(contexts, ".grease_pencil_weight");
ARRAY_SET_ITEMS(contexts, ".greasepencil_weight");
break;
case CTX_MODE_EDIT_POINT_CLOUD:
ARRAY_SET_ITEMS(contexts, ".point_cloud_edit");

View File

@ -267,12 +267,15 @@ static int gizmo_preselect_elem_test_select(bContext *C, wmGizmo *gz, const int
ED_region_tag_redraw_editor_overlays(region);
}
// return best.eed ? 0 : -1;
return -1;
return best.ele ? 0 : -1;
}
static void gizmo_preselect_elem_setup(wmGizmo *gz)
{
/* Needed so it's possible to "highlight" the gizmo without having the
* tweak operator attempting to handle it's input. */
gz->flag |= WM_GIZMO_HIDDEN_KEYMAP;
MeshElemGizmo3D *gz_ele = (MeshElemGizmo3D *)gz;
if (gz_ele->psel == nullptr) {
gz_ele->psel = EDBM_preselect_elem_create();
@ -425,12 +428,15 @@ static int gizmo_preselect_edgering_test_select(bContext *C, wmGizmo *gz, const
ED_region_tag_redraw_editor_overlays(region);
}
// return best.eed ? 0 : -1;
return -1;
return best.eed ? 0 : -1;
}
static void gizmo_preselect_edgering_setup(wmGizmo *gz)
{
/* Needed so it's possible to "highlight" the gizmo without having the
* tweak operator attempting to handle it's input. */
gz->flag |= WM_GIZMO_HIDDEN_KEYMAP;
MeshEdgeRingGizmo3D *gz_ring = (MeshEdgeRingGizmo3D *)gz;
if (gz_ring->psel == nullptr) {
gz_ring->psel = EDBM_preselect_edgering_create();

View File

@ -11,12 +11,28 @@
namespace blender::geometry {
struct ConvertCurvesOptions {
bool convert_bezier_handles_to_poly_points = false;
bool convert_bezier_handles_to_catmull_rom_points = false;
/**
* Make the nurb curve behave like a bezier curve and also keep the handle positions as control
* points.
*/
bool keep_bezier_shape_as_nurbs = true;
/**
* Keep the exact shape of the catmull rom curve by inserting extra handle control points in the
* nurbs curve.
*/
bool keep_catmull_rom_shape_as_nurbs = true;
};
/**
* Change the types of the selected curves, potentially changing the total point count.
*/
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info);
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options = {});
} // namespace blender::geometry

View File

@ -629,19 +629,175 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src
return dst_curves;
}
static bke::CurvesGeometry convert_curves_to_catmull_rom_or_poly(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options)
{
const bool use_bezier_handles = (dst_type == CURVE_TYPE_CATMULL_ROM) ?
options.convert_bezier_handles_to_catmull_rom_points :
options.convert_bezier_handles_to_poly_points;
if (!use_bezier_handles || !src_curves.has_curve_with_type(CURVE_TYPE_BEZIER)) {
return convert_curves_trivial(src_curves, selection, dst_type);
}
const OffsetIndices src_points_by_curve = src_curves.points_by_curve();
const VArray<int8_t> src_types = src_curves.curve_types();
const VArray<bool> src_cyclic = src_curves.cyclic();
const Span<float3> src_positions = src_curves.positions();
const bke::AttributeAccessor src_attributes = src_curves.attributes();
IndexMaskMemory memory;
const IndexMask unselected = selection.complement(src_curves.curves_range(), memory);
bke::CurvesGeometry dst_curves = bke::curves::copy_only_curve_domain(src_curves);
dst_curves.fill_curve_types(selection, dst_type);
MutableSpan<int> dst_offsets = dst_curves.offsets_for_write();
offset_indices::copy_group_sizes(src_points_by_curve, unselected, dst_offsets);
selection.foreach_index(GrainSize(1024), [&](const int i) {
const IndexRange src_points = src_points_by_curve[i];
const CurveType src_curve_type = CurveType(src_types[i]);
int &size = dst_offsets[i];
if (src_curve_type == CURVE_TYPE_BEZIER) {
size = src_points.size() * 3;
}
else {
size = src_points.size();
}
});
offset_indices::accumulate_counts_to_offsets(dst_offsets);
dst_curves.resize(dst_offsets.last(), dst_curves.curves_num());
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
MutableSpan<float3> dst_positions = dst_curves.positions_for_write();
bke::MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
Vector<bke::AttributeTransferData> generic_attributes = bke::retrieve_attributes_for_transfer(
src_attributes,
dst_attributes,
ATTR_DOMAIN_MASK_POINT,
propagation_info,
{"position",
"handle_type_left",
"handle_type_right",
"handle_right",
"handle_left",
"nurbs_weight"});
auto convert_from_catmull_rom_or_poly_or_nurbs = [&](const IndexMask &selection) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, src_positions, dst_positions);
for (bke::AttributeTransferData &attribute : generic_attributes) {
array_utils::copy_group_to_group(
src_points_by_curve, dst_points_by_curve, selection, attribute.src, attribute.dst.span);
}
};
auto convert_from_bezier = [&](const IndexMask &selection) {
const Span<float3> src_left_handles = src_curves.handle_positions_left();
const Span<float3> src_right_handles = src_curves.handle_positions_right();
/* Transfer positions. */
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
dst_positions[dst_points_start + 0] = src_left_handles[src_point_i];
dst_positions[dst_points_start + 1] = src_positions[src_point_i];
dst_positions[dst_points_start + 2] = src_right_handles[src_point_i];
}
});
/* Transfer attributes. The handles the same attribute values as their corresponding control
* point. */
for (bke::AttributeTransferData &attribute : generic_attributes) {
const CPPType &cpp_type = attribute.src.type();
selection.foreach_index([&](const int curve_i) {
const IndexRange src_points = src_points_by_curve[curve_i];
const IndexRange dst_points = dst_points_by_curve[curve_i];
for (const int i : src_points.index_range()) {
const int src_point_i = src_points[i];
const int dst_points_start = dst_points.start() + 3 * i;
const void *src_value = attribute.src[src_point_i];
cpp_type.fill_assign_n(src_value, attribute.dst.span[dst_points_start], 3);
}
});
}
};
bke::curves::foreach_curve_by_type(src_curves.curve_types(),
src_curves.curve_type_counts(),
selection,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_catmull_rom_or_poly_or_nurbs,
convert_from_bezier,
convert_from_catmull_rom_or_poly_or_nurbs);
for (bke::AttributeTransferData &attribute : generic_attributes) {
attribute.dst.finish();
}
bke::copy_attributes_group_to_group(src_attributes,
bke::AttrDomain::Point,
propagation_info,
{},
src_points_by_curve,
dst_points_by_curve,
unselected,
dst_attributes);
return dst_curves;
}
/**
* Converts some curves to poly curves before they are converted to nurbs. This is useful because
* it discards the bezier/catmull-rom shape which is sometimes the desired behavior.
*/
static bke::CurvesGeometry convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const ConvertCurvesOptions &options)
{
const VArray<int8_t> src_curve_types = src_curves.curve_types();
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_predicate(
selection, GrainSize(4096), memory, [&](const int curve_i) {
const CurveType type = CurveType(src_curve_types[curve_i]);
if (!options.keep_bezier_shape_as_nurbs && type == CURVE_TYPE_BEZIER) {
return true;
}
if (!options.keep_catmull_rom_shape_as_nurbs && type == CURVE_TYPE_CATMULL_ROM) {
return true;
}
return false;
});
return convert_curves_trivial(src_curves, mask, CURVE_TYPE_POLY);
}
bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves,
const IndexMask &selection,
const CurveType dst_type,
const bke::AnonymousAttributePropagationInfo &propagation_info)
const bke::AnonymousAttributePropagationInfo &propagation_info,
const ConvertCurvesOptions &options)
{
switch (dst_type) {
case CURVE_TYPE_CATMULL_ROM:
case CURVE_TYPE_POLY:
return convert_curves_trivial(src_curves, selection, dst_type);
return convert_curves_to_catmull_rom_or_poly(
src_curves, selection, dst_type, propagation_info, options);
case CURVE_TYPE_BEZIER:
return convert_curves_to_bezier(src_curves, selection, propagation_info);
case CURVE_TYPE_NURBS:
case CURVE_TYPE_NURBS: {
if (!options.keep_bezier_shape_as_nurbs || !options.keep_catmull_rom_shape_as_nurbs) {
const bke::CurvesGeometry tmp_src_curves =
convert_bezier_or_catmull_rom_to_poly_before_conversion_to_nurbs(
src_curves, selection, options);
return convert_curves_to_nurbs(tmp_src_curves, selection, propagation_info);
}
return convert_curves_to_nurbs(src_curves, selection, propagation_info);
}
}
BLI_assert_unreachable();
return {};

View File

@ -323,6 +323,8 @@ void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params &params)
void PackIsland::finalize_geometry_(const UVPackIsland_Params &params, MemArena *arena, Heap *heap)
{
BLI_assert(BLI_heap_len(heap) == 0);
/* After all the triangles and polygons have been added to a #PackIsland, but before we can start
* running packing algorithms, there is a one-time finalization process where we can
* pre-calculate a few quantities about the island, including pre-rotation, bounding box, or
@ -353,16 +355,15 @@ void PackIsland::finalize_geometry_(const UVPackIsland_Params &params, MemArena
/* Compute convex hull. */
int convex_len = BLI_convexhull_2d(source, vert_count, index_map);
/* Write back. */
triangle_vertices_.clear();
Array<float2> convexVertices(convex_len);
for (int i = 0; i < convex_len; i++) {
convexVertices[i] = source[index_map[i]];
if (convex_len >= 3) {
/* Write back. */
triangle_vertices_.clear();
Array<float2> convexVertices(convex_len);
for (int i = 0; i < convex_len; i++) {
convexVertices[i] = source[index_map[i]];
}
add_polygon(convexVertices, arena, heap);
}
add_polygon(convexVertices, arena, heap);
BLI_heap_clear(heap, nullptr);
}
/* Pivot calculation might be performed multiple times during pre-processing.

View File

@ -383,6 +383,7 @@ PanelType *gpencil_modifier_subpanel_register(ARegionType *region_type,
{
PanelType *panel_type = static_cast<PanelType *>(MEM_callocN(sizeof(PanelType), __func__));
BLI_assert(parent != nullptr);
SNPRINTF(panel_type->idname, "%s_%s", parent->idname, name);
STRNCPY(panel_type->label, label);
STRNCPY(panel_type->context, "modifier");
@ -393,7 +394,6 @@ PanelType *gpencil_modifier_subpanel_register(ARegionType *region_type,
panel_type->poll = gpencil_modifier_ui_poll;
panel_type->flag = PANEL_TYPE_DEFAULT_CLOSED;
BLI_assert(parent != nullptr);
STRNCPY(panel_type->parent_id, parent->idname);
panel_type->parent = parent;
BLI_addtail(&parent->children, BLI_genericNodeN(panel_type));

View File

@ -219,6 +219,7 @@ set(VULKAN_SRC
vulkan/vk_pixel_buffer.cc
vulkan/vk_push_constants.cc
vulkan/vk_query.cc
vulkan/render_graph/nodes/vk_pipeline_data.cc
vulkan/render_graph/vk_command_buffer_wrapper.cc
vulkan/render_graph/vk_command_builder.cc
vulkan/render_graph/vk_resource_access_info.cc

View File

@ -0,0 +1,87 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#pragma once
#include "render_graph/vk_render_graph_links.hh"
#include "render_graph/vk_resource_access_info.hh"
#include "vk_node_info.hh"
namespace blender::gpu::render_graph {
/**
* Information stored inside the render graph node. See `VKRenderGraphNode`.
*/
struct VKDispatchIndirectData {
VKPipelineData pipeline_data;
VkBuffer buffer;
VkDeviceSize offset;
};
/**
* Information needed to add a node to the render graph.
*/
struct VKDispatchIndirectCreateInfo : NonCopyable {
VKDispatchIndirectData dispatch_indirect_node = {};
const VKResourceAccessInfo &resources;
VKDispatchIndirectCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDispatchIndirectNode : public VKNodeInfo<VKNodeType::DISPATCH_INDIRECT,
VKDispatchIndirectCreateInfo,
VKDispatchIndirectData,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VKResourceType::IMAGE | VKResourceType::BUFFER> {
public:
/**
* Update the node data with the data inside create_info.
*
* Has been implemented as a template to ensure all node specific data
* (`VK*Data`/`VK*CreateInfo`) types can be included in the same header file as the logic. The
* actual node data (`VKRenderGraphNode` includes all header files.)
*/
template<typename Node> static void set_node_data(Node &node, const CreateInfo &create_info)
{
node.dispatch_indirect = create_info.dispatch_indirect_node;
vk_pipeline_data_copy(node.dispatch_indirect.pipeline_data,
create_info.dispatch_indirect_node.pipeline_data);
}
/**
* Free the pipeline data stored in the render graph node data.
*/
void free_data(VKDispatchIndirectData &data)
{
vk_pipeline_data_free(data.pipeline_data);
}
/**
* Extract read/write resource dependencies from `create_info` and add them to `node_links`.
*/
void build_links(VKResourceStateTracker &resources,
VKRenderGraphNodeLinks &node_links,
const CreateInfo &create_info) override
{
create_info.resources.build_links(resources, node_links);
ResourceWithStamp buffer_resource = resources.get_buffer(
create_info.dispatch_indirect_node.buffer);
node_links.inputs.append({buffer_resource, VK_ACCESS_INDIRECT_COMMAND_READ_BIT});
}
/**
* Build the commands and add them to the command_buffer.
*/
void build_commands(VKCommandBufferInterface &command_buffer,
const Data &data,
VKBoundPipelines &r_bound_pipelines) override
{
vk_pipeline_data_build_commands(
command_buffer, data.pipeline_data, r_bound_pipelines, VK_PIPELINE_BIND_POINT_COMPUTE);
command_buffer.dispatch_indirect(data.buffer, data.offset);
}
};
} // namespace blender::gpu::render_graph

View File

@ -27,8 +27,9 @@ struct VKDispatchData {
* Information needed to add a node to the render graph.
*/
struct VKDispatchCreateInfo : NonCopyable {
VKDispatchData dispatch_node;
VKResourceAccessInfo resources;
VKDispatchData dispatch_node = {};
const VKResourceAccessInfo &resources;
VKDispatchCreateInfo(const VKResourceAccessInfo &resources) : resources(resources) {}
};
class VKDispatchNode : public VKNodeInfo<VKNodeType::DISPATCH,
@ -75,33 +76,8 @@ class VKDispatchNode : public VKNodeInfo<VKNodeType::DISPATCH,
const Data &data,
VKBoundPipelines &r_bound_pipelines) override
{
/* TODO: introduce helper function in pipeline types. */
const VKPipelineData &pipeline_data = data.pipeline_data;
if (assign_if_different(r_bound_pipelines.compute.vk_pipeline, pipeline_data.vk_pipeline)) {
command_buffer.bind_pipeline(VK_PIPELINE_BIND_POINT_COMPUTE,
r_bound_pipelines.compute.vk_pipeline);
}
if (assign_if_different(r_bound_pipelines.compute.vk_descriptor_set,
pipeline_data.vk_descriptor_set))
{
command_buffer.bind_descriptor_sets(VK_PIPELINE_BIND_POINT_COMPUTE,
pipeline_data.vk_pipeline_layout,
0,
1,
&r_bound_pipelines.compute.vk_descriptor_set,
0,
nullptr);
}
if (pipeline_data.push_constants_size) {
command_buffer.push_constants(pipeline_data.vk_pipeline_layout,
VK_SHADER_STAGE_COMPUTE_BIT,
0,
pipeline_data.push_constants_size,
pipeline_data.push_constants_data);
}
vk_pipeline_data_build_commands(
command_buffer, data.pipeline_data, r_bound_pipelines, VK_PIPELINE_BIND_POINT_COMPUTE);
command_buffer.dispatch(data.group_count_x, data.group_count_y, data.group_count_z);
}
};

View File

@ -30,6 +30,7 @@ enum class VKNodeType {
COPY_BUFFER_TO_IMAGE,
BLIT_IMAGE,
DISPATCH,
DISPATCH_INDIRECT,
SYNCHRONIZATION,
};

View File

@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup gpu
*/
#include "render_graph/nodes/vk_pipeline_data.hh"
#include "render_graph/vk_command_buffer_wrapper.hh"
namespace blender::gpu::render_graph {
void vk_pipeline_data_copy(VKPipelineData &dst, const VKPipelineData &src)
{
dst.push_constants_data = nullptr;
dst.push_constants_size = src.push_constants_size;
if (src.push_constants_size) {
BLI_assert(src.push_constants_data);
void *data = MEM_mallocN(src.push_constants_size, __func__);
memcpy(data, src.push_constants_data, src.push_constants_size);
dst.push_constants_data = data;
}
}
void vk_pipeline_data_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineData &pipeline_data,
VKBoundPipelines &r_bound_pipelines,
VkPipelineBindPoint vk_pipeline_bind_point)
{
if (assign_if_different(r_bound_pipelines.compute.vk_pipeline, pipeline_data.vk_pipeline)) {
command_buffer.bind_pipeline(vk_pipeline_bind_point, r_bound_pipelines.compute.vk_pipeline);
}
if (assign_if_different(r_bound_pipelines.compute.vk_descriptor_set,
pipeline_data.vk_descriptor_set))
{
command_buffer.bind_descriptor_sets(vk_pipeline_bind_point,
pipeline_data.vk_pipeline_layout,
0,
1,
&r_bound_pipelines.compute.vk_descriptor_set,
0,
nullptr);
}
if (pipeline_data.push_constants_size) {
command_buffer.push_constants(pipeline_data.vk_pipeline_layout,
vk_pipeline_bind_point,
0,
pipeline_data.push_constants_size,
pipeline_data.push_constants_data);
}
}
void vk_pipeline_data_free(VKPipelineData &data)
{
MEM_SAFE_FREE(data.push_constants_data);
}
} // namespace blender::gpu::render_graph

View File

@ -11,6 +11,8 @@
#include "vk_common.hh"
namespace blender::gpu::render_graph {
class VKCommandBufferInterface;
/**
* Container for storing shader descriptor set and push constants.
*
@ -24,6 +26,23 @@ struct VKPipelineData {
const void *push_constants_data;
};
/** Resources bound for a compute/graphics pipeline. */
struct VKBoundPipeline {
VkPipeline vk_pipeline;
VkDescriptorSet vk_descriptor_set;
};
/**
* Vulkan keeps track of bound resources for graphics separate from compute.
* This struct store last bound resources for both bind points.
*/
struct VKBoundPipelines {
/** Last bound resources for compute pipeline. */
VKBoundPipeline compute;
/** Last bound resources for graphics pipeline. */
VKBoundPipeline graphics;
};
/**
* Copy src pipeline data into dst. The push_constant_data will be duplicated and needs to be freed
* using `vk_pipeline_data_free`.
@ -31,33 +50,26 @@ struct VKPipelineData {
* Memory duplication isn't used as push_constant_data in the src doesn't need to be allocated via
* guardedalloc.
*/
BLI_INLINE void vk_pipeline_data_copy(VKPipelineData &dst, const VKPipelineData &src)
{
dst.push_constants_data = nullptr;
dst.push_constants_size = src.push_constants_size;
if (src.push_constants_size) {
BLI_assert(src.push_constants_data);
void *data = MEM_mallocN(src.push_constants_size, __func__);
memcpy(data, src.push_constants_data, src.push_constants_size);
dst.push_constants_data = data;
}
}
void vk_pipeline_data_copy(VKPipelineData &dst, const VKPipelineData &src);
/**
* Record the commands to the given command buffer to bind the descriptor set, pipeline and push
* constants.
*
* Descriptor set and pipeline are only bound, when they are different than the last bound. The
* r_bound_pipelines are checked to identify if they are the last bound. Descriptor set and
* pipeline are bound at the given pipeline bind point.
*
* Any available push constants in the pipeline data are always bound.
*/
void vk_pipeline_data_build_commands(VKCommandBufferInterface &command_buffer,
const VKPipelineData &pipeline_data,
VKBoundPipelines &r_bound_pipelines,
VkPipelineBindPoint vk_pipeline_bind_point);
/**
* Free localized data created by `vk_pipeline_data_copy`.
*/
BLI_INLINE void vk_pipeline_data_free(VKPipelineData &data)
{
MEM_SAFE_FREE(data.push_constants_data);
}
struct VKBoundPipeline {
VkPipeline vk_pipeline;
VkDescriptorSet vk_descriptor_set;
};
struct VKBoundPipelines {
VKBoundPipeline compute;
VKBoundPipeline graphics;
};
void vk_pipeline_data_free(VKPipelineData &data);
} // namespace blender::gpu::render_graph

View File

@ -20,15 +20,15 @@ TEST(vk_render_graph, dispatch_read_back)
VKResourceStateTracker resources;
VKRenderGraph render_graph(std::make_unique<CommandBufferLog>(log), resources);
resources.add_buffer(buffer);
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
render_graph.submit_buffer_for_read(buffer);
EXPECT_EQ(3, log.size());
@ -58,25 +58,27 @@ TEST(vk_render_graph, dispatch_dispatch_read_back)
resources.add_buffer(buffer);
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
render_graph.submit_buffer_for_read(buffer);
@ -119,25 +121,27 @@ TEST(vk_render_graph, dispatch_dispatch_read_back_with_changing_descriptor_sets)
resources.add_buffer(buffer);
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_a;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_b;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
render_graph.submit_buffer_for_read(buffer);
@ -183,25 +187,27 @@ TEST(vk_render_graph, dispatch_dispatch_read_back_with_changing_pipelines)
resources.add_buffer(buffer);
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_a;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_b;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
render_graph.submit_buffer_for_read(buffer);
@ -247,25 +253,27 @@ TEST(vk_render_graph, dispatch_dispatch_read_back_with_changing_pipelines_descri
resources.add_buffer(buffer);
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_a;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_a;
dispatch_info.dispatch_node.group_count_x = 1;
dispatch_info.dispatch_node.group_count_y = 1;
dispatch_info.dispatch_node.group_count_z = 1;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
{
VKDispatchNode::CreateInfo dispatch_info = {};
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchNode::CreateInfo dispatch_info(access_info);
dispatch_info.dispatch_node.pipeline_data.vk_pipeline = pipeline_b;
dispatch_info.dispatch_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_info.dispatch_node.pipeline_data.vk_descriptor_set = descriptor_set_b;
dispatch_info.dispatch_node.group_count_x = 2;
dispatch_info.dispatch_node.group_count_y = 2;
dispatch_info.dispatch_node.group_count_z = 2;
dispatch_info.resources.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
render_graph.add_node(dispatch_info);
}
render_graph.submit_buffer_for_read(buffer);
@ -295,4 +303,120 @@ TEST(vk_render_graph, dispatch_dispatch_read_back_with_changing_pipelines_descri
EXPECT_EQ("dispatch(group_count_x=2, group_count_y=2, group_count_z=2)", log[6]);
}
/**
* Test dispatch indirect
*/
TEST(vk_render_graph, dispatch_indirect_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkBuffer> command_buffer(2u);
VkHandle<VkPipeline> pipeline(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
Vector<std::string> log;
VKCommandBufferWrapper wrapper;
VKResourceStateTracker resources;
VKRenderGraph render_graph(std::make_unique<CommandBufferLog>(log), resources);
resources.add_buffer(buffer);
resources.add_buffer(command_buffer);
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout = pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
render_graph.add_node(dispatch_indirect_info);
render_graph.submit_buffer_for_read(buffer);
EXPECT_EQ(4, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_INDIRECT_COMMAND_READ_BIT, buffer=0x2, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[0]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[1]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[2]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=0)", log[3]);
}
TEST(vk_render_graph, dispatch_indirect_dispatch_indirect_read_back)
{
VkHandle<VkBuffer> buffer(1u);
VkHandle<VkBuffer> command_buffer(2u);
VkHandle<VkPipeline> pipeline(3u);
VkHandle<VkPipelineLayout> pipeline_layout(4u);
VkHandle<VkDescriptorSet> descriptor_set(5u);
Vector<std::string> log;
VKCommandBufferWrapper wrapper;
VKResourceStateTracker resources;
VKRenderGraph render_graph(std::make_unique<CommandBufferLog>(log), resources);
resources.add_buffer(buffer);
resources.add_buffer(command_buffer);
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout =
pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
render_graph.add_node(dispatch_indirect_info);
}
{
VKResourceAccessInfo access_info = {};
access_info.buffers.append({buffer, VK_ACCESS_SHADER_WRITE_BIT});
VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(access_info);
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline = pipeline;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_pipeline_layout =
pipeline_layout;
dispatch_indirect_info.dispatch_indirect_node.pipeline_data.vk_descriptor_set = descriptor_set;
dispatch_indirect_info.dispatch_indirect_node.buffer = command_buffer;
dispatch_indirect_info.dispatch_indirect_node.offset = 12;
render_graph.add_node(dispatch_indirect_info);
}
render_graph.submit_buffer_for_read(buffer);
EXPECT_EQ(6, log.size());
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=, "
"dst_access_mask=VK_ACCESS_INDIRECT_COMMAND_READ_BIT, buffer=0x2, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[0]);
EXPECT_EQ("bind_pipeline(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, pipeline=0x3)",
log[1]);
EXPECT_EQ(
"bind_descriptor_sets(pipeline_bind_point=VK_PIPELINE_BIND_POINT_COMPUTE, layout=0x4, "
"p_descriptor_sets=0x5)",
log[2]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=0)", log[3]);
EXPECT_EQ(
"pipeline_barrier(src_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, "
"dst_stage_mask=VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT" +
endl() +
" - buffer_barrier(src_access_mask=VK_ACCESS_SHADER_WRITE_BIT, "
"dst_access_mask=VK_ACCESS_SHADER_WRITE_BIT, buffer=0x1, offset=0, "
"size=18446744073709551615)" +
endl() + ")",
log[4]);
EXPECT_EQ("dispatch_indirect(buffer=0x2, offset=12)", log[5]);
}
} // namespace blender::gpu::render_graph

View File

@ -186,10 +186,14 @@ class CommandBufferLog : public VKCommandBufferInterface {
void dispatch_indirect(VkBuffer buffer, VkDeviceSize offset) override
{
UNUSED_VARS(buffer, offset);
BLI_assert_msg(is_recording_,
"Command is added to command buffer, which isn't in recording state.");
BLI_assert_unreachable();
std::stringstream ss;
ss << "dispatch_indirect(";
ss << "buffer=" << to_string(buffer);
ss << ", offset=" << offset;
ss << ")";
log_.append(ss.str());
}
void copy_buffer(VkBuffer src_buffer,

View File

@ -164,6 +164,10 @@ class VKRenderGraph : public NonCopyable {
{
add_node<VKDispatchNode>(dispatch);
}
void add_node(const VKDispatchIndirectNode::CreateInfo &dispatch)
{
add_node<VKDispatchIndirectNode>(dispatch);
}
/**
* Submit partial graph to be able to read the expected result of the rendering commands

View File

@ -15,6 +15,7 @@
#include "nodes/vk_copy_buffer_to_image_node.hh"
#include "nodes/vk_copy_image_node.hh"
#include "nodes/vk_copy_image_to_buffer_node.hh"
#include "nodes/vk_dispatch_indirect_node.hh"
#include "nodes/vk_dispatch_node.hh"
#include "nodes/vk_fill_buffer_node.hh"
#include "nodes/vk_synchronization_node.hh"
@ -44,6 +45,7 @@ struct VKRenderGraphNode {
VKCopyImageNode::Data copy_image;
VKCopyImageToBufferNode::Data copy_image_to_buffer;
VKDispatchNode::Data dispatch;
VKDispatchIndirectNode::Data dispatch_indirect;
VKFillBufferNode::Data fill_buffer;
VKSynchronizationNode::Data synchronization;
};
@ -110,6 +112,8 @@ struct VKRenderGraphNode {
return VKBlitImageNode::pipeline_stage;
case VKNodeType::DISPATCH:
return VKDispatchNode::pipeline_stage;
case VKNodeType::DISPATCH_INDIRECT:
return VKDispatchIndirectNode::pipeline_stage;
case VKNodeType::SYNCHRONIZATION:
return VKSynchronizationNode::pipeline_stage;
}
@ -190,6 +194,12 @@ struct VKRenderGraphNode {
node_info.build_commands(command_buffer, dispatch, r_bound_pipelines);
break;
}
case VKNodeType::DISPATCH_INDIRECT: {
VKDispatchIndirectNode node_info;
node_info.build_commands(command_buffer, dispatch_indirect, r_bound_pipelines);
break;
}
}
}
@ -205,6 +215,12 @@ struct VKRenderGraphNode {
break;
}
case VKNodeType::DISPATCH_INDIRECT: {
VKDispatchIndirectNode node_info;
node_info.free_data(dispatch_indirect);
break;
}
case VKNodeType::UNUSED:
case VKNodeType::CLEAR_COLOR_IMAGE:
case VKNodeType::CLEAR_DEPTH_STENCIL_IMAGE:

View File

@ -141,7 +141,9 @@ void VKBackend::compute_dispatch(int groups_x_len, int groups_y_len, int groups_
{
VKContext &context = *VKContext::get();
if (use_render_graph) {
render_graph::VKDispatchCreateInfo &dispatch_info = context.update_and_get_dispatch_info();
render_graph::VKResourceAccessInfo &resources = context.update_and_get_access_info();
render_graph::VKDispatchNode::CreateInfo dispatch_info(resources);
context.update_pipeline_data(dispatch_info.dispatch_node.pipeline_data);
dispatch_info.dispatch_node.group_count_x = groups_x_len;
dispatch_info.dispatch_node.group_count_y = groups_y_len;
dispatch_info.dispatch_node.group_count_z = groups_z_len;
@ -160,12 +162,22 @@ void VKBackend::compute_dispatch_indirect(StorageBuf *indirect_buf)
{
BLI_assert(indirect_buf);
VKContext &context = *VKContext::get();
render_graph::VKResourceAccessInfo resource_access_info = {};
context.state_manager_get().apply_bindings(context, resource_access_info);
context.bind_compute_pipeline();
VKStorageBuffer &indirect_buffer = *unwrap(indirect_buf);
VKCommandBuffers &command_buffers = context.command_buffers_get();
command_buffers.dispatch(indirect_buffer);
if (use_render_graph) {
render_graph::VKResourceAccessInfo &resources = context.update_and_get_access_info();
render_graph::VKDispatchIndirectNode::CreateInfo dispatch_indirect_info(resources);
context.update_pipeline_data(dispatch_indirect_info.dispatch_indirect_node.pipeline_data);
dispatch_indirect_info.dispatch_indirect_node.buffer = indirect_buffer.vk_handle();
dispatch_indirect_info.dispatch_indirect_node.offset = 0;
context.render_graph.add_node(dispatch_indirect_info);
}
else {
render_graph::VKResourceAccessInfo resource_access_info = {};
context.state_manager_get().apply_bindings(context, resource_access_info);
context.bind_compute_pipeline();
VKCommandBuffers &command_buffers = context.command_buffers_get();
command_buffers.dispatch(indirect_buffer);
}
}
Context *VKBackend::context_alloc(void *ghost_window, void *ghost_context)

View File

@ -217,6 +217,7 @@ void VKContext::update_pipeline_data(render_graph::VKPipelineData &pipeline_data
{
VKShader &vk_shader = unwrap(*shader);
pipeline_data.vk_pipeline_layout = vk_shader.vk_pipeline_layout_get();
pipeline_data.vk_pipeline = vk_shader.ensure_and_get_compute_pipeline();
/* Update descriptor set. */
pipeline_data.vk_descriptor_set = VK_NULL_HANDLE;
@ -231,29 +232,17 @@ void VKContext::update_pipeline_data(render_graph::VKPipelineData &pipeline_data
const VKPushConstants::Layout &push_constants_layout =
vk_shader.interface_get().push_constants_layout_get();
if (push_constants_layout.storage_type_get() == VKPushConstants::StorageType::PUSH_CONSTANTS) {
vk_shader.push_constants.update(*this);
pipeline_data.push_constants_size = push_constants_layout.size_in_bytes();
pipeline_data.push_constants_data = vk_shader.push_constants.data();
}
}
void VKContext::update_dispatch_info()
render_graph::VKResourceAccessInfo &VKContext::update_and_get_access_info()
{
dispatch_info_.dispatch_node = {};
dispatch_info_.resources.reset();
state_manager_get().apply_bindings(*this, dispatch_info_.resources);
update_pipeline_data(dispatch_info_.dispatch_node.pipeline_data);
VKShader &vk_shader = unwrap(*shader);
VkPipeline vk_pipeline = vk_shader.ensure_and_get_compute_pipeline();
dispatch_info_.dispatch_node.pipeline_data.vk_pipeline = vk_pipeline;
}
render_graph::VKDispatchNode::CreateInfo &VKContext::update_and_get_dispatch_info()
{
VKShader *shader = unwrap(this->shader);
shader->push_constants.update(*this);
update_dispatch_info();
return dispatch_info_;
access_info_.reset();
state_manager_get().apply_bindings(*this, access_info_);
return access_info_;
}
/** \} */

View File

@ -35,7 +35,8 @@ class VKContext : public Context, NonCopyable {
GPUTexture *surface_texture_ = nullptr;
void *ghost_context_;
render_graph::VKDispatchNode::CreateInfo dispatch_info_ = {};
/* Reusable data. Stored inside context to limit reallocations. */
render_graph::VKResourceAccessInfo access_info_ = {};
public:
render_graph::VKRenderGraph render_graph;
@ -72,8 +73,12 @@ class VKContext : public Context, NonCopyable {
VKFrameBuffer *active_framebuffer_get() const;
void bind_compute_pipeline();
void update_dispatch_info();
render_graph::VKDispatchNode::CreateInfo &update_and_get_dispatch_info();
render_graph::VKResourceAccessInfo &update_and_get_access_info();
/**
* Update the give shader data with the current state of the context.
*/
void update_pipeline_data(render_graph::VKPipelineData &pipeline_data);
void bind_graphics_pipeline(const GPUPrimType prim_type,
const VKVertexAttributeObject &vertex_attribute_object);
@ -107,13 +112,6 @@ class VKContext : public Context, NonCopyable {
private:
void swap_buffers_pre_handler(const GHOST_VulkanSwapChainData &data);
void swap_buffers_post_handler();
/**
* Update the give shader data with the current state of the context.
*
* NOTE: Shader data structure is reused between render graph nodes.
*/
void update_pipeline_data(render_graph::VKPipelineData &pipeline_data);
};
BLI_INLINE bool operator==(const VKContext &a, const VKContext &b)

View File

@ -52,7 +52,7 @@ namespace blender::io::alembic {
/* Construct the depsgraph for exporting. */
static bool build_depsgraph(ExportJobData *job)
{
if (strlen(job->params.collection) > 0) {
if (job->params.collection[0]) {
Collection *collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(job->bmain, ID_GR, job->params.collection));
if (!collection) {

View File

@ -36,7 +36,7 @@ void exporter_main(bContext *C, const PLYExportParams &export_params)
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
if (strlen(export_params.collection) > 0) {
if (export_params.collection[0]) {
Collection *collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
if (!collection) {

View File

@ -142,7 +142,7 @@ void exporter_main(bContext *C, const STLExportParams &export_params)
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
if (strlen(export_params.collection) > 0) {
if (export_params.collection[0]) {
Collection *collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
if (!collection) {

View File

@ -93,6 +93,7 @@ set(SRC
intern/usd_capi_import.cc
intern/usd_hierarchy_iterator.cc
intern/usd_hook.cc
intern/usd_mesh_utils.cc
intern/usd_writer_abstract.cc
intern/usd_writer_armature.cc
intern/usd_writer_camera.cc
@ -134,6 +135,7 @@ set(SRC
intern/usd_hash_types.hh
intern/usd_hierarchy_iterator.hh
intern/usd_hook.hh
intern/usd_mesh_utils.hh
intern/usd_writer_abstract.hh
intern/usd_writer_armature.hh
intern/usd_writer_camera.hh

View File

@ -485,7 +485,7 @@ bool USD_export(bContext *C,
*
* Has to be done from main thread currently, as it may affect Main original data (e.g. when
* doing deferred update of the view-layers, see #112534 for details). */
if (strlen(job->params.collection) > 0) {
if (job->params.collection[0]) {
Collection *collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(job->bmain, ID_GR, job->params.collection));
if (!collection) {

View File

@ -0,0 +1,234 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_mesh_utils.hh"
#include "usd_hash_types.hh"
#include "BKE_attribute.hh"
#include "BKE_report.hh"
#include "BLI_color.hh"
#include "BLI_span.hh"
#include "DNA_mesh_types.h"
namespace blender::io::usd {
std::optional<eCustomDataType> convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type,
ReportList *reports)
{
static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
blender::Map<pxr::SdfValueTypeName, eCustomDataType> map;
map.add_new(pxr::SdfValueTypeNames->FloatArray, CD_PROP_FLOAT);
map.add_new(pxr::SdfValueTypeNames->Double, CD_PROP_FLOAT);
map.add_new(pxr::SdfValueTypeNames->IntArray, CD_PROP_INT32);
map.add_new(pxr::SdfValueTypeNames->Float2Array, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2dArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2fArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2hArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3dArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3fArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3hArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->Float3Array, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Color3fArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->Color3hArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->Color3dArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->StringArray, CD_PROP_STRING);
map.add_new(pxr::SdfValueTypeNames->BoolArray, CD_PROP_BOOL);
map.add_new(pxr::SdfValueTypeNames->QuatfArray, CD_PROP_QUATERNION);
map.add_new(pxr::SdfValueTypeNames->QuatdArray, CD_PROP_QUATERNION);
map.add_new(pxr::SdfValueTypeNames->QuathArray, CD_PROP_QUATERNION);
return map;
}();
const eCustomDataType *value = type_map.lookup_ptr(usd_type);
if (value == nullptr) {
BKE_reportf(reports,
RPT_WARNING,
"Unsupported type %s for mesh data",
usd_type.GetAsToken().GetText());
return std::nullopt;
}
return *value;
}
/* To avoid putting the templated method definition in the header file,
* it is necessary to define each of the possible template instantiations
* that we support. Ugly here, but it keeps the header looking clean.
*/
template pxr::VtArray<pxr::GfVec2f> get_prim_attribute_array<pxr::GfVec2f>(
const pxr::UsdGeomPrimvar &primvar, const double motionSampleTime, ReportList *reports);
template pxr::VtArray<pxr::GfVec3f> get_prim_attribute_array<pxr::GfVec3f>(
const pxr::UsdGeomPrimvar &primvar, const double motionSampleTime, ReportList *reports);
template pxr::VtArray<bool> get_prim_attribute_array<bool>(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports);
template pxr::VtArray<int> get_prim_attribute_array<int>(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports);
template pxr::VtArray<float> get_prim_attribute_array<float>(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports);
template<typename T>
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports)
{
pxr::VtArray<T> array;
pxr::VtValue primvar_val;
if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) {
BKE_reportf(reports,
RPT_WARNING,
"USD Import: unable to get array values for primvar '%s'",
primvar.GetName().GetText());
return array;
}
if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
BKE_reportf(reports,
RPT_WARNING,
"USD Import: can't cast attribute '%s' to array",
primvar.GetName().GetText());
return array;
}
array = primvar_val.Cast<pxr::VtArray<T>>().template UncheckedGet<pxr::VtArray<T>>();
return array;
}
void read_color_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &primvar,
double motion_sample_time,
ReportList *reports,
bool is_left_handed)
{
if (!(mesh && primvar && primvar.HasValue())) {
return;
}
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(
primvar, motion_sample_time, reports);
if (usd_colors.empty()) {
return;
}
pxr::TfToken interp = primvar.GetInterpolation();
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->corners_num) ||
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->corners_num) ||
(interp == pxr::UsdGeomTokens->vertex && usd_colors.size() != mesh->verts_num) ||
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num))
{
BKE_reportf(
reports,
RPT_WARNING,
"USD Import: color attribute value '%s' count inconsistent with interpolation type",
primvar.GetName().GetText());
return;
}
const StringRef primvar_name(primvar.GetBaseName().GetString());
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::AttrDomain color_domain = bke::AttrDomain::Point;
if (ELEM(interp,
pxr::UsdGeomTokens->varying,
pxr::UsdGeomTokens->faceVarying,
pxr::UsdGeomTokens->uniform))
{
color_domain = bke::AttrDomain::Corner;
}
bke::SpanAttributeWriter<ColorGeometry4f> color_data;
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name,
color_domain);
if (!color_data) {
BKE_reportf(reports,
RPT_WARNING,
"USD Import: couldn't add color attribute '%s'",
primvar.GetBaseName().GetText());
return;
}
if (ELEM(interp, pxr::UsdGeomTokens->constant)) {
/* For situations where there's only a single item, flood fill the object. */
color_data.span.fill(
ColorGeometry4f(usd_colors[0][0], usd_colors[0][1], usd_colors[0][2], 1.0f));
}
/* Check for situations that allow for a straight-forward copy by index. */
else if (interp == pxr::UsdGeomTokens->vertex ||
(interp == pxr::UsdGeomTokens->faceVarying && !is_left_handed))
{
for (int i = 0; i < usd_colors.size(); i++) {
ColorGeometry4f color = ColorGeometry4f(
usd_colors[i][0], usd_colors[i][1], usd_colors[i][2], 1.0f);
color_data.span[i] = color;
}
}
else {
/* Catch all for the remaining cases. */
/* Special case: we will expand uniform color into corner color.
* Uniforms in USD come through as single colors, face-varying. Since Blender does not
* support this particular combination for paintable color attributes, we convert the type
* here to make sure that the user gets the same visual result.
*/
const OffsetIndices faces = mesh->faces();
const Span<int> corner_verts = mesh->corner_verts();
for (const int i : faces.index_range()) {
const IndexRange face = faces[i];
for (int j = 0; j < face.size(); ++j) {
int loop_index = face[j];
/* Default for constant interpolation. */
int usd_index = 0;
if (interp == pxr::UsdGeomTokens->vertex) {
usd_index = corner_verts[loop_index];
}
else if (interp == pxr::UsdGeomTokens->faceVarying) {
usd_index = face.start();
if (is_left_handed) {
usd_index += face.size() - 1 - j;
}
else {
usd_index += j;
}
}
else if (interp == pxr::UsdGeomTokens->uniform) {
/* Uniform varying uses the face index. */
usd_index = i;
}
if (usd_index >= usd_colors.size()) {
continue;
}
ColorGeometry4f color = ColorGeometry4f(
usd_colors[usd_index][0], usd_colors[usd_index][1], usd_colors[usd_index][2], 1.0f);
color_data.span[loop_index] = color;
}
}
}
color_data.finish();
}
} // namespace blender::io::usd

View File

@ -0,0 +1,37 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_mesh.hh"
#include "BKE_report.hh"
#include "DNA_customdata_types.h"
#include "usd.hh"
#include <pxr/base/vt/array.h>
#include <pxr/usd/sdf/valueTypeName.h>
#include <pxr/usd/usdGeom/primvar.h>
#include <optional>
struct Mesh;
struct ReportList;
namespace blender::io::usd {
std::optional<eCustomDataType> convert_usd_type_to_blender(const pxr::SdfValueTypeName usd_type,
ReportList *reports);
template<typename T>
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports);
void read_color_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &color_primvar,
double motion_sample_time,
ReportList *reports,
bool is_left_handed);
} // namespace blender::io::usd

View File

@ -7,6 +7,7 @@
#include "usd_reader_mesh.hh"
#include "usd_hash_types.hh"
#include "usd_mesh_utils.hh"
#include "usd_reader_material.hh"
#include "usd_skel_convert.hh"
@ -158,54 +159,6 @@ USDMeshReader::USDMeshReader(const pxr::UsdPrim &prim,
{
}
static std::optional<eCustomDataType> convert_usd_type_to_blender(
const pxr::SdfValueTypeName usd_type, ReportList *reports)
{
static const blender::Map<pxr::SdfValueTypeName, eCustomDataType> type_map = []() {
blender::Map<pxr::SdfValueTypeName, eCustomDataType> map;
map.add_new(pxr::SdfValueTypeNames->FloatArray, CD_PROP_FLOAT);
map.add_new(pxr::SdfValueTypeNames->Double, CD_PROP_FLOAT);
map.add_new(pxr::SdfValueTypeNames->IntArray, CD_PROP_INT32);
map.add_new(pxr::SdfValueTypeNames->Float2Array, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2dArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2fArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord2hArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3dArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3fArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->TexCoord3hArray, CD_PROP_FLOAT2);
map.add_new(pxr::SdfValueTypeNames->Float3Array, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Point3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Normal3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3fArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3hArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Vector3dArray, CD_PROP_FLOAT3);
map.add_new(pxr::SdfValueTypeNames->Color3fArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->Color3hArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->Color3dArray, CD_PROP_COLOR);
map.add_new(pxr::SdfValueTypeNames->StringArray, CD_PROP_STRING);
map.add_new(pxr::SdfValueTypeNames->BoolArray, CD_PROP_BOOL);
map.add_new(pxr::SdfValueTypeNames->QuatfArray, CD_PROP_QUATERNION);
map.add_new(pxr::SdfValueTypeNames->QuatdArray, CD_PROP_QUATERNION);
map.add_new(pxr::SdfValueTypeNames->QuathArray, CD_PROP_QUATERNION);
return map;
}();
const eCustomDataType *value = type_map.lookup_ptr(usd_type);
if (value == nullptr) {
BKE_reportf(reports,
RPT_WARNING,
"Unsupported type %s for mesh data",
usd_type.GetAsToken().GetText());
return std::nullopt;
}
return *value;
}
static const std::optional<bke::AttrDomain> convert_usd_varying_to_blender(
const pxr::TfToken usd_domain, ReportList *reports)
{
@ -353,150 +306,6 @@ void USDMeshReader::read_mpolys(Mesh *mesh)
bke::mesh_calc_edges(*mesh, false, false);
}
template<typename T>
pxr::VtArray<T> get_prim_attribute_array(const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime,
ReportList *reports)
{
pxr::VtArray<T> array;
pxr::VtValue primvar_val;
if (!primvar.ComputeFlattened(&primvar_val, motionSampleTime)) {
BKE_reportf(reports,
RPT_WARNING,
"Unable to get array values for primvar %s",
primvar.GetName().GetText());
return array;
}
if (!primvar_val.CanCast<pxr::VtArray<T>>()) {
BKE_reportf(reports,
RPT_WARNING,
"USD Import: can't cast attribute '%s' to array",
primvar.GetName().GetText());
return array;
}
array = primvar_val.Cast<pxr::VtArray<T>>().template UncheckedGet<pxr::VtArray<T>>();
return array;
}
void USDMeshReader::read_color_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime)
{
if (!(mesh && primvar && primvar.HasValue())) {
return;
}
pxr::VtArray<pxr::GfVec3f> usd_colors = get_prim_attribute_array<pxr::GfVec3f>(
primvar, motionSampleTime, reports());
if (usd_colors.empty()) {
return;
}
pxr::TfToken interp = primvar.GetInterpolation();
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->corners_num) ||
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->verts_num) ||
(interp == pxr::UsdGeomTokens->vertex && usd_colors.size() != mesh->verts_num) ||
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->faces_num))
{
BKE_reportf(
reports(),
RPT_WARNING,
"USD Import: color attribute value '%s' count inconsistent with interpolation type",
primvar.GetName().GetText());
return;
}
const StringRef primvar_name(primvar.GetBaseName().GetString());
bke::MutableAttributeAccessor attributes = mesh->attributes_for_write();
bke::AttrDomain color_domain = bke::AttrDomain::Point;
if (ELEM(interp, pxr::UsdGeomTokens->faceVarying, pxr::UsdGeomTokens->uniform)) {
color_domain = bke::AttrDomain::Corner;
}
bke::SpanAttributeWriter<ColorGeometry4f> color_data;
color_data = attributes.lookup_or_add_for_write_only_span<ColorGeometry4f>(primvar_name,
color_domain);
if (!color_data) {
BKE_reportf(reports(),
RPT_WARNING,
"USD Import: couldn't add color attribute '%s'",
primvar.GetBaseName().GetText());
return;
}
if (ELEM(interp, pxr::UsdGeomTokens->constant)) {
/* For situations where there's only a single item, flood fill the object. */
color_data.span.fill(
ColorGeometry4f(usd_colors[0][0], usd_colors[0][1], usd_colors[0][2], 1.0f));
}
/* Check for situations that allow for a straight-forward copy by index. */
else if (interp == pxr::UsdGeomTokens->vertex || interp == pxr::UsdGeomTokens->varying ||
(interp == pxr::UsdGeomTokens->faceVarying && !is_left_handed_))
{
for (int i = 0; i < usd_colors.size(); i++) {
ColorGeometry4f color = ColorGeometry4f(
usd_colors[i][0], usd_colors[i][1], usd_colors[i][2], 1.0f);
color_data.span[i] = color;
}
}
else {
/* Catch all for the remaining cases. */
/* Special case: we will expand uniform color into corner color.
* Uniforms in USD come through as single colors, face-varying. Since Blender does not
* support this particular combination for paintable color attributes, we convert the type
* here to make sure that the user gets the same visual result.
*/
const OffsetIndices faces = mesh->faces();
const Span<int> corner_verts = mesh->corner_verts();
for (const int i : faces.index_range()) {
const IndexRange face = faces[i];
for (int j = 0; j < face.size(); ++j) {
int loop_index = face[j];
/* Default for constant interpolation. */
int usd_index = 0;
if (interp == pxr::UsdGeomTokens->vertex) {
usd_index = corner_verts[loop_index];
}
else if (interp == pxr::UsdGeomTokens->faceVarying) {
usd_index = face.start();
if (is_left_handed_) {
usd_index += face.size() - 1 - j;
}
else {
usd_index += j;
}
}
else if (interp == pxr::UsdGeomTokens->uniform) {
/* Uniform varying uses the face index. */
usd_index = i;
}
if (usd_index >= usd_colors.size()) {
continue;
}
ColorGeometry4f color = ColorGeometry4f(
usd_colors[usd_index][0], usd_colors[usd_index][1], usd_colors[usd_index][2], 1.0f);
color_data.span[loop_index] = color;
}
}
}
color_data.finish();
}
void USDMeshReader::read_uv_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime)
@ -956,7 +765,7 @@ void USDMeshReader::read_custom_data(const ImportSettings *settings,
active_color_name = name;
}
read_color_data_primvar(mesh, pv, motionSampleTime);
read_color_data_primvar(mesh, pv, motionSampleTime, reports(), is_left_handed_);
}
}
@ -1113,6 +922,9 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
* the topology is consistent, as in the Alembic importer. */
ImportSettings settings;
if (settings_) {
settings.validate_meshes = settings_->validate_meshes;
}
settings.read_flag |= params.read_flags;
if (topology_changed(existing_mesh, params.motion_sample_time)) {
@ -1139,6 +951,12 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
}
}
if (settings.validate_meshes) {
if (BKE_mesh_validate(active_mesh, false, false)) {
BKE_reportf(reports(), RPT_INFO, "Fixed mesh for prim: %s", mesh_prim_.GetPath().GetText());
}
}
return active_mesh;
}

View File

@ -94,10 +94,6 @@ class USDMeshReader : public USDGeomReader {
double motionSampleTime,
bool new_mesh);
void read_color_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &color_primvar,
const double motionSampleTime);
void read_uv_data_primvar(Mesh *mesh,
const pxr::UsdGeomPrimvar &primvar,
const double motionSampleTime);

View File

@ -2,15 +2,19 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_attribute.hh"
#include "BKE_geometry_set.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_windowmanager_types.h"
#include "usd_hash_types.hh"
#include "usd_mesh_utils.hh"
#include "usd_reader_shape.hh"
#include <pxr/usd/usdGeom/capsule.h>
@ -24,6 +28,11 @@
#include <pxr/usdImaging/usdImaging/cylinderAdapter.h>
#include <pxr/usdImaging/usdImaging/sphereAdapter.h>
namespace usdtokens {
/* Materials */
static const pxr::TfToken displayColor("displayColor", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace blender::io::usd {
USDShapeReader::USDShapeReader(const pxr::UsdPrim &prim,
@ -134,8 +143,8 @@ Mesh *USDShapeReader::read_mesh(Mesh *existing_mesh,
}
/* Should have a good set of data by this point-- copy over. */
Mesh *active_mesh = mesh_from_prim(
existing_mesh, params.motion_sample_time, face_indices, face_counts);
Mesh *active_mesh = mesh_from_prim(existing_mesh, params, face_indices, face_counts);
if (active_mesh == existing_mesh) {
return existing_mesh;
}
@ -170,14 +179,74 @@ void USDShapeReader::read_geometry(bke::GeometrySet &geometry_set,
}
}
void USDShapeReader::apply_primvars_to_mesh(Mesh *mesh, const double motionSampleTime) const
{
/* TODO: also handle the displayOpacity primvar. */
if (!mesh || !prim_) {
return;
}
pxr::UsdGeomPrimvarsAPI pv_api = pxr::UsdGeomPrimvarsAPI(prim_);
std::vector<pxr::UsdGeomPrimvar> primvars = pv_api.GetPrimvarsWithValues();
pxr::TfToken active_color_name;
for (pxr::UsdGeomPrimvar &pv : primvars) {
if (!pv.HasValue()) {
BKE_reportf(reports(),
RPT_WARNING,
"Skipping primvar %s, mesh %s -- no value",
pv.GetName().GetText(),
&mesh->id.name[2]);
continue;
}
if (!pv.GetAttr().GetTypeName().IsArray()) {
/* Non-array attributes are technically improper USD. */
continue;
}
const pxr::TfToken name = pv.StripPrimvarsName(pv.GetPrimvarName());
/* Skip reading primvars that have been read before and are not time varying. */
if (primvar_time_varying_map_.contains(name) && !primvar_time_varying_map_.lookup(name)) {
continue;
}
const pxr::SdfValueTypeName sdf_type = pv.GetTypeName();
const std::optional<eCustomDataType> type = convert_usd_type_to_blender(sdf_type, reports());
if (type == CD_PROP_COLOR) {
/* Set the active color name to 'displayColor', if a color primvar
* with this name exists. Otherwise, use the name of the first
* color primvar we find for the active color. */
if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
active_color_name = name;
}
read_color_data_primvar(mesh, pv, motionSampleTime, reports(), false);
/* Record whether the primvar attribute might be time varying. */
if (!primvar_time_varying_map_.contains(name)) {
primvar_time_varying_map_.add(name, pv.ValueMightBeTimeVarying());
}
}
}
if (!active_color_name.IsEmpty()) {
BKE_id_attributes_default_color_set(&mesh->id, active_color_name.GetText());
BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
}
}
Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh,
double motionSampleTime,
const USDMeshReadParams params,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const
{
pxr::VtVec3fArray positions;
if (!read_mesh_values(motionSampleTime, positions, face_indices, face_counts)) {
if (!read_mesh_values(params.motion_sample_time, positions, face_indices, face_counts)) {
return existing_mesh;
}
@ -203,11 +272,25 @@ Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh,
vert_positions[i][2] = positions[i][2];
}
if (params.read_flags & MOD_MESHSEQ_READ_COLOR) {
if (active_mesh != existing_mesh) {
/* Clear the primvar map to force attributes to be reloaded. */
this->primvar_time_varying_map_.clear();
}
apply_primvars_to_mesh(active_mesh, params.motion_sample_time);
}
return active_mesh;
}
bool USDShapeReader::is_time_varying()
{
for (const bool animating_flag : primvar_time_varying_map_.values()) {
if (animating_flag) {
return true;
}
}
if (prim_.IsA<pxr::UsdGeomCapsule>()) {
pxr::UsdGeomCapsule geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||

View File

@ -16,6 +16,11 @@ namespace blender::io::usd {
* as the GL viewport to generate geometry for each of the supported types.
*/
class USDShapeReader : public USDGeomReader {
/* A cache to record whether a given primvar is time-varying, so that static primvars are not
* read more than once when the mesh is evaluated for animation by the cache file modifier.
* The map is mutable so that it can be updated in const functions. */
mutable blender::Map<const pxr::TfToken, bool> primvar_time_varying_map_;
private:
/* Template required to read mesh information out of Shape prims,
* as each prim type has a separate subclass. */
@ -32,10 +37,12 @@ class USDShapeReader : public USDGeomReader {
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const;
void apply_primvars_to_mesh(Mesh *mesh, double motionSampleTime) const;
/* Read the pxr:UsdGeomMesh values and convert them to a Blender Mesh,
* also returning face_indices and counts for further loop processing. */
Mesh *mesh_from_prim(Mesh *existing_mesh,
double motionSampleTime,
USDMeshReadParams params,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const;
@ -52,6 +59,8 @@ class USDShapeReader : public USDGeomReader {
USDMeshReadParams /*params*/,
const char ** /*err_str*/) override;
/* Returns the generated mesh might be affected by time-varying attributes.
* This assumes mesh_from_prim() has been called. */
bool is_time_varying();
virtual bool topology_changed(const Mesh * /*existing_mesh*/,

View File

@ -599,7 +599,7 @@ static void export_in_memory_texture(Image *ima,
char image_abs_path[FILE_MAX];
char file_name[FILE_MAX];
if (strlen(ima->filepath) > 0) {
if (ima->filepath[0]) {
get_absolute_path(ima, image_abs_path);
BLI_path_split_file_part(image_abs_path, file_name, FILE_MAX);
}
@ -845,7 +845,7 @@ static std::string get_tex_image_asset_filepath(const USDExporterContext &usd_ex
std::string path;
if (strlen(ima->filepath) > 0) {
if (ima->filepath[0]) {
/* Get absolute path. */
path = get_tex_image_asset_filepath(ima);
}

View File

@ -332,7 +332,7 @@ void exporter_main(bContext *C, const OBJExportParams &export_params)
ed::object::mode_set(C, OB_MODE_OBJECT);
Collection *collection = nullptr;
if (strlen(export_params.collection) > 0) {
if (export_params.collection[0]) {
Main *bmain = CTX_data_main(C);
collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(bmain, ID_GR, export_params.collection));

View File

@ -26,7 +26,6 @@ typedef enum eObjectMode {
OB_MODE_WEIGHT_GPENCIL_LEGACY = 1 << 10,
OB_MODE_VERTEX_GPENCIL_LEGACY = 1 << 11,
OB_MODE_SCULPT_CURVES = 1 << 12,
OB_MODE_PAINT_GREASE_PENCIL = 1 << 13,
} eObjectMode;
/** #Object.dt, #View3DShading.type */
@ -60,5 +59,4 @@ typedef enum eDrawType {
#define OB_MODE_ALL_MODE_DATA \
(OB_MODE_EDIT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_SCULPT | OB_MODE_POSE | \
OB_MODE_PAINT_GPENCIL_LEGACY | OB_MODE_EDIT_GPENCIL_LEGACY | OB_MODE_SCULPT_GPENCIL_LEGACY | \
OB_MODE_WEIGHT_GPENCIL_LEGACY | OB_MODE_VERTEX_GPENCIL_LEGACY | OB_MODE_SCULPT_CURVES | \
OB_MODE_PAINT_GREASE_PENCIL)
OB_MODE_WEIGHT_GPENCIL_LEGACY | OB_MODE_VERTEX_GPENCIL_LEGACY | OB_MODE_SCULPT_CURVES)

View File

@ -89,11 +89,6 @@ const EnumPropertyItem rna_enum_object_mode_items[] = {
"Vertex Paint",
"Grease Pencil Vertex Paint Strokes"},
{OB_MODE_SCULPT_CURVES, "SCULPT_CURVES", ICON_SCULPTMODE_HLT, "Sculpt Mode", ""},
{OB_MODE_PAINT_GREASE_PENCIL,
"PAINT_GREASE_PENCIL",
ICON_GREASEPENCIL,
"Draw Mode",
"Paint Grease Pencil Strokes"},
{0, nullptr, 0, nullptr, nullptr},
};

View File

@ -491,6 +491,7 @@ PanelType *modifier_subpanel_register(ARegionType *region_type,
{
PanelType *panel_type = MEM_cnew<PanelType>(__func__);
BLI_assert(parent != nullptr);
SNPRINTF(panel_type->idname, "%s_%s", parent->idname, name);
STRNCPY(panel_type->label, label);
STRNCPY(panel_type->context, "modifier");
@ -502,7 +503,6 @@ PanelType *modifier_subpanel_register(ARegionType *region_type,
panel_type->poll = modifier_ui_poll;
panel_type->flag = PANEL_TYPE_DEFAULT_CLOSED;
BLI_assert(parent != nullptr);
STRNCPY(panel_type->parent_id, parent->idname);
panel_type->parent = parent;
BLI_addtail(&parent->children, BLI_genericNodeN(panel_type));

View File

@ -21,7 +21,7 @@ static void node_declare(NodeDeclarationBuilder &b)
.subtype(PROP_DISTANCE);
b.add_input<decl::Int>("Band Width")
.default_value(3)
.min(0)
.min(1)
.max(100)
.description("Width of the active voxel surface, in voxels");
b.add_output<decl::Float>("SDF Grid");
@ -41,7 +41,7 @@ static void node_geo_exec(GeoNodeExecParams params)
mesh->corner_verts(),
mesh->corner_tris(),
params.extract_input<float>("Voxel Size"),
params.extract_input<int>("Band Width"));
std::max(1, params.extract_input<int>("Band Width")));
params.set_output("SDF Grid", std::move(grid));
#else
node_geo_exec_with_missing_openvdb(params);

View File

@ -1146,7 +1146,7 @@ static void points_in_planes_fn(const float co[3], int i, int j, int k, void *us
PyDoc_STRVAR(
/* Wrap. */
M_Geometry_points_in_planes_doc,
".. function:: points_in_planes(planes, epsilon_coplanar=1e-4f, epsilon_isect=1e-6f)\n"
".. function:: points_in_planes(planes, epsilon_coplanar=1e-4, epsilon_isect=1e-6)\n"
"\n"
" Returns a list of points inside all planes given and a list of index values for "
"the planes used.\n"

View File

@ -599,7 +599,7 @@ class RetimingRange {
: start(start_frame), end(end_frame), speed(speed), type(type)
{
if (type == TRANSITION) {
speed = 1.0f;
this->speed = 1.0f;
claculate_speed_table_from_seq(seq);
}
}

View File

@ -253,6 +253,7 @@ PanelType *shaderfx_subpanel_register(ARegionType *region_type,
{
PanelType *panel_type = static_cast<PanelType *>(MEM_callocN(sizeof(PanelType), __func__));
BLI_assert(parent != nullptr);
SNPRINTF(panel_type->idname, "%s_%s", parent->idname, name);
STRNCPY(panel_type->label, label);
STRNCPY(panel_type->context, "shaderfx");
@ -263,7 +264,6 @@ PanelType *shaderfx_subpanel_register(ARegionType *region_type,
panel_type->poll = shaderfx_ui_poll;
panel_type->flag = PANEL_TYPE_DEFAULT_CLOSED;
BLI_assert(parent != nullptr);
STRNCPY(panel_type->parent_id, parent->idname);
panel_type->parent = parent;
BLI_addtail(&parent->children, BLI_genericNodeN(panel_type));

View File

@ -349,8 +349,8 @@ int main(int argc,
#ifdef BUILD_DATE
{
time_t temp_time = build_commit_timestamp;
tm *tm = gmtime(&temp_time);
const time_t temp_time = build_commit_timestamp;
const tm *tm = gmtime(&temp_time);
if (LIKELY(tm)) {
strftime(build_commit_date, sizeof(build_commit_date), "%Y-%m-%d", tm);
strftime(build_commit_time, sizeof(build_commit_time), "%H:%M", tm);