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
466 lines
14 KiB
Python
466 lines
14 KiB
Python
# SPDX-FileCopyrightText: 2012-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# Panels get sub-classed (not registered directly).
|
|
# Menus are referenced as-is.
|
|
|
|
from bpy.types import Menu, UIList
|
|
from bpy.app.translations import contexts as i18n_contexts
|
|
from bl_ui import anim
|
|
|
|
|
|
# Use by both image & clip context menus.
|
|
def draw_mask_context_menu(layout, _context):
|
|
layout.operator_menu_enum("mask.handle_type_set", "type")
|
|
layout.operator("mask.switch_direction")
|
|
layout.operator("mask.cyclic_toggle")
|
|
|
|
layout.separator()
|
|
layout.operator("mask.copy_splines", icon='COPYDOWN')
|
|
layout.operator("mask.paste_splines", icon='PASTEDOWN')
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.shape_key_rekey", text="Re-Key Shape Points")
|
|
layout.operator("mask.feather_weight_clear")
|
|
layout.operator("mask.shape_key_feather_reset", text="Reset Feather Animation")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.parent_set")
|
|
layout.operator("mask.parent_clear")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.delete")
|
|
|
|
|
|
class MASK_UL_layers(UIList):
|
|
def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index):
|
|
# assert(isinstance(item, bpy.types.MaskLayer)
|
|
mask = item
|
|
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
|
layout.prop(mask, "name", text="", emboss=False, icon_value=icon)
|
|
row = layout.row(align=True)
|
|
row.prop(mask, "hide", text="", emboss=False)
|
|
row.prop(mask, "hide_select", text="", emboss=False)
|
|
row.prop(mask, "hide_render", text="", emboss=False)
|
|
elif self.layout_type == 'GRID':
|
|
layout.alignment = 'CENTER'
|
|
layout.label(text="", icon_value=icon)
|
|
|
|
|
|
class MASK_PT_mask:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'UI'
|
|
bl_label = "Mask Settings"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(mask, "frame_start")
|
|
col.prop(mask, "frame_end")
|
|
|
|
|
|
class MASK_PT_layers:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'UI'
|
|
bl_label = "Mask Layers"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
active_layer = mask.layers.active
|
|
|
|
rows = 4 if active_layer else 1
|
|
|
|
row = layout.row()
|
|
row.template_list(
|
|
"MASK_UL_layers", "", mask, "layers",
|
|
mask, "active_layer_index", rows=rows,
|
|
)
|
|
|
|
sub = row.column(align=True)
|
|
|
|
sub.operator("mask.layer_new", icon='ADD', text="")
|
|
sub.operator("mask.layer_remove", icon='REMOVE', text="")
|
|
|
|
if active_layer:
|
|
sub.separator()
|
|
|
|
sub.operator("mask.layer_move", icon='TRIA_UP', text="").direction = 'UP'
|
|
sub.operator("mask.layer_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
|
|
|
|
# blending
|
|
row = layout.row(align=True)
|
|
row.prop(active_layer, "alpha")
|
|
row.prop(active_layer, "invert", text="", icon='IMAGE_ALPHA')
|
|
|
|
layout.prop(active_layer, "blend")
|
|
layout.prop(active_layer, "falloff")
|
|
|
|
col = layout.column()
|
|
col.prop(active_layer, "use_fill_overlap", text="Overlap")
|
|
col.prop(active_layer, "use_fill_holes", text="Holes")
|
|
|
|
|
|
class MASK_PT_spline:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'UI'
|
|
bl_label = "Active Spline"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
|
|
if mask and sc.mode == 'MASK':
|
|
return mask.layers.active and mask.layers.active.splines.active
|
|
|
|
return False
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
spline = mask.layers.active.splines.active
|
|
|
|
col = layout.column()
|
|
col.prop(spline, "offset_mode")
|
|
col.prop(spline, "weight_interpolation", text="Interpolation")
|
|
|
|
col.prop(spline, "use_cyclic")
|
|
col.prop(spline, "use_fill")
|
|
|
|
col.prop(spline, "use_self_intersection_check")
|
|
|
|
|
|
class MASK_PT_point:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'UI'
|
|
bl_label = "Active Point"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
|
|
if mask and sc.mode == 'MASK':
|
|
mask_layer_active = mask.layers.active
|
|
return (mask_layer_active and
|
|
mask_layer_active.splines.active_point)
|
|
|
|
return False
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
point = mask.layers.active.splines.active_point
|
|
parent = point.parent
|
|
|
|
col = layout.column()
|
|
# Currently only parenting the movie-clip is allowed,
|
|
# so do not over-complicate things for now by using single template_ID
|
|
# col.template_any_ID(parent, "id", "id_type", text="")
|
|
|
|
col.label(text="Parent:")
|
|
col.prop(parent, "id", text="")
|
|
|
|
if parent.id_type == 'MOVIECLIP' and parent.id:
|
|
clip = parent.id
|
|
tracking = clip.tracking
|
|
|
|
row = col.row()
|
|
row.prop(parent, "type", expand=True)
|
|
|
|
col.prop_search(parent, "parent", tracking, "objects", icon='OBJECT_DATA', text="Object")
|
|
|
|
tracks_list = "tracks" if parent.type == 'POINT_TRACK' else "plane_tracks"
|
|
|
|
if parent.parent in tracking.objects:
|
|
ob = tracking.objects[parent.parent]
|
|
col.prop_search(
|
|
parent, "sub_parent", ob,
|
|
tracks_list, icon='ANIM_DATA', text="Track", text_ctxt=i18n_contexts.id_movieclip,
|
|
)
|
|
else:
|
|
col.prop_search(
|
|
parent, "sub_parent", tracking,
|
|
tracks_list, icon='ANIM_DATA', text="Track", text_ctxt=i18n_contexts.id_movieclip,
|
|
)
|
|
|
|
|
|
class MASK_PT_animation:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'UI'
|
|
bl_label = "Animation"
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
layout.use_property_decorate = False
|
|
|
|
# poll() ensures this is not None.
|
|
sc = context.space_data
|
|
mask = sc.mask
|
|
|
|
col = layout.column(align=True)
|
|
anim.draw_action_and_slot_selector_for_id(col, mask)
|
|
|
|
|
|
class MASK_PT_display:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'HEADER'
|
|
bl_label = "Mask Display"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
space_data = context.space_data
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(space_data, "show_mask_spline", text="Spline")
|
|
sub = row.row()
|
|
sub.active = space_data.show_mask_spline
|
|
sub.prop(space_data, "mask_display_type", text="")
|
|
row = layout.row(align=True)
|
|
row.prop(space_data, "show_mask_overlay", text="Overlay")
|
|
sub = row.row()
|
|
sub.active = space_data.show_mask_overlay
|
|
sub.prop(space_data, "mask_overlay_mode", text="")
|
|
row = layout.row()
|
|
row.active = space_data.show_mask_overlay and (space_data.mask_overlay_mode == 'COMBINED')
|
|
row.prop(space_data, "blend_factor", text="Blending Factor")
|
|
|
|
|
|
class MASK_PT_transforms:
|
|
# subclasses must define...
|
|
# ~ bl_space_type = 'CLIP_EDITOR'
|
|
# ~ bl_region_type = 'TOOLS'
|
|
bl_label = "Transforms"
|
|
bl_category = "Mask"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Transform:")
|
|
col.operator("transform.translate")
|
|
col.operator("transform.rotate")
|
|
col.operator("transform.resize", text="Scale")
|
|
col.operator("transform.transform", text="Scale Feather").mode = 'MASK_SHRINKFATTEN'
|
|
|
|
|
|
class MASK_PT_tools:
|
|
bl_label = "Mask Tools"
|
|
bl_category = "Mask"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
space_data = context.space_data
|
|
return space_data.mask and space_data.mode == 'MASK'
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Spline:")
|
|
col.operator("mask.delete")
|
|
col.operator("mask.cyclic_toggle")
|
|
col.operator("mask.switch_direction")
|
|
col.operator("mask.handle_type_set").type = 'VECTOR'
|
|
col.operator("mask.feather_weight_clear")
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Parenting:")
|
|
row = col.row(align=True)
|
|
row.operator("mask.parent_set", text="Parent")
|
|
row.operator("mask.parent_clear", text="Clear")
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Animation:")
|
|
row = col.row(align=True)
|
|
row.operator("mask.shape_key_insert", text="Insert Key")
|
|
row.operator("mask.shape_key_clear", text="Clear Key")
|
|
col.operator("mask.shape_key_feather_reset", text="Reset Feather Animation")
|
|
col.operator("mask.shape_key_rekey", text="Re-Key Shape Points")
|
|
|
|
|
|
class MASK_MT_mask(Menu):
|
|
bl_label = "Mask"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.menu("MASK_MT_transform")
|
|
layout.operator("mask.feather_weight_clear")
|
|
|
|
layout.separator()
|
|
layout.operator("mask.cyclic_toggle")
|
|
layout.operator("mask.handle_type_set")
|
|
layout.operator("mask.normals_make_consistent")
|
|
layout.operator("mask.switch_direction")
|
|
|
|
layout.separator()
|
|
layout.operator("mask.copy_splines")
|
|
layout.operator("mask.paste_splines")
|
|
|
|
layout.separator()
|
|
layout.operator("mask.parent_clear")
|
|
layout.operator("mask.parent_set")
|
|
|
|
layout.separator()
|
|
layout.menu("MASK_MT_animation")
|
|
|
|
layout.separator()
|
|
layout.menu("MASK_MT_visibility")
|
|
layout.operator("mask.delete")
|
|
|
|
|
|
class MASK_MT_add(Menu):
|
|
bl_idname = "MASK_MT_add"
|
|
bl_label = "Add"
|
|
bl_translation_context = i18n_contexts.operator_default
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator_context = 'INVOKE_REGION_WIN'
|
|
layout.operator("mask.primitive_circle_add", text="Circle", icon='MESH_CIRCLE')
|
|
layout.operator("mask.primitive_square_add", text="Square", icon='MESH_PLANE')
|
|
|
|
|
|
class MASK_MT_visibility(Menu):
|
|
bl_label = "Show/Hide"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("mask.hide_view_clear")
|
|
layout.operator("mask.hide_view_set", text="Hide Selected").unselected = False
|
|
layout.operator("mask.hide_view_set", text="Hide Unselected").unselected = True
|
|
|
|
|
|
class MASK_MT_transform(Menu):
|
|
bl_label = "Transform"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("transform.translate")
|
|
layout.operator("transform.rotate")
|
|
layout.operator("transform.resize")
|
|
|
|
layout.separator()
|
|
layout.operator("transform.tosphere")
|
|
layout.operator("transform.shear")
|
|
layout.operator("transform.push_pull")
|
|
|
|
layout.separator()
|
|
layout.operator("transform.transform", text="Scale Feather").mode = 'MASK_SHRINKFATTEN'
|
|
|
|
|
|
class MASK_MT_animation(Menu):
|
|
bl_label = "Animation"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("mask.shape_key_clear")
|
|
layout.operator("mask.shape_key_insert")
|
|
layout.operator("mask.shape_key_feather_reset")
|
|
layout.operator("mask.shape_key_rekey")
|
|
|
|
|
|
class MASK_MT_select(Menu):
|
|
bl_label = "Select"
|
|
|
|
def draw(self, _context):
|
|
layout = self.layout
|
|
|
|
layout.operator("mask.select_all", text="All").action = 'SELECT'
|
|
layout.operator("mask.select_all", text="None").action = 'DESELECT'
|
|
layout.operator("mask.select_all", text="Invert").action = 'INVERT'
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.select_box")
|
|
layout.operator("mask.select_circle")
|
|
layout.operator_menu_enum("mask.select_lasso", "mode")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.select_more")
|
|
layout.operator("mask.select_less")
|
|
|
|
layout.separator()
|
|
|
|
layout.operator("mask.select_linked", text="Select Linked")
|
|
|
|
|
|
classes = (
|
|
MASK_UL_layers,
|
|
MASK_MT_mask,
|
|
MASK_MT_add,
|
|
MASK_MT_visibility,
|
|
MASK_MT_transform,
|
|
MASK_MT_animation,
|
|
MASK_MT_select,
|
|
)
|
|
|
|
if __name__ == "__main__": # only for live edit.
|
|
from bpy.utils import register_class
|
|
for cls in classes:
|
|
register_class(cls)
|