Files
test/scripts/startup/bl_ui/properties_data_grease_pencil.py
Nika Kutsniashvili 5dd53fdb29 Fix: Avoid relative imports in UI code
Relative imports are intentionally avoided in UI code (`scripts/startup/bl_ui`) because it makes it impossible to run individual files in isolation.

Common workflow for making mock-ups and quick UI edits is to load the UI file in Blender's text editor with "Edit Source" operator, make changes, and run the script to see the changes. But in the current version it's impossible because files include relative imports, which don't allow Python to run scripts individually.

Pull Request: https://projects.blender.org/blender/blender/pulls/138246
2025-05-04 13:26:03 +02:00

541 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
if self.layout_type in {'DEFAULT', 'COMPACT'}:
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)
elif self.layout_type == 'GRID':
layout.alignment = 'CENTER'
layout.prop(mask, "name", text="", emboss=False, icon_value=icon)
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)