gp_toolbox/OP_layer_manager.py

814 lines
28 KiB
Python
Raw Normal View History

from os import error
import bpy
import re
from bpy.types import Operator
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.app.handlers import persistent
from .utils import get_addon_prefs, is_vector_close
# --- OPS ---
# PATTERN = r'([A-Z]{2})?_?([A-Z]{2})?_?(.*)' # bad ! match whithout separator
# pattern = r'(?:(^[A-Z]{2})_)?(?:([A-Z]{2})_)?(.*)' # matching only two letter
# pattern = r'^([A-Z]{2}_)?([A-Z]{2}_)?(.*)' # matching letters with separator
# pattern = r'^([A-Z]{1,6}_)?([A-Z]{1,6}_)?(.*)' # matching capital letters from one to six
# pattern = r'^([A-Z]{1,6}_)?([A-Z]{1,6}_)?(.*?)(_[A-Z]{2})?$' # 2 letter suffix
# pattern = r'^(?P<tag>[A-Z]{1,6}_)?(?P<tag2>[A-Z]{1,6}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?$' # named
# pattern = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<tag2>[A-Z]{1,6}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?$' # group start ' - '
# PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<tag2>[A-Z]{1,6}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
def layer_name_build(layer, prefix='', desc='', suffix=''):
'''GET a layer and argument to build and assign name
return new name
'''
prefs = get_addon_prefs()
sep = prefs.separator
name = old = layer.info
pattern = PATTERN.replace('_', sep) # set separator
res = re.search(pattern, name.strip())
# prefix -> tag
# prefix2 -> tag2
# desc -> name
# suffix -> sfix
grp = '' if res.group('grp') is None else res.group('grp')
tag = '' if res.group('tag') is None else res.group('tag')
# tag2 = '' if res.group('tag2') is None else res.group('tag2')
name = '' if res.group('name') is None else res.group('name')
sfix = '' if res.group('sfix') is None else res.group('sfix')
inc = '' if res.group('inc') is None else res.group('inc')
if grp:
grp = ' ' + grp # name is strip(), so grp first spaces are gones.
if prefix:
if prefix == 'prefixkillcode':
tag = ''
else:
tag = prefix.upper().strip() + sep
# if prefix2:
# tag2 = prefix2.upper().strip() + sep
if desc:
name = desc
if suffix:
if suffix == 'suffixkillcode':
sfix = ''
else:
sfix = sep + suffix.upper().strip()
# check if name is available without the increment ending
new = f'{grp}{tag}{name}{sfix}'
layer.info = new
## update name in modifier targets
if old != new:
# find objects using this GP datablock
for ob_user in [o for o in bpy.data.objects if o.data == layer.id_data]: # bpy.context.scene.objects
# maybe a more elegant way exists to find all objects users ?
# update Gpencil modifier targets
for mod in ob_user.grease_pencil_modifiers:
if not hasattr(mod, 'layer'):
continue
if mod.layer == old:
mod.layer = new
"""
def layer_name_build(layer, prefix='', prefix2='', desc='', suffix=''):
'''GET a layer and infos to build name
Can take one or two prefix and description/name of the layer)
'''
prefs = get_addon_prefs()
sep = prefs.separator
name = layer.info
pattern = pattern.replace('_', sep) # set separator
res = re.search(pattern, name.strip())
p1 = '' if res.group(1) is None else res.group(1)
p2 = '' if res.group(2) is None else res.group(2)
p3 = '' if res.group(3) is None else res.group(3)
p4 = '' if res.group(4) is None else res.group(4)
if prefix:
if prefix == 'prefixkillcode':
p1 = ''
else:
p1 = prefix.upper().strip() + sep
if prefix2:
p2 = prefix2.upper().strip() + sep
if desc:
p3 = desc
if suffix:
if suffix == 'suffixkillcode':
p4 = ''
else:
p4 = sep + suffix.upper().strip()
new = f'{p1}{p2}{p3}{p4}'
layer.info = new
"""
## multi-prefix solution (Caps letters)
class GPTB_OT_layer_name_build(Operator):
bl_idname = "gp.layer_name_build"
bl_label = "Layer Name Build"
bl_description = "Change prefix of layer name"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
prefix : StringProperty(default='', options={'SKIP_SAVE'})
# prefix2 : StringProperty(default='', options={'SKIP_SAVE'})
prefix_tooltip : StringProperty(default='', options={'SKIP_SAVE'})
desc : StringProperty(default='', options={'SKIP_SAVE'})
suffix : StringProperty(default='', options={'SKIP_SAVE'})
@classmethod
def description(cls, context, properties):
if properties.prefix_tooltip:
return f"Use prefix: {properties.prefix} ({properties.prefix_tooltip})"
else:
return f"Use prefix: {properties.prefix}"
def execute(self, context):
ob = context.object
gpl = ob.data.layers
act = gpl.active
if not act:
self.report({'ERROR'}, 'no layer active')
return {"CANCELLED"}
layer_name_build(act, prefix=self.prefix, desc=self.desc, suffix=self.suffix)
## Deactivate multi-selection on layer !
## somethimes it affect a random layer that is still considered selected
# for l in gpl:
# if l.select or l == act:
# layer_name_build(l, prefix=self.prefix, desc=self.desc, suffix=self.suffix)
return {"FINISHED"}
2021-07-29 11:20:43 +02:00
def grp_toggle(l, mode='TOGGLE'):
'''take mode in (TOGGLE, GROUP, UNGROUP) '''
grp_item_id = ' - '
res = re.search(r'^(\s{1,3}-\s{0,3})(.*)', l.info)
2021-07-29 11:20:43 +02:00
if not res and mode in ('TOGGLE', 'GROUP'):
# No gpr : add group prefix after stripping all space and dash
l.info = grp_item_id + l.info.lstrip(' -')
2021-07-29 11:20:43 +02:00
elif res and mode in ('TOGGLE', 'UNGROUP'):
# found : delete group prefix
l.info = res.group(2)
2021-07-29 11:20:43 +02:00
class GPTB_OT_layer_group_toggle(Operator):
bl_idname = "gp.layer_group_toggle"
bl_label = "Group Toggle"
bl_description = "Group or ungroup a layer"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return True
# group : StringProperty(default='', options={'SKIP_SAVE'})
def execute(self, context):
ob = context.object
gpl = ob.data.layers
act = gpl.active
if not act:
self.report({'ERROR'}, 'no layer active')
return {"CANCELLED"}
for l in gpl:
if l.select or l == act:
grp_toggle(l)
return {"FINISHED"}
class GPTB_OT_layer_new_group(Operator):
bl_idname = "gp.layer_new_group"
bl_label = "New Group"
bl_description = "Create a group from active layer"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
ob = context.object
gpl = ob.data.layers
act = gpl.active
if not act:
self.report({'ERROR'}, 'no layer active')
return {"CANCELLED"}
2021-07-29 11:20:43 +02:00
res = re.search(PATTERN, act.info)
if not res:
self.report({'ERROR'}, 'Could not create a group name, create a layer manually')
return {"CANCELLED"}
2021-07-29 11:20:43 +02:00
name = res.group('name').strip(' -')
if not name:
self.report({'ERROR'}, f'No name found in {act.info}')
return {"CANCELLED"}
2021-07-29 11:20:43 +02:00
if name in [l.info.strip(' -') for l in gpl]:
self.report({'WARNING'}, f'Name already exists: {act.info}')
return {"FINISHED"}
2021-07-29 11:20:43 +02:00
grp_toggle(act, mode='GROUP')
n = gpl.new(name, set_active=False)
n.use_onion_skinning = n.use_lights = False
n.hide = True
n.opacity = 0
return {"FINISHED"}
#-## SELECTION MANAGEMENT ##-#
def activate_channel_group_color(context):
if not context.preferences.edit.use_anim_channel_group_colors:
context.preferences.edit.use_anim_channel_group_colors = True
def refresh_areas():
for area in bpy.context.screen.areas:
area.tag_redraw()
def build_layers_targets_from_dopesheet(context):
'''Return all selected layers on context GP dopesheet according to seelction and filters'''
ob = context.object
gpl = context.object.data.layers
act = gpl.active
dopeset = context.space_data.dopesheet
if dopeset.show_only_selected:
pool = [o for o in context.selected_objects if o.type == 'GPENCIL']
else:
pool = [o for o in context.scene.objects if o.type == 'GPENCIL']
if not dopeset.show_hidden:
pool = [o for o in pool if o.visible_get()]
layer_pool = [l for o in pool for l in o.data.layers]
layer_pool = list(set(layer_pool)) # remove dupli-layers from same data source with
# apply search filter
if dopeset.filter_text:
layer_pool = [l for l in layer_pool if (dopeset.filter_text.lower() in l.info.lower()) ^ dopeset.use_filter_invert]
return layer_pool
def build_dope_gp_list(layer_list):
'''Take a list of GP layers return a dict with pairs {gp data : own layer list}'''
from collections import defaultdict
gps = defaultdict(list)
for l in layer_list:
gps[l.id_data].append(l)
return gps
class GPTB_OT_select_set_same_prefix(Operator):
bl_idname = "gp.select_same_prefix"
bl_label = "Select Same Prefix"
bl_description = "Select layers that have the same prefix as active\nSet with ctrl+clic"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
mode : EnumProperty(default='SELECT', options={'SKIP_SAVE'},
items=(
('SELECT', "Select", "Select layer with same prefix as active"),
('SET', "Set", "Set prefix on selected layer to the same as active"),
),
)
def invoke(self, context, event):
if event.ctrl:
self.mode = 'SET'
return self.execute(context)
def execute(self, context):
prefs = get_addon_prefs()
sep = prefs.separator # '_'
gp = context.object.data
act = gp.layers.active
pool = build_layers_targets_from_dopesheet(context)
if not pool:
self.report({'ERROR'}, 'No layers found in current GP dopesheet')
return {"CANCELLED"}
gp_dic = build_dope_gp_list(pool)
if not act:
# Check in other displayed layer if there is an active one
for gp, _layer_list in gp_dic.items():
if gp.layers.active:
# overwrite gp variable at the same time
act = gp.layers.active
break
if not act:
self.report({'ERROR'}, 'No active layer to base action')
return {"CANCELLED"}
print(f'Select/Set ref layer: {gp.name} > {gp.layers.active.info}')
res = re.search(PATTERN, act.info)
if not res:
self.report({'ERROR'}, f'Error scanning {act.info}')
return {"CANCELLED"}
namespace = res.group('tag')
if not namespace:
self.report({'WARNING'}, f'No prefix detected in {act.info} with separator {sep}')
return {"CANCELLED"}
if self.mode == 'SELECT':
## with split
# namespace = act.info.split(sep,1)[0]
# namespace_bool_list = [l.info.split(sep,1)[0] == namespace for l in gpl]
## with reg # only active
# namespace_bool_list = [l.info.split(sep,1)[0] + sep == namespace for l in gpl]
# gpl.foreach_set('select', namespace_bool_list)
## don't work Need Foreach set per gp
# for l in pool:
# l.select = l.info.split(sep,1)[0] + sep == namespace
for gp, layers in gp_dic.items():
# check namespace + restrict selection to visible layers according to filters
# TODO : Should use the regex pattern to detect and compare r.group('tag')
namespace_bool_list = [(l in layers) and (l.info.split(sep,1)[0] + sep == namespace) for l in gp.layers]
gp.layers.foreach_set('select', namespace_bool_list)
elif self.mode == 'SET':
for l in pool:
if not l.select or l == act:
continue
layer_name_build(l, prefix=namespace.strip(sep))
refresh_areas()
return {"FINISHED"}
class GPTB_OT_select_set_same_color(Operator):
bl_idname = "gp.select_same_color"
bl_label = "Select Same Color"
bl_description = "Select layers that have the same color as active\nSet with ctrl+clic"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
mode : EnumProperty(default='SELECT', options={'SKIP_SAVE'},
items=(
('SELECT', "Select", "Select layer with same prefix as active"),
('SET', "Set", "Set prefix on selected layer to the same as active"),
),
)
def invoke(self, context, event):
if event.ctrl:
self.mode = 'SET'
return self.execute(context)
def execute(self, context):
gp = context.object.data
act = gp.layers.active
pool = build_layers_targets_from_dopesheet(context)
if not pool:
self.report({'ERROR'}, 'No layers found in current GP dopesheet')
return {"CANCELLED"}
gp_dic = build_dope_gp_list(pool)
if not act:
# Check in other displayed layer if there is an active one
for gp, _layer_list in gp_dic.items():
if gp.layers.active:
# overwrite gp variable at the same time
act = gp.layers.active
break
if not act:
self.report({'ERROR'}, 'No active layer to base action')
return {"CANCELLED"}
print(f'Select/Set ref layer: {gp.name} > {gp.layers.active.info}')
color = act.channel_color
if self.mode == 'SELECT':
## NEED FOREACH TO APPLY SELECT
## Only on active object
# same_color_bool = [l.channel_color == act.channel_color for l in gpl]
# gpl.foreach_set('select', same_color_bool) # only
# On multiple objects -- don't work, need foreach
# for l in pool:
# print(l.id_data.name, l.info, l.channel_color == act.channel_color)
# l.select = l.channel_color == act.channel_color
"""
gps = []
for l in pool:
if l.id_data not in gps:
gps.append(l.id_data)
for gp in gps:
same_color_bool = [(l in pool) and is_vector_close(l.channel_color, color) for l in gp.layers]
gp.layers.foreach_set('select', same_color_bool)
"""
for gp, layers in gp_dic.items():
# check color and restrict selection to visible layers according to filters
same_color_bool = [(l in layers) and is_vector_close(l.channel_color, color) for l in gp.layers]
gp.layers.foreach_set('select', same_color_bool)
elif self.mode == 'SET':
activate_channel_group_color(context)
for l in pool: # only on active object use gpl
if not l.select or l == act:
continue
l.channel_color = color
refresh_areas()
return {"FINISHED"}
def replace_layer_name(target, replacement, selected_only=True, prefix_only=True, regex=False):
prefs = get_addon_prefs()
sep = prefs.separator
if not target:
return
gpl = bpy.context.object.data.layers
if selected_only:
lays = [l for l in gpl if l.select] # exclude : l.info != 'background'
else:
lays = [l for l in gpl] # exclude : if l.info != 'background'
ct = 0
for l in lays:
old = l.info
if regex:
new = re.sub(target, replacement, l.info)
if old != new:
l.info = new
print('rename:', old, '-->', new)
ct += 1
continue
if prefix_only:
if not sep in l.info:
# only if separator exists
continue
splited = l.info.split(sep)
prefix = splited[0]
new_prefix = prefix.replace(target, replacement)
if prefix != new_prefix:
splited[0] = new_prefix
l.info = sep.join(splited)
print('rename:', old, '-->', l.info)
ct += 1
else:
new = l.info.replace(target, replacement)
if old != new:
l.info = new
print('rename:', old, '-->', new)
ct += 1
return ct
class GPTB_OT_rename_gp_layer(Operator):
'''rename GP layers based on a search and replace'''
bl_idname = "gp.rename_gp_layers"
bl_label = "Rename Gp Layers"
bl_description = "Search/Replace string in all GP layers"
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
find: StringProperty(name="Find", description="Name to replace", default="", maxlen=0, options={'ANIMATABLE'}, subtype='NONE')
replace: StringProperty(name="Repl", description="New name placed", default="", maxlen=0, options={'ANIMATABLE'}, subtype='NONE')
selected: BoolProperty(name="Selected Only", description="Affect only selected layers", default=False)
prefix: BoolProperty(name="Prefix Only", description="Affect only prefix of name (skip layer without separator in name)", default=False)
use_regex: BoolProperty(name="Regex", description="use regular expression (advanced), equivalent to python re.sub()", default=False)
def execute(self, context):
count = replace_layer_name(self.find, self.replace, selected_only=self.selected, prefix_only=self.prefix, regex=self.use_regex)
if count:
mess = str(count) + ' layers renamed'
self.report({'INFO'}, mess)
else:
self.report({'WARNING'}, 'No text found !')
return{'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
row = layout.row()
row_a= row.row()
row_a.prop(self, "selected")
row_b= row.row()
row_b.prop(self, "prefix")
row_c= row.row()
row_c.prop(self, "use_regex")
row_b.active = not self.use_regex
layout.prop(self, "find")
layout.prop(self, "replace")
## --- UI layer panel---
def layer_name_builder_ui(self, context):
'''appended to DATA_PT_gpencil_layers'''
prefs = get_addon_prefs()
if not prefs.show_prefix_buttons:
return
if not len(prefs.prefixes.namespaces) and not prefs.suffixes:
return
layout = self.layout
# {'EDIT_GPENCIL', 'PAINT_GPENCIL','SCULPT_GPENCIL','WEIGHT_GPENCIL', 'VERTEX_GPENCIL'}
# layout.separator()
col = layout.column()
line_limit = 8
if len(prefs.prefixes.namespaces):
ct = 0
# can't use enumerate cause there can be hided prefix
for namespace in prefs.prefixes.namespaces:
if namespace.hide:
continue
if ct % line_limit == 0:
row = col.row(align=True)
ct += 1
op = row.operator("gp.layer_name_build", text=namespace.prefix)
op.prefix = namespace.prefix
op.prefix_tooltip = namespace.name
if ct > 0:
row.operator("gp.layer_name_build", text='', icon='X').prefix = 'prefixkillcode'
## old single string prefix method
"""
if prefs.prefixes:
p = prefs.prefixes.split(',')
for i, prefix in enumerate(all_prefixes):
if i % line_limit == 0:
row = col.row(align=True)
row.operator("gp.layer_name_build", text=prefix.upper() ).prefix = prefix
row.operator("gp.layer_name_build", text='', icon='X').prefix = 'prefixkillcode'
"""
## secondary prefix ?
## name (description)
row = col.row(align=True)
row.prop(context.scene.gptoolprops, 'layer_name', text='')
## mimic groups using dash (disabled for now)
# row.operator("gp.layer_new_group", text='', icon='COLLECTION_NEW')
# row.operator("gp.layer_group_toggle", text='', icon='OUTLINER_OB_GROUP_INSTANCE')
## no need for desc ops, already trigerred from update
# row.operator("gp.layer_name_build", text='', icon='EVENT_RETURN').desc = context.scene.gptoolprops.layer_name
if prefs.suffixes:
all_suffixes = prefs.suffixes.split(',')
for i, suffix in enumerate(all_suffixes):
if i % line_limit == 0:
row = col.row(align=True)
row.operator("gp.layer_name_build", text=suffix.upper() ).suffix = suffix
row.operator("gp.layer_name_build", text='', icon='X').suffix = 'suffixkillcode'
## --- UI dopesheet ---
def gpencil_dopesheet_header(self, context):
'''to append in DOPESHEET_HT_header'''
layout = self.layout
st = context.space_data
if st.mode != 'GPENCIL':
return
row = layout.row(align=True)
# row.operator('gp.active_channel_color_to_selected', text='', icon='RESTRICT_COLOR_ON')
row.operator('gp.select_same_prefix', text='', icon='SYNTAX_OFF') # SORTALPHA, SMALL_CAPS
row.operator('gp.select_same_color', text='', icon='RESTRICT_COLOR_ON')
## --- UI context menu ---
def gpencil_layer_dropdown_menu(self, context):
'''to append in GPENCIL_MT_layer_context_menu'''
self.layout.operator('gp.rename_gp_layers', icon='BORDERMOVE')
## handler and msgbus
def obj_layer_name_callback():
'''assign layer name properties so user an tweak it'''
ob = bpy.context.object
if not ob or ob.type != 'GPENCIL':
return
if not ob.data.layers.active:
return
2021-07-29 11:20:43 +02:00
## Set selection to active object ot avoid un-sync selection on Layers stack
## (happen when an objet is selected but not active with 'lock object mode')
for l in ob.data.layers:
l.select = l == ob.data.layers.active
2021-07-29 11:20:43 +02:00
res = re.search(PATTERN, ob.data.layers.active.info.strip())
if not res:
return
if not res.group('name'):
return
2021-07-30 18:24:54 +02:00
# print('grp:', res.group('grp'))
# print('tag:', res.group('tag'))
# print('name:', res.group('name'))
# print('sfix:', res.group('sfix'))
# print('inc:', res.group('inc'))
bpy.context.scene.gptoolprops['layer_name'] = res.group('name')
@persistent
def subscribe_handler(dummy):
subscribe_to = (bpy.types.GreasePencilLayers, "active_index")
bpy.msgbus.subscribe_rna(
key=subscribe_to,
# owner of msgbus subcribe (for clearing later)
# owner=handle,
2021-07-30 18:24:54 +02:00
owner=bpy.types.GreasePencil, # <-- can attach to an ID during all it's lifetime...
# Args passed to callback function (tuple)
args=(),
# Callback function for property update
notify=obj_layer_name_callback,
options={'PERSISTENT'},
)
##--- Add layers
class GPTB_PT_layer_name_ui(bpy.types.Panel):
bl_space_type = 'TOPBAR' # dummy
bl_region_type = 'HEADER'
bl_options = {'INSTANCED'}
bl_label = 'Layer Rename'
bl_ui_units_x = 14
def invoke(self, context, event):
# all_addons_l = get_modifier_list()
wm = context.window_manager
wm.invoke_props_dialog(self) # , width=600
return {'FINISHED'}
def draw(self, context):
layout = self.layout
# def row_with_icon(layout, icon):
# # Edit first editable button in popup
# row = layout.row()
# row.activate_init = True
# row.label(icon=icon)
# return row
# row = row_with_icon(layout, 'OUTLINER_DATA_GP_LAYER')
row = layout.row()
row.activate_init = True
row.label(icon='OUTLINER_DATA_GP_LAYER')
row.prop(context.object.data.layers.active, 'info', text='')
def add_layer(context):
bpy.ops.gpencil.layer_add()
context.object.data.layers.active.use_lights = False
class GPTB_OT_add_gp_layer_with_rename(Operator):
bl_idname = "gp.add_layer_rename"
bl_label = "Add Rename GPencil Layer"
bl_description = "Create a new gp layer with use light toggled off and popup a rename box"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
def execute(self, context):
add_layer(context)
bpy.ops.wm.call_panel(name="GPTB_PT_layer_name_ui", keep_open = False)
return {"FINISHED"}
class GPTB_OT_add_gp_layer(Operator):
bl_idname = "gp.add_layer"
bl_label = "Add GPencil Layer"
bl_description = "Create a new gp layer with use light toggled off"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
def execute(self, context):
add_layer(context)
return {"FINISHED"}
addon_keymaps = []
def register_keymaps():
addon = bpy.context.window_manager.keyconfigs.addon
##---# Insert Layers
## Insert new gp layer (with no use_light)
km = addon.keymaps.new(name = "Grease Pencil", space_type = "EMPTY") # global (only paint ?)
kmi = km.keymap_items.new('gp.add_layer', type='INSERT', value='PRESS')
addon_keymaps.append((km, kmi))
## Insert new gp layer (with no use_light and immediately pop up a box to rename)
# km = addon.keymaps.new(name = "Grease Pencil", space_type = "EMPTY") # global (only paint ?)
kmi = km.keymap_items.new('gp.add_layer_rename', type='INSERT', value='PRESS', shift=True)
addon_keymaps.append((km, kmi))
##---# F2 rename calls
## Direct rename active layer in Paint mode
km = addon.keymaps.new(name = "Grease Pencil Stroke Paint Mode", space_type = "EMPTY")
kmi = km.keymap_items.new('wm.call_panel', type='F2', value='PRESS')
kmi.properties.name = 'GPTB_PT_layer_name_ui'
kmi.properties.keep_open = False
addon_keymaps.append((km, kmi))
## Same in edit mode
km = addon.keymaps.new(name = "Grease Pencil Stroke Edit Mode", space_type = "EMPTY")
kmi = km.keymap_items.new('wm.call_panel', type='F2', value='PRESS')
kmi.properties.name = 'GPTB_PT_layer_name_ui'
kmi.properties.keep_open = False
addon_keymaps.append((km, kmi))
def unregister_keymaps():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
classes=(
GPTB_OT_rename_gp_layer,
GPTB_OT_layer_name_build,
GPTB_OT_layer_group_toggle,
GPTB_OT_layer_new_group,
GPTB_OT_select_set_same_prefix,
GPTB_OT_select_set_same_color,
## Layer add and pop-up rename
GPTB_PT_layer_name_ui, # pop-up
GPTB_OT_add_gp_layer_with_rename, # shift+Ins
GPTB_OT_add_gp_layer, # Ins
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.DATA_PT_gpencil_layers.prepend(layer_name_builder_ui)
bpy.types.DOPESHEET_HT_header.append(gpencil_dopesheet_header)
bpy.types.GPENCIL_MT_layer_context_menu.append(gpencil_layer_dropdown_menu)
bpy.app.handlers.load_post.append(subscribe_handler) # need to restart after first activation
register_keymaps()
def unregister():
unregister_keymaps()
bpy.app.handlers.load_post.remove(subscribe_handler)
bpy.types.GPENCIL_MT_layer_context_menu.remove(gpencil_layer_dropdown_menu)
bpy.types.DOPESHEET_HT_header.remove(gpencil_dopesheet_header)
bpy.types.DATA_PT_gpencil_layers.remove(layer_name_builder_ui)
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
# delete layer index trigger
bpy.msgbus.clear_by_owner(bpy.types.GreasePencil)