The grid layout type for UI list is planned for removal in 5.0, see blender/blender#110461. In previous UI meetings, we talked about deprecating the Grid mode of the UI list, which is not actually accessible in UI and was never used. Nowadays, there is a new grid view that can be exposed in the API in the future. Initially, I wanted to remove references to layout_type in UI templates in the text editor, because a lot of add-on developers on the extensions platform base their lists on that code, and a lot of them are therefore including soon to be deprecated code in their add-ons, which I want to avoid in the future. But I thought we might as well remove it from our python scripts as well, since it's just basically redundant code that doesn't do anything. And also because many add-on developers use bundled python scripts for references as well. Pull Request: https://projects.blender.org/blender/blender/pulls/138395
537 lines
18 KiB
Python
537 lines
18 KiB
Python
# SPDX-FileCopyrightText: 2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
import bpy
|
|
from bpy.types import Panel, Menu, UIList
|
|
from rna_prop_ui import PropertyPanel
|
|
from bl_ui.space_properties import PropertiesAnimationMixin
|
|
|
|
|
|
class DataButtonsPanel:
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return hasattr(context, "grease_pencil") and context.grease_pencil
|
|
|
|
|
|
class LayerDataButtonsPanel:
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
grease_pencil = context.grease_pencil
|
|
return grease_pencil and grease_pencil.layers.active
|
|
|
|
|
|
class GREASE_PENCIL_UL_masks(UIList):
|
|
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
|
|
mask = item
|
|
row = layout.row(align=True)
|
|
row.prop(mask, "name", text="", emboss=False, icon_value=icon)
|
|
row.prop(mask, "invert", text="", emboss=False)
|
|
row.prop(mask, "hide", text="", emboss=False)
|
|
|
|
|
|
class GreasePencil_LayerMaskPanel:
|
|
def draw_header(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
|
|
self.layout.prop(layer, "use_masks", text="", toggle=0)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
|
|
layout = self.layout
|
|
layout.enabled = layer.use_masks
|
|
|
|
if not layer:
|
|
return
|
|
|
|
rows = 4
|
|
row = layout.row()
|
|
col = row.column()
|
|
col.template_list(
|
|
"GREASE_PENCIL_UL_masks", "", layer, "mask_layers", layer.mask_layers,
|
|
"active_mask_index", rows=rows, sort_lock=True,
|
|
)
|
|
|
|
col = row.column(align=True)
|
|
col.menu("GREASE_PENCIL_MT_layer_mask_add", icon='ADD', text="")
|
|
col.operator("grease_pencil.layer_mask_remove", icon='REMOVE', text="")
|
|
|
|
col.separator()
|
|
|
|
sub = col.column(align=True)
|
|
sub.operator("grease_pencil.layer_mask_reorder", icon='TRIA_UP', text="").direction = 'UP'
|
|
sub.operator("grease_pencil.layer_mask_reorder", icon='TRIA_DOWN', text="").direction = 'DOWN'
|
|
|
|
|
|
class GreasePencil_LayerTransformPanel:
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
layout.active = not layer.lock
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "translation")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "rotation")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "scale")
|
|
|
|
|
|
class GreasePencil_LayerAdjustmentsPanel:
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
layout.active = not layer.lock
|
|
|
|
# Layer options
|
|
col = layout.column(align=True)
|
|
|
|
col.prop(layer, "tint_color")
|
|
col.prop(layer, "tint_factor", text="Factor", slider=True)
|
|
|
|
col = layout.row(align=True)
|
|
col.prop(layer, "radius_offset", text="Stroke Thickness")
|
|
|
|
|
|
class GreasePencil_LayerRelationsPanel:
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
layout.active = not layer.lock
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "parent", text="Parent")
|
|
|
|
if layer.parent and layer.parent.type == 'ARMATURE':
|
|
row = layout.row(align=True)
|
|
row.prop_search(layer, "parent_bone", layer.parent.data, "bones", text="Bone")
|
|
|
|
layout.separator()
|
|
|
|
col = layout.row(align=True)
|
|
col.prop(layer, "pass_index")
|
|
|
|
col = layout.row(align=True)
|
|
col.prop_search(layer, "viewlayer_render", context.scene, "view_layers", text="View Layer")
|
|
|
|
col = layout.row(align=True)
|
|
# Only enable this property when a view layer is selected.
|
|
col.enabled = bool(layer.viewlayer_render)
|
|
col.prop(layer, "use_viewlayer_masks")
|
|
|
|
|
|
class GreasePencil_LayerDisplayPanel:
|
|
bl_label = "Display"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
|
|
layout.prop(layer, "channel_color", text="Channel Color")
|
|
|
|
|
|
class GREASE_PENCIL_MT_layer_mask_add(Menu):
|
|
bl_label = "Add Mask"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
grease_pencil = context.grease_pencil
|
|
active_layer = grease_pencil.layers.active
|
|
found = False
|
|
for layer in grease_pencil.layers:
|
|
if layer == active_layer or layer.name in active_layer.mask_layers:
|
|
continue
|
|
|
|
found = True
|
|
layout.operator("grease_pencil.layer_mask_add", text=layer.name).name = layer.name
|
|
|
|
if not found:
|
|
layout.label(text="No layers to add")
|
|
|
|
|
|
class DATA_PT_context_grease_pencil(DataButtonsPanel, Panel):
|
|
bl_label = ""
|
|
bl_options = {'HIDE_HEADER'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
ob = context.object
|
|
grease_pencil = context.grease_pencil
|
|
space = context.space_data
|
|
|
|
if ob:
|
|
layout.template_ID(ob, "data")
|
|
elif grease_pencil:
|
|
layout.template_ID(space, "pin_id")
|
|
|
|
|
|
class GREASE_PENCIL_MT_grease_pencil_add_layer_extra(Menu):
|
|
bl_label = "Add Extra"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
grease_pencil = context.grease_pencil
|
|
layer = grease_pencil.layers.active
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.layer_duplicate", text="Duplicate", icon='DUPLICATE').empty_keyframes = False
|
|
layout.operator("grease_pencil.layer_duplicate", text="Duplicate Empty Keyframes").empty_keyframes = True
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.layer_reveal", icon='RESTRICT_VIEW_OFF', text="Show All")
|
|
layout.operator("grease_pencil.layer_hide", icon='RESTRICT_VIEW_ON', text="Hide Others").unselected = True
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.layer_lock_all", icon='LOCKED', text="Lock All").lock = True
|
|
layout.operator("grease_pencil.layer_lock_all", icon='UNLOCKED', text="Unlock All").lock = False
|
|
|
|
layout.separator()
|
|
layout.prop(grease_pencil, "use_autolock_layers", text="Autolock Inactive Layers")
|
|
|
|
if layer:
|
|
layout.prop(layer, "ignore_locked_materials")
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.layer_merge", text="Merge Down").mode = 'ACTIVE'
|
|
layout.operator("grease_pencil.layer_merge", text="Merge Group").mode = 'GROUP'
|
|
layout.operator("grease_pencil.layer_merge", text="Merge All").mode = 'ALL'
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.relative_layer_mask_add", text="Mask with Layer Above").mode = 'ABOVE'
|
|
layout.operator("grease_pencil.relative_layer_mask_add", text="Mask with Layer Below").mode = 'BELOW'
|
|
|
|
layout.separator()
|
|
layout.operator("grease_pencil.layer_duplicate_object", text="Copy Layer to Selected").only_active = True
|
|
layout.operator("grease_pencil.layer_duplicate_object", text="Copy All Layers to Selected").only_active = False
|
|
|
|
|
|
class GREASE_PENCIL_MT_group_context_menu(Menu):
|
|
bl_label = "Layer Group"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.operator("grease_pencil.layer_group_remove", text="Delete Group").keep_children = False
|
|
layout.operator("grease_pencil.layer_group_remove", text="Ungroup").keep_children = True
|
|
layout.operator("grease_pencil.layer_merge", text="Merge Group").mode = 'GROUP'
|
|
|
|
layout.separator()
|
|
row = layout.row(align=True)
|
|
row.operator_enum("grease_pencil.layer_group_color_tag", "color_tag", icon_only=True)
|
|
|
|
|
|
class DATA_PT_grease_pencil_layers(DataButtonsPanel, Panel):
|
|
bl_label = "Layers"
|
|
|
|
@classmethod
|
|
def draw_settings(cls, layout, grease_pencil):
|
|
layer = grease_pencil.layers.active
|
|
is_layer_active = layer is not None
|
|
is_group_active = grease_pencil.layer_groups.active is not None
|
|
|
|
row = layout.row()
|
|
row.template_grease_pencil_layer_tree()
|
|
|
|
col = row.column()
|
|
sub = col.column(align=True)
|
|
sub.operator_context = 'EXEC_DEFAULT'
|
|
sub.operator("grease_pencil.layer_add", icon='ADD', text="")
|
|
sub.operator("grease_pencil.layer_group_add", icon='NEWFOLDER', text="")
|
|
sub.separator()
|
|
|
|
if is_layer_active:
|
|
sub.operator("grease_pencil.layer_remove", icon='REMOVE', text="")
|
|
if is_group_active:
|
|
sub.operator("grease_pencil.layer_group_remove", icon='REMOVE', text="").keep_children = True
|
|
|
|
sub.separator()
|
|
|
|
sub.menu("GREASE_PENCIL_MT_grease_pencil_add_layer_extra", icon='DOWNARROW_HLT', text="")
|
|
|
|
col.separator()
|
|
|
|
sub = col.column(align=True)
|
|
sub.operator("grease_pencil.layer_move", icon='TRIA_UP', text="").direction = 'UP'
|
|
sub.operator("grease_pencil.layer_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
|
|
|
|
if not is_layer_active:
|
|
return
|
|
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = True
|
|
col = layout.column(align=True)
|
|
|
|
# Layer main properties
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "blend_mode", text="Blend Mode")
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "opacity", text="Opacity", slider=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(layer, "use_lights", text="Lights")
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
grease_pencil = context.grease_pencil
|
|
|
|
self.draw_settings(layout, grease_pencil)
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_masks(LayerDataButtonsPanel, GreasePencil_LayerMaskPanel, Panel):
|
|
bl_label = "Masks"
|
|
bl_parent_id = "DATA_PT_grease_pencil_layers"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_transform(LayerDataButtonsPanel, GreasePencil_LayerTransformPanel, Panel):
|
|
bl_label = "Transform"
|
|
bl_parent_id = "DATA_PT_grease_pencil_layers"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_adjustments(LayerDataButtonsPanel, GreasePencil_LayerAdjustmentsPanel, Panel):
|
|
bl_label = "Adjustments"
|
|
bl_parent_id = "DATA_PT_grease_pencil_layers"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_relations(LayerDataButtonsPanel, GreasePencil_LayerRelationsPanel, Panel):
|
|
bl_label = "Relations"
|
|
bl_parent_id = "DATA_PT_grease_pencil_layers"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_display(LayerDataButtonsPanel, GreasePencil_LayerDisplayPanel, Panel):
|
|
bl_label = "Display"
|
|
bl_parent_id = "DATA_PT_grease_pencil_layers"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
|
|
class DATA_PT_grease_pencil_layer_group_display(Panel):
|
|
bl_label = "Display"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
grease_pencil = context.grease_pencil
|
|
return grease_pencil and grease_pencil.layer_groups.active
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
grease_pencil = context.grease_pencil
|
|
group = grease_pencil.layer_groups.active
|
|
|
|
layout.prop(group, "channel_color", text="Channel Color")
|
|
|
|
|
|
class DATA_PT_grease_pencil_onion_skinning(DataButtonsPanel, Panel):
|
|
bl_label = "Onion Skinning"
|
|
|
|
def draw(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
|
|
col = layout.column()
|
|
col.prop(grease_pencil, "onion_mode")
|
|
col.prop(grease_pencil, "onion_factor", text="Opacity", slider=True)
|
|
col.prop(grease_pencil, "onion_keyframe_type")
|
|
|
|
if grease_pencil.onion_mode == 'ABSOLUTE':
|
|
col = layout.column(align=True)
|
|
col.prop(grease_pencil, "ghost_before_range", text="Frames Before")
|
|
col.prop(grease_pencil, "ghost_after_range", text="Frames After")
|
|
elif grease_pencil.onion_mode == 'RELATIVE':
|
|
col = layout.column(align=True)
|
|
col.prop(grease_pencil, "ghost_before_range", text="Keyframes Before")
|
|
col.prop(grease_pencil, "ghost_after_range", text="Keyframes After")
|
|
|
|
|
|
class DATA_PT_grease_pencil_onion_skinning_custom_colors(DataButtonsPanel, Panel):
|
|
bl_parent_id = "DATA_PT_grease_pencil_onion_skinning"
|
|
bl_label = "Custom Colors"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw_header(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
self.layout.prop(grease_pencil, "use_ghost_custom_colors", text="")
|
|
|
|
def draw(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.enabled = grease_pencil.users <= 1 and grease_pencil.use_ghost_custom_colors
|
|
|
|
layout.prop(grease_pencil, "before_color", text="Before")
|
|
layout.prop(grease_pencil, "after_color", text="After")
|
|
|
|
|
|
class DATA_PT_grease_pencil_onion_skinning_display(DataButtonsPanel, Panel):
|
|
bl_parent_id = "DATA_PT_grease_pencil_onion_skinning"
|
|
bl_label = "Display"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
# This was done in GPv2 but it's not entirely clear why. Presumably it was
|
|
# to indicate that the settings will affect the onion skinning of the
|
|
# other users.
|
|
layout.enabled = grease_pencil.users <= 1
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(grease_pencil, "use_onion_fade", text="Fade")
|
|
sub = layout.column()
|
|
sub.active = grease_pencil.onion_mode in {'RELATIVE', 'SELECTED'}
|
|
sub.prop(grease_pencil, "use_onion_loop", text="Show Start Frame")
|
|
|
|
|
|
class DATA_PT_grease_pencil_settings(DataButtonsPanel, Panel):
|
|
bl_label = "Settings"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
grease_pencil = context.grease_pencil
|
|
col = layout.column(align=True)
|
|
col.prop(grease_pencil, "stroke_depth_order", text="Stroke Depth Order")
|
|
|
|
|
|
class DATA_PT_grease_pencil_animation(DataButtonsPanel, PropertiesAnimationMixin, PropertyPanel, Panel):
|
|
_animated_id_context_property = "grease_pencil"
|
|
|
|
|
|
class DATA_PT_grease_pencil_custom_props(DataButtonsPanel, PropertyPanel, Panel):
|
|
_context_path = "object.data"
|
|
_property_type = bpy.types.GreasePencilv3
|
|
|
|
|
|
class GREASE_PENCIL_UL_attributes(UIList):
|
|
def filter_items(self, _context, data, property):
|
|
attributes = getattr(data, property)
|
|
flags = []
|
|
indices = [i for i in range(len(attributes))]
|
|
|
|
# Filtering by name
|
|
if self.filter_name:
|
|
flags = bpy.types.UI_UL_list.filter_items_by_name(
|
|
self.filter_name, self.bitflag_filter_item, attributes, "name", reverse=self.use_filter_invert,
|
|
)
|
|
if not flags:
|
|
flags = [self.bitflag_filter_item] * len(attributes)
|
|
|
|
# Filtering internal attributes
|
|
for idx, item in enumerate(attributes):
|
|
flags[idx] = 0 if item.is_internal else flags[idx]
|
|
|
|
# Reorder by name.
|
|
if self.use_filter_sort_alpha:
|
|
indices = bpy.types.UI_UL_list.sort_items_by_name(attributes, "name")
|
|
|
|
return flags, indices
|
|
|
|
def draw_item(self, _context, layout, _data, attribute, _icon, _active_data, _active_propname, _index):
|
|
data_type = attribute.bl_rna.properties["data_type"].enum_items[attribute.data_type]
|
|
|
|
split = layout.split(factor=0.50)
|
|
split.emboss = 'NONE'
|
|
split.prop(attribute, "name", text="")
|
|
sub = split.row()
|
|
sub.alignment = 'RIGHT'
|
|
sub.active = False
|
|
sub.label(text=data_type.name)
|
|
|
|
|
|
class DATA_PT_grease_pencil_attributes(DataButtonsPanel, Panel):
|
|
bl_label = "Attributes"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
def draw(self, context):
|
|
grease_pencil = context.grease_pencil
|
|
|
|
layout = self.layout
|
|
row = layout.row()
|
|
|
|
col = row.column()
|
|
col.template_list(
|
|
"GREASE_PENCIL_UL_attributes",
|
|
"attributes",
|
|
grease_pencil,
|
|
"attributes",
|
|
grease_pencil.attributes,
|
|
"active_index",
|
|
rows=3,
|
|
)
|
|
|
|
col = row.column(align=True)
|
|
col.operator("geometry.attribute_add", icon='ADD', text="")
|
|
col.operator("geometry.attribute_remove", icon='REMOVE', text="")
|
|
|
|
|
|
classes = (
|
|
GREASE_PENCIL_UL_masks,
|
|
GREASE_PENCIL_MT_layer_mask_add,
|
|
DATA_PT_context_grease_pencil,
|
|
DATA_PT_grease_pencil_layers,
|
|
DATA_PT_grease_pencil_layer_masks,
|
|
DATA_PT_grease_pencil_layer_transform,
|
|
DATA_PT_grease_pencil_layer_adjustments,
|
|
DATA_PT_grease_pencil_layer_relations,
|
|
DATA_PT_grease_pencil_layer_display,
|
|
DATA_PT_grease_pencil_layer_group_display,
|
|
DATA_PT_grease_pencil_onion_skinning,
|
|
DATA_PT_grease_pencil_onion_skinning_custom_colors,
|
|
DATA_PT_grease_pencil_onion_skinning_display,
|
|
DATA_PT_grease_pencil_settings,
|
|
DATA_PT_grease_pencil_custom_props,
|
|
GREASE_PENCIL_MT_grease_pencil_add_layer_extra,
|
|
GREASE_PENCIL_MT_group_context_menu,
|
|
DATA_PT_grease_pencil_animation,
|
|
GREASE_PENCIL_UL_attributes,
|
|
DATA_PT_grease_pencil_attributes,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|