WIP: UI: Status Bar Keymaps When Idle #121059
|
@ -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
|
||||
|
||||
|
@ -8091,6 +8143,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),
|
||||
]},
|
||||
)
|
||||
|
||||
|
@ -8785,6 +8838,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
|
||||
|
@ -1580,6 +1582,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,
|
||||
)
|
||||
|
|
|
@ -2678,9 +2678,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
|
||||
|
@ -7944,6 +7965,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)
|
||||
|
||||
|
@ -8494,7 +8516,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)
|
||||
|
|
|
@ -76,7 +76,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
|
||||
|
@ -2110,6 +2110,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
|
||||
|
||||
|
@ -2136,7 +2139,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="")
|
||||
|
@ -2160,10 +2163,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.
|
||||
*/
|
||||
|
|
|
@ -84,6 +84,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) {
|
||||
|
|
|
@ -811,7 +811,7 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
|||
return true;
|
||||
}
|
||||
|
||||
const GVArray src = *src_attributes.lookup(id, meta_data.domain);
|
||||
GVArray src = *src_attributes.lookup(id, meta_data.domain);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
id, meta_data.domain, meta_data.data_type);
|
||||
attributes.append({std::move(src), meta_data, std::move(dst)});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -251,6 +251,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;
|
||||
|
@ -532,8 +533,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:
|
||||
|
@ -615,6 +614,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:
|
||||
|
@ -627,8 +628,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;
|
||||
}
|
||||
|
@ -728,12 +727,7 @@ void BKE_paint_runtime_init(const ToolSettings *ts, Paint *paint)
|
|||
}
|
||||
else if (ts->gp_paint && paint == &ts->gp_paint->paint) {
|
||||
paint->runtime.tool_offset = offsetof(Brush, gpencil_tool);
|
||||
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.tool_offset = offsetof(Brush, gpencil_vertex_tool);
|
||||
|
@ -1115,7 +1109,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;
|
||||
}
|
||||
|
||||
|
@ -4789,6 +4783,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);
|
||||
|
|
|
@ -526,11 +526,19 @@ static void write_lightcache_texture(BlendWriter *writer, LightCacheTexture *tex
|
|||
{
|
||||
if (tex->data) {
|
||||
size_t data_size = tex->components * tex->tex_size[0] * tex->tex_size[1] * tex->tex_size[2];
|
||||
if (tex->data_type == LIGHTCACHETEX_FLOAT) {
|
||||
data_size *= sizeof(float);
|
||||
}
|
||||
else if (tex->data_type == LIGHTCACHETEX_UINT) {
|
||||
data_size *= sizeof(uint);
|
||||
switch (tex->data_type) {
|
||||
case LIGHTCACHETEX_BYTE:
|
||||
data_size *= sizeof(uint8_t);
|
||||
break;
|
||||
case LIGHTCACHETEX_UINT:
|
||||
data_size *= sizeof(uint32_t);
|
||||
break;
|
||||
case LIGHTCACHETEX_FLOAT:
|
||||
data_size *= sizeof(float);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
break;
|
||||
}
|
||||
|
||||
/* FIXME: We can't save more than what 32bit systems can handle.
|
||||
|
@ -565,15 +573,20 @@ static void direct_link_lightcache_texture(BlendDataReader *reader, LightCacheTe
|
|||
int data_size = lctex->components * lctex->tex_size[0] * lctex->tex_size[1] *
|
||||
lctex->tex_size[2];
|
||||
|
||||
if (lctex->data_type == LIGHTCACHETEX_FLOAT) {
|
||||
BLO_read_float_array(reader, data_size, (float **)&lctex->data);
|
||||
}
|
||||
else if (lctex->data_type == LIGHTCACHETEX_UINT) {
|
||||
BLO_read_uint32_array(reader, data_size, (uint **)&lctex->data);
|
||||
}
|
||||
else {
|
||||
BLI_assert_unreachable();
|
||||
lctex->data = nullptr;
|
||||
switch (lctex->data_type) {
|
||||
case LIGHTCACHETEX_BYTE:
|
||||
BLO_read_uint8_array(reader, data_size, (uint8_t **)&lctex->data);
|
||||
break;
|
||||
case LIGHTCACHETEX_UINT:
|
||||
BLO_read_uint32_array(reader, data_size, (uint32_t **)&lctex->data);
|
||||
break;
|
||||
case LIGHTCACHETEX_FLOAT:
|
||||
BLO_read_float_array(reader, data_size, (float **)&lctex->data);
|
||||
break;
|
||||
default:
|
||||
BLI_assert_unreachable();
|
||||
lctex->data = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -585,8 +598,8 @@ static void direct_link_lightcache_texture(BlendDataReader *reader, LightCacheTe
|
|||
void EEVEE_lightcache_blend_read_data(BlendDataReader *reader, LightCache *cache)
|
||||
{
|
||||
cache->flag &= ~LIGHTCACHE_NOT_USABLE;
|
||||
direct_link_lightcache_texture(reader, &cache->cube_tx);
|
||||
direct_link_lightcache_texture(reader, &cache->grid_tx);
|
||||
direct_link_lightcache_texture(reader, &cache->cube_tx);
|
||||
|
||||
if (cache->cube_mips) {
|
||||
BLO_read_struct_array(reader, LightCacheTexture, cache->mips_len, &cache->cube_mips);
|
||||
|
@ -595,8 +608,8 @@ void EEVEE_lightcache_blend_read_data(BlendDataReader *reader, LightCache *cache
|
|||
}
|
||||
}
|
||||
|
||||
BLO_read_struct_array(reader, LightGridCache, cache->grid_len, &cache->cube_data);
|
||||
BLO_read_struct_array(reader, LightProbeCache, cache->cube_len, &cache->grid_data);
|
||||
BLO_read_struct_array(reader, LightGridCache, cache->grid_len, &cache->grid_data);
|
||||
BLO_read_struct_array(reader, LightProbeCache, cache->cube_len, &cache->cube_data);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -110,7 +110,7 @@ void main()
|
|||
float cone_cos = cone_cosine_from_roughness(mip_roughness_clamped);
|
||||
|
||||
vec3 out_direction = sphere_probe_texel_to_direction(
|
||||
out_local_texel, out_texel_area, sample_coord);
|
||||
vec2(out_local_texel), out_texel_area, sample_coord);
|
||||
out_direction = normalize(out_direction);
|
||||
|
||||
mat3x3 basis = tangent_basis(out_direction);
|
||||
|
|
|
@ -27,24 +27,24 @@ SphereProbeUvArea reinterpret_as_atlas_coord(ivec4 packed_coord)
|
|||
|
||||
/* local_texel is the texel coordinate inside the probe area [0..texel_area.extent) range.
|
||||
* Returned vector is not normalized. */
|
||||
vec3 sphere_probe_texel_to_direction(ivec2 local_texel,
|
||||
vec3 sphere_probe_texel_to_direction(vec2 local_texel,
|
||||
SphereProbePixelArea texel_area,
|
||||
SphereProbeUvArea uv_area,
|
||||
out vec2 sampling_uv)
|
||||
{
|
||||
/* Texel in probe atlas. */
|
||||
ivec2 texel = local_texel + texel_area.offset;
|
||||
vec2 texel = local_texel + vec2(texel_area.offset);
|
||||
/* UV in sampling area. No half pixel bias to texel as the octahedral map edges area lined up
|
||||
* with texel center. Note that we don't use the last row & column of pixel, hence the -2 instead
|
||||
* of -1. See sphere_probe_miplvl_scale_bias. */
|
||||
sampling_uv = vec2(texel) / vec2(texel_area.extent - 2);
|
||||
sampling_uv = texel / vec2(texel_area.extent - 2);
|
||||
/* Direction in world space. */
|
||||
return octahedral_uv_to_direction(sampling_uv);
|
||||
}
|
||||
|
||||
/* local_texel is the texel coordinate inside the probe area [0..texel_area.extent) range.
|
||||
* Returned vector is not normalized. */
|
||||
vec3 sphere_probe_texel_to_direction(ivec2 local_texel,
|
||||
vec3 sphere_probe_texel_to_direction(vec2 local_texel,
|
||||
SphereProbePixelArea texel_area,
|
||||
SphereProbeUvArea uv_area)
|
||||
{
|
||||
|
|
|
@ -28,40 +28,58 @@ float octahedral_texel_solid_angle(ivec2 local_texel,
|
|||
/* Do not weight these border pixels that are redundant. */
|
||||
return 0.0;
|
||||
}
|
||||
/* Since we are pouting texel centers on the edges of the octahedron, the shape of a texel can be
|
||||
/* Since we are putting texel centers on the edges of the octahedron, the shape of a texel can be
|
||||
* anything from a simple quad (at the Z=0 poles), to a 4 pointed start (at the Z=+-1 poles)
|
||||
* passing by arrow tail shapes (at the X=0 and Y=0 edges). So while it would be more correct to
|
||||
* account for all these shapes (using 8 triangles), it proves to be quite involved with all the
|
||||
* corner cases. Instead, we compute the area as if the texels were not aligned with the edges.
|
||||
* This simplify things at the cost of making the weighting a tiny bit off for every pixels.
|
||||
* The sum of all texels is still giving 4 PI. */
|
||||
vec3 v00 = sphere_probe_texel_to_direction(local_texel + ivec2(-1, -1), write_co, sample_co);
|
||||
vec3 v10 = sphere_probe_texel_to_direction(local_texel + ivec2(+0, -1), write_co, sample_co);
|
||||
vec3 v20 = sphere_probe_texel_to_direction(local_texel + ivec2(-1, -1), write_co, sample_co);
|
||||
vec3 v01 = sphere_probe_texel_to_direction(local_texel + ivec2(-1, +0), write_co, sample_co);
|
||||
vec3 v11 = sphere_probe_texel_to_direction(local_texel + ivec2(+0, +0), write_co, sample_co);
|
||||
vec3 v21 = sphere_probe_texel_to_direction(local_texel + ivec2(+1, +0), write_co, sample_co);
|
||||
vec3 v02 = sphere_probe_texel_to_direction(local_texel + ivec2(-1, +1), write_co, sample_co);
|
||||
vec3 v12 = sphere_probe_texel_to_direction(local_texel + ivec2(+0, +1), write_co, sample_co);
|
||||
vec3 v22 = sphere_probe_texel_to_direction(local_texel + ivec2(+1, +1), write_co, sample_co);
|
||||
* passing by arrow tail shapes (at the X=0 and Y=0 edges).
|
||||
*
|
||||
* But we can leverage the symetries of the octahedral mapping. Given that all oddly shaped
|
||||
* texels are on the X=0 and Y=0 planes, we first fold all texels to the first quadrant.
|
||||
*
|
||||
* The texel footprint clipped to a quadrant is a well defined spherical quad. We then multiply
|
||||
* by the number of clipped shape the real texel sustains. This number is 2 at the X=0 and Y=0
|
||||
* edges (the arrow shaped pixels) and 4 at the Z=+-1 poles (4 pointed start shaped pixels).
|
||||
*
|
||||
* The sum of all texels solid angle should be 4 PI (area of sphere). */
|
||||
|
||||
/* Wrap to bottom left quadrant. */
|
||||
int half_size = write_co.extent >> 1;
|
||||
int padded_size = write_co.extent - 2;
|
||||
ivec2 wrapped_texel;
|
||||
wrapped_texel.x = (local_texel.x >= half_size) ? (padded_size - local_texel.x) : local_texel.x;
|
||||
wrapped_texel.y = (local_texel.y >= half_size) ? (padded_size - local_texel.y) : local_texel.y;
|
||||
|
||||
vec2 texel_corner_v00 = vec2(wrapped_texel) + vec2(-0.5, -0.5);
|
||||
vec2 texel_corner_v10 = vec2(wrapped_texel) + vec2(+0.5, -0.5);
|
||||
vec2 texel_corner_v01 = vec2(wrapped_texel) + vec2(-0.5, +0.5);
|
||||
vec2 texel_corner_v11 = vec2(wrapped_texel) + vec2(+0.5, +0.5);
|
||||
/* Clamp to well defined shape in spherical domain. */
|
||||
texel_corner_v00 = clamp(texel_corner_v00, vec2(0.0), vec2(half_size - 1));
|
||||
texel_corner_v10 = clamp(texel_corner_v10, vec2(0.0), vec2(half_size - 1));
|
||||
texel_corner_v01 = clamp(texel_corner_v01, vec2(0.0), vec2(half_size - 1));
|
||||
texel_corner_v11 = clamp(texel_corner_v11, vec2(0.0), vec2(half_size - 1));
|
||||
/* Convert to point on sphere. */
|
||||
vec3 v00 = sphere_probe_texel_to_direction(texel_corner_v00, write_co, sample_co);
|
||||
vec3 v10 = sphere_probe_texel_to_direction(texel_corner_v10, write_co, sample_co);
|
||||
vec3 v01 = sphere_probe_texel_to_direction(texel_corner_v01, write_co, sample_co);
|
||||
vec3 v11 = sphere_probe_texel_to_direction(texel_corner_v11, write_co, sample_co);
|
||||
/* The solid angle functions expect normalized vectors. */
|
||||
v00 = normalize(v00);
|
||||
v10 = normalize(v10);
|
||||
v20 = normalize(v20);
|
||||
v01 = normalize(v01);
|
||||
v11 = normalize(v11);
|
||||
v21 = normalize(v21);
|
||||
v02 = normalize(v02);
|
||||
v12 = normalize(v12);
|
||||
v22 = normalize(v22);
|
||||
#if 0 /* Has artifacts, is marginally more correct. */
|
||||
/* For some reason quad_solid_angle(v10, v20, v11, v21) gives some strange artifacts at Z=0. */
|
||||
return 0.25 * (quad_solid_angle(v00, v10, v01, v11) + quad_solid_angle(v10, v20, v11, v21) +
|
||||
quad_solid_angle(v01, v11, v02, v12) + quad_solid_angle(v11, v21, v12, v22));
|
||||
#else
|
||||
/* Choosing the positive quad (0,0) > (+1,+1) for stability. */
|
||||
return quad_solid_angle(v11, v21, v12, v22);
|
||||
#endif
|
||||
/* Compute solid angle of the spherical quad. */
|
||||
float texel_clipped_solid_angle = quad_solid_angle(v00, v10, v01, v11);
|
||||
/* Multiply by the symetric halfs that we omited.
|
||||
* Also important to note that we avoid weighting the same pixel more than it's total sampled
|
||||
* footprint if it is duplicated in another pixel of the map. So border pixels do not require any
|
||||
* special treatment. Only the center cross needs it. */
|
||||
if (wrapped_texel.x == half_size - 1) {
|
||||
texel_clipped_solid_angle *= 2.0;
|
||||
}
|
||||
if (wrapped_texel.y == half_size - 1) {
|
||||
texel_clipped_solid_angle *= 2.0;
|
||||
}
|
||||
return texel_clipped_solid_angle;
|
||||
}
|
||||
|
||||
void main()
|
||||
|
@ -75,7 +93,7 @@ void main()
|
|||
|
||||
vec2 wrapped_uv;
|
||||
vec3 direction = sphere_probe_texel_to_direction(
|
||||
local_texel, write_coord, sample_coord, wrapped_uv);
|
||||
vec2(local_texel), write_coord, sample_coord, wrapped_uv);
|
||||
vec4 radiance_and_transmittance = texture(cubemap_tx, direction);
|
||||
vec3 radiance = radiance_and_transmittance.xyz;
|
||||
|
||||
|
@ -118,11 +136,12 @@ void main()
|
|||
* Note that this is an approximation since the footprint of a thread-group is not
|
||||
* necessarily a convex polygons (with center of gravity at midpoint).
|
||||
* But the actual error introduce by this approximation is not perceivable. */
|
||||
/* FIXME(fclem): The error IS very perceivable for resolution lower than a quadrant. */
|
||||
ivec2 max_group_texel = local_texel + ivec2(gl_WorkGroupSize.xy);
|
||||
/* Min direction is the local direction since this is only ran by thread 0. */
|
||||
vec3 min_direction = normalize(direction);
|
||||
vec3 max_direction = normalize(
|
||||
sphere_probe_texel_to_direction(max_group_texel, write_coord, sample_coord));
|
||||
sphere_probe_texel_to_direction(vec2(max_group_texel), write_coord, sample_coord));
|
||||
vec3 L = normalize(min_direction + max_direction);
|
||||
/* Convert radiance to spherical harmonics. */
|
||||
SphericalHarmonicL1 sh;
|
||||
|
|
|
@ -718,12 +718,13 @@ SphericalHarmonicL1 spherical_harmonics_clamp(SphericalHarmonicL1 sh, float clam
|
|||
{
|
||||
/* Convert coefficients to per channel column. */
|
||||
mat4x4 per_channel = transpose(mat4x4(sh.L0.M0, sh.L1.Mn1, sh.L1.M0, sh.L1.Mp1));
|
||||
/* Maximum per channel. */
|
||||
vec3 max_L1 = vec3(reduce_max(abs(per_channel[0].yzw)),
|
||||
reduce_max(abs(per_channel[1].yzw)),
|
||||
reduce_max(abs(per_channel[2].yzw)));
|
||||
/* Magnitute per channel. */
|
||||
vec3 mag_L1;
|
||||
mag_L1.r = length(per_channel[0].yzw);
|
||||
mag_L1.g = length(per_channel[1].yzw);
|
||||
mag_L1.b = length(per_channel[2].yzw);
|
||||
/* Find maximum of the sh function over all channels. */
|
||||
vec3 max_sh = abs(sh.L0.M0.rgb) * 0.282094792 + max_L1 * 0.488602512;
|
||||
vec3 max_sh = abs(sh.L0.M0.rgb) * 0.282094792 + mag_L1 * 0.488602512;
|
||||
|
||||
float fac = clamp_value * safe_rcp(reduce_max(max_sh));
|
||||
if (fac > 1.0) {
|
||||
|
|
|
@ -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_toolslots_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(ts, (Paint **)&ts->gp_weightpaint);
|
||||
Paint *weight_paint = BKE_paint_get_active_from_paintmode(scene, PaintMode::WeightGPencil);
|
||||
BKE_paint_ensure(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_toolslots_brush_validate(bmain, &ts->gp_weightpaint->paint);
|
||||
BKE_paint_toolslots_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];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -104,6 +104,9 @@ void ED_slider_allow_increments_set(tSlider *slider, bool value);
|
|||
void ED_slider_mode_set(tSlider *slider, SliderMode mode);
|
||||
SliderMode ED_slider_mode_get(const tSlider *slider);
|
||||
void ED_slider_unit_set(tSlider *slider, const char *unit);
|
||||
/* Set a name that will show next to the slider to indicate which property is modified currently.
|
||||
* To clear, set to an empty string. */
|
||||
void ED_slider_property_label_set(tSlider *slider, const char *prop_name);
|
||||
|
||||
/* ************** XXX OLD CRUFT WARNING ************* */
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -44,6 +44,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
|
||||
|
@ -93,6 +97,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(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -6139,6 +6139,7 @@ bool SCULPT_vertex_is_occluded(SculptSession *ss, PBVHVertRef vertex, bool origi
|
|||
srd.ray_normal = ray_normal;
|
||||
srd.depth = depth;
|
||||
srd.face_normal = face_normal;
|
||||
srd.corner_verts = ss->corner_verts;
|
||||
|
||||
isect_ray_tri_watertight_v3_precalc(&srd.isect_precalc, ray_normal);
|
||||
bke::pbvh::raycast(
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1030,6 +1030,7 @@ static int ease_modal(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
ED_slider_unit_set(gso->slider, "%");
|
||||
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
||||
}
|
||||
ED_slider_property_label_set(gso->slider, RNA_property_ui_name(gso->factor_prop));
|
||||
ease_modal_update(C, op);
|
||||
break;
|
||||
}
|
||||
|
@ -1055,6 +1056,7 @@ static int ease_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
|||
ED_slider_allow_overshoot_set(gso->slider, false, false);
|
||||
ED_slider_factor_bounds_set(gso->slider, -1, 1);
|
||||
ED_slider_factor_set(gso->slider, 0.0f);
|
||||
ED_slider_property_label_set(gso->slider, RNA_property_ui_name(gso->factor_prop));
|
||||
|
||||
return invoke_result;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -726,10 +726,8 @@ struct BeztMap {
|
|||
BezTriple *bezt;
|
||||
/** Index of `bezt` in `fcu->bezt` array before sorting. */
|
||||
uint oldIndex;
|
||||
/** Swap order of handles (-1=clear; 0=not checked, 1=swap). */
|
||||
short swap_handles;
|
||||
/** Interpolation of previous and next segments. */
|
||||
char prev_ipo, current_ipo;
|
||||
/** Swap order of handles. Can happen when rotating keys around their common center. */
|
||||
bool swap_handles;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -743,18 +741,12 @@ static blender::Vector<BeztMap> bezt_to_beztmaps(BezTriple *bezts, const int tot
|
|||
|
||||
blender::Vector<BeztMap> bezms = blender::Vector<BeztMap>(totvert);
|
||||
|
||||
BezTriple *prevbezt = nullptr;
|
||||
for (const int i : bezms.index_range()) {
|
||||
BezTriple *bezt = &bezts[i];
|
||||
BeztMap &bezm = bezms[i];
|
||||
bezm.bezt = bezt;
|
||||
|
||||
bezm.swap_handles = false;
|
||||
bezm.oldIndex = i;
|
||||
|
||||
bezm.prev_ipo = (prevbezt) ? prevbezt->ipo : bezt->ipo;
|
||||
bezm.current_ipo = bezt->ipo;
|
||||
|
||||
prevbezt = bezt;
|
||||
}
|
||||
|
||||
return bezms;
|
||||
|
@ -764,8 +756,16 @@ static blender::Vector<BeztMap> bezt_to_beztmaps(BezTriple *bezts, const int tot
|
|||
static void sort_time_beztmaps(const blender::MutableSpan<BeztMap> bezms)
|
||||
{
|
||||
BeztMap *bezm;
|
||||
bool ok = true;
|
||||
|
||||
/* Check if handles need to be swapped. */
|
||||
for (BeztMap &bezm : bezms) {
|
||||
/* Handles are only swapped if they are both on the wrong side of the key. Otherwise the one
|
||||
* handle out of place is just clamped at the key position later. */
|
||||
bezm.swap_handles = (bezm.bezt->vec[0][0] > bezm.bezt->vec[1][0] &&
|
||||
bezm.bezt->vec[2][0] < bezm.bezt->vec[1][0]);
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
/* Keep repeating the process until nothing is out of place anymore. */
|
||||
while (ok) {
|
||||
ok = false;
|
||||
|
@ -779,21 +779,6 @@ static void sort_time_beztmaps(const blender::MutableSpan<BeztMap> bezms)
|
|||
ok = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Do we need to check if the handles need to be swapped?
|
||||
* Optimization: this only needs to be performed in the first loop. */
|
||||
if (bezm->swap_handles == 0) {
|
||||
if ((bezm->bezt->vec[0][0] > bezm->bezt->vec[1][0]) &&
|
||||
(bezm->bezt->vec[2][0] < bezm->bezt->vec[1][0]))
|
||||
{
|
||||
/* Handles need to be swapped. */
|
||||
bezm->swap_handles = 1;
|
||||
}
|
||||
else {
|
||||
/* Handles need to be cleared. */
|
||||
bezm->swap_handles = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -827,7 +812,7 @@ static void beztmap_to_data(TransInfo *t, FCurve *fcu, const blender::Span<BeztM
|
|||
/* Update all transdata pointers, no need to check for selections etc,
|
||||
* since only points that are really needed were created as transdata. */
|
||||
if (td2d->loc2d == bezm->bezt->vec[0]) {
|
||||
if (bezm->swap_handles == 1) {
|
||||
if (bezm->swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[i].vec[2];
|
||||
}
|
||||
else {
|
||||
|
@ -836,7 +821,7 @@ static void beztmap_to_data(TransInfo *t, FCurve *fcu, const blender::Span<BeztM
|
|||
adjusted[j] = true;
|
||||
}
|
||||
else if (td2d->loc2d == bezm->bezt->vec[2]) {
|
||||
if (bezm->swap_handles == 1) {
|
||||
if (bezm->swap_handles) {
|
||||
td2d->loc2d = fcu->bezt[i].vec[0];
|
||||
}
|
||||
else {
|
||||
|
@ -860,7 +845,7 @@ static void beztmap_to_data(TransInfo *t, FCurve *fcu, const blender::Span<BeztM
|
|||
|
||||
/* The handle type pointer has to be updated too. */
|
||||
if (adjusted[j] && td->flag & TD_BEZTRIPLE && td->hdata) {
|
||||
if (bezm->swap_handles == 1) {
|
||||
if (bezm->swap_handles) {
|
||||
td->hdata->h1 = &fcu->bezt[i].h2;
|
||||
td->hdata->h2 = &fcu->bezt[i].h1;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,10 @@ struct tSlider {
|
|||
/* How the factor number is drawn. When drawing percent it is factor*100. */
|
||||
SliderMode slider_mode;
|
||||
|
||||
/* Optional string that will display next to the slider to indicate which property is modified
|
||||
* right now. */
|
||||
std::string property_label;
|
||||
|
||||
/* What unit to add to the slider. */
|
||||
char unit_string[SLIDER_UNIT_STRING_SIZE];
|
||||
|
||||
|
@ -224,19 +228,28 @@ static void draw_backdrop(const int fontid,
|
|||
const rctf *main_line_rect,
|
||||
const uint8_t color_bg[4],
|
||||
const short region_y_size,
|
||||
const float base_tick_height)
|
||||
const float base_tick_height,
|
||||
const std::string &property_label)
|
||||
{
|
||||
float string_pixel_size[2];
|
||||
float percent_string_pixel_size[2];
|
||||
const char *percentage_string_placeholder = "000%%";
|
||||
BLF_width_and_height(fontid,
|
||||
percentage_string_placeholder,
|
||||
sizeof(percentage_string_placeholder),
|
||||
&string_pixel_size[0],
|
||||
&string_pixel_size[1]);
|
||||
const float pad[2] = {(region_y_size - base_tick_height) / 2, 2.0f * U.pixelsize};
|
||||
&percent_string_pixel_size[0],
|
||||
&percent_string_pixel_size[1]);
|
||||
|
||||
float property_name_pixel_size[2];
|
||||
BLF_width_and_height(fontid,
|
||||
property_label.c_str(),
|
||||
property_label.size(),
|
||||
&property_name_pixel_size[0],
|
||||
&property_name_pixel_size[1]);
|
||||
const float pad[2] = {(region_y_size - base_tick_height) / 2 + 12.0f * U.pixelsize,
|
||||
2.0f * U.pixelsize};
|
||||
rctf backdrop_rect{};
|
||||
backdrop_rect.xmin = main_line_rect->xmin - string_pixel_size[0] - pad[0];
|
||||
backdrop_rect.xmax = main_line_rect->xmax + pad[0];
|
||||
backdrop_rect.xmin = main_line_rect->xmin - property_name_pixel_size[0] - pad[0];
|
||||
backdrop_rect.xmax = main_line_rect->xmax + percent_string_pixel_size[0] + pad[0];
|
||||
backdrop_rect.ymin = pad[1];
|
||||
backdrop_rect.ymax = region_y_size - pad[1];
|
||||
UI_draw_roundbox_3ub_alpha(&backdrop_rect, true, 4.0f, color_bg, color_bg[3]);
|
||||
|
@ -304,7 +317,12 @@ static void slider_draw(const bContext * /*C*/, ARegion *region, void *arg)
|
|||
handle_pos_x = main_line_rect.xmin + SLIDE_PIXEL_DISTANCE * range_factor;
|
||||
}
|
||||
|
||||
draw_backdrop(fontid, &main_line_rect, color_bg, slider->region_header->winy, base_tick_height);
|
||||
draw_backdrop(fontid,
|
||||
&main_line_rect,
|
||||
color_bg,
|
||||
slider->region_header->winy,
|
||||
base_tick_height,
|
||||
slider->property_label);
|
||||
|
||||
draw_main_line(&main_line_rect, slider->factor, slider->overshoot, color_overshoot, color_line);
|
||||
|
||||
|
@ -356,11 +374,25 @@ static void slider_draw(const bContext * /*C*/, ARegion *region, void *arg)
|
|||
&factor_string_pixel_size[0],
|
||||
&factor_string_pixel_size[1]);
|
||||
|
||||
BLF_position(fontid,
|
||||
main_line_rect.xmin - 12.0 * U.pixelsize - factor_string_pixel_size[0],
|
||||
(region->winy / 2) - factor_string_pixel_size[1] / 2,
|
||||
0.0f);
|
||||
const float text_padding = 12.0 * U.pixelsize;
|
||||
const float factor_string_pos_x = main_line_rect.xmax + text_padding;
|
||||
BLF_position(
|
||||
fontid, factor_string_pos_x, (region->winy / 2) - factor_string_pixel_size[1] / 2, 0.0f);
|
||||
BLF_draw(fontid, factor_string, sizeof(factor_string));
|
||||
|
||||
if (!slider->property_label.empty()) {
|
||||
float property_name_pixel_size[2];
|
||||
BLF_width_and_height(fontid,
|
||||
slider->property_label.c_str(),
|
||||
slider->property_label.length(),
|
||||
&property_name_pixel_size[0],
|
||||
&property_name_pixel_size[1]);
|
||||
BLF_position(fontid,
|
||||
main_line_rect.xmin - text_padding - property_name_pixel_size[0],
|
||||
(region->winy / 2) - property_name_pixel_size[1] / 2,
|
||||
0.0f);
|
||||
BLF_draw(fontid, slider->property_label.c_str(), slider->property_label.length());
|
||||
}
|
||||
}
|
||||
|
||||
static void slider_update_factor(tSlider *slider, const wmEvent *event)
|
||||
|
@ -584,6 +616,11 @@ void ED_slider_unit_set(tSlider *slider, const char *unit)
|
|||
STRNCPY(slider->unit_string, unit);
|
||||
}
|
||||
|
||||
void ED_slider_property_label_set(tSlider *slider, const char *property_label)
|
||||
{
|
||||
slider->property_label.assign(property_label);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
void ED_region_draw_mouse_line_cb(const bContext *C, ARegion *region, void *arg_info)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -922,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)) {
|
||||
|
@ -948,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
|
@ -345,12 +345,7 @@ static bool rna_Brush_mode_with_tool_poll(PointerRNA *ptr, PointerRNA value)
|
|||
if (slot_index != brush->gpencil_tool) {
|
||||
return false;
|
||||
}
|
||||
if (U.experimental.use_grease_pencil_version3) {
|
||||
mode = OB_MODE_PAINT_GREASE_PENCIL;
|
||||
}
|
||||
else {
|
||||
mode = OB_MODE_PAINT_GPENCIL_LEGACY;
|
||||
}
|
||||
mode = OB_MODE_PAINT_GPENCIL_LEGACY;
|
||||
}
|
||||
else if (paint_contains_brush_slot(&ts->gp_vertexpaint->paint, tslot, &slot_index)) {
|
||||
if (slot_index != brush->gpencil_vertex_tool) {
|
||||
|
|
|
@ -661,12 +661,6 @@ static void rna_GizmoGroup_bl_label_set(PointerRNA *ptr, const char *value)
|
|||
}
|
||||
}
|
||||
|
||||
static bool rna_GizmoGroup_has_reports_get(PointerRNA *ptr)
|
||||
{
|
||||
wmGizmoGroup *gzgroup = static_cast<wmGizmoGroup *>(ptr->data);
|
||||
return (gzgroup->reports && gzgroup->reports->list.first);
|
||||
}
|
||||
|
||||
# ifdef WITH_PYTHON
|
||||
|
||||
static bool rna_gizmogroup_poll_cb(const bContext *C, wmGizmoGroupType *gzgt)
|
||||
|
@ -1501,14 +1495,6 @@ static void rna_def_gizmogroup(BlenderRNA *brna)
|
|||
prop, "rna_GizmoGroup_name_get", "rna_GizmoGroup_name_length", nullptr);
|
||||
RNA_def_property_ui_text(prop, "Name", "");
|
||||
|
||||
prop = RNA_def_property(srna, "has_reports", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE); /* this is 'virtual' property */
|
||||
RNA_def_property_boolean_funcs(prop, "rna_GizmoGroup_has_reports_get", nullptr);
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Has Reports",
|
||||
"GizmoGroup has a set of reports (warnings and errors) from last execution");
|
||||
|
||||
RNA_define_verify_sdna(false); /* not in sdna */
|
||||
|
||||
prop = RNA_def_property(srna, "gizmos", PROP_COLLECTION, PROP_NONE);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -477,8 +477,6 @@ struct wmGizmoGroup {
|
|||
|
||||
/** Python stores the class instance here. */
|
||||
void *py_instance;
|
||||
/** Errors and warnings storage. */
|
||||
ReportList *reports;
|
||||
|
||||
/** Has the same result as hiding all gizmos individually. */
|
||||
union {
|
||||
|
|
|
@ -101,11 +101,6 @@ void wm_gizmogroup_free(bContext *C, wmGizmoGroup *gzgroup)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (gzgroup->reports && (gzgroup->reports->flag & RPT_FREE)) {
|
||||
BKE_reports_free(gzgroup->reports);
|
||||
MEM_freeN(gzgroup->reports);
|
||||
}
|
||||
|
||||
if (gzgroup->customdata_free) {
|
||||
gzgroup->customdata_free(gzgroup->customdata);
|
||||
}
|
||||
|
|
|
@ -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