WIP: Brush assets project #106303
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, '/', '\\');
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(®ion->handlers, keymap);
|
||||
|
||||
keymap = WM_keymap_ensure(
|
||||
wm->defaultconf, "Grease Pencil Weight Paint", SPACE_EMPTY, RGN_TYPE_WINDOW);
|
||||
WM_event_add_keymap_handler(®ion->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(®ion->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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -323,6 +323,8 @@ void PackIsland::calculate_pre_rotation_(const UVPackIsland_Params ¶ms)
|
|||
|
||||
void PackIsland::finalize_geometry_(const UVPackIsland_Params ¶ms, 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 ¶ms, 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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -30,6 +30,7 @@ enum class VKNodeType {
|
|||
COPY_BUFFER_TO_IMAGE,
|
||||
BLIT_IMAGE,
|
||||
DISPATCH,
|
||||
DISPATCH_INDIRECT,
|
||||
SYNCHRONIZATION,
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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_;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() ||
|
||||
|
|
|
@ -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*/,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue