gp_toolbox/OP_layer_manager.py

786 lines
27 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
# TODO: allow a more flexible prefix pattern
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
2024-11-11 15:56:43 +01:00
name = old = layer.name
2023-02-07 12:03:00 +01:00
pattern = PATTERN.replace('_', sep) # set separator
2023-02-07 12:03:00 +01:00
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
2023-02-07 12:03:00 +01:00
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}'
2024-11-11 15:56:43 +01:00
layer.name = new
2023-02-07 12:03:00 +01:00
## 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 ?
2023-02-07 12:03:00 +01:00
# update Gpencil modifier targets
2024-11-27 16:40:31 +01:00
for mod in ob_user.modifiers:
2024-11-11 18:44:35 +01:00
if not hasattr(mod, 'layer_filter'):
continue
2024-11-11 18:44:35 +01:00
if mod.layer_filter == old:
mod.layer_filter = new
2023-02-07 12:03:00 +01:00
"""
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
2024-11-11 15:56:43 +01:00
name = layer.name
2023-02-07 12:03:00 +01:00
pattern = pattern.replace('_', sep) # set separator
2023-02-07 12:03:00 +01:00
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
2023-02-07 12:03:00 +01:00
if desc:
p3 = desc
2023-02-07 12:03:00 +01:00
if suffix:
if suffix == 'suffixkillcode':
p4 = ''
else:
p4 = sep + suffix.upper().strip()
new = f'{p1}{p2}{p3}{p4}'
2024-11-11 15:56:43 +01:00
layer.name = 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'})
desc : StringProperty(default='', options={'SKIP_SAVE'})
suffix : StringProperty(default='', options={'SKIP_SAVE'})
tooltip : StringProperty(default='', options={'SKIP_SAVE'})
@classmethod
def description(cls, context, properties):
tag = properties.prefix if properties.prefix else properties.suffix
if properties.tooltip:
return f"Use prefix: {tag} ({properties.tooltip})"
else:
return f"Use prefix: {tag}"
def execute(self, context):
ob = context.object
gpl = ob.data.layers
act = gpl.active
if not act:
act = ob.data.layer_groups.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"}
#-## 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
2023-02-07 12:03:00 +01:00
if dopeset.show_only_selected:
2024-11-11 15:35:39 +01:00
pool = [o for o in context.selected_objects if o.type == 'GREASEPENCIL']
else:
2024-11-11 15:35:39 +01:00
pool = [o for o in context.scene.objects if o.type == 'GREASEPENCIL']
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
2023-02-07 12:03:00 +01:00
# apply search filter
if dopeset.filter_text:
2024-11-11 15:56:43 +01:00
layer_pool = [l for l in layer_pool if (dopeset.filter_text.lower() in l.name.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):
2024-11-11 15:35:39 +01:00
return context.object and context.object.type == 'GREASEPENCIL'
2023-02-07 12:03:00 +01:00
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"}
2024-11-11 15:56:43 +01:00
print(f'Select/Set ref layer: {gp.name} > {gp.layers.active.name}')
2024-11-11 15:56:43 +01:00
res = re.search(PATTERN, act.name)
if not res:
2024-11-11 15:56:43 +01:00
self.report({'ERROR'}, f'Error scanning {act.name}')
return {"CANCELLED"}
namespace = res.group('tag')
if not namespace:
2024-11-11 15:56:43 +01:00
self.report({'WARNING'}, f'No prefix detected in {act.name} with separator {sep}')
return {"CANCELLED"}
if self.mode == 'SELECT':
## with split
2024-11-11 15:56:43 +01:00
# namespace = act.name.split(sep,1)[0]
# namespace_bool_list = [l.name.split(sep,1)[0] == namespace for l in gpl]
2023-02-07 12:03:00 +01:00
## with reg # only active
2024-11-11 15:56:43 +01:00
# namespace_bool_list = [l.name.split(sep,1)[0] + sep == namespace for l in gpl]
# gpl.foreach_set('select', namespace_bool_list)
2023-02-07 12:03:00 +01:00
## don't work Need Foreach set per gp
# for l in pool:
2024-11-11 15:56:43 +01:00
# l.select = l.name.split(sep,1)[0] + sep == namespace
2023-02-07 12:03:00 +01:00
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')
2024-11-11 15:56:43 +01:00
namespace_bool_list = [(l in layers) and (l.name.split(sep,1)[0] + sep == namespace) for l in gp.layers]
gp.layers.foreach_set('select', namespace_bool_list)
2023-02-07 12:03:00 +01:00
elif self.mode == 'SET':
2023-02-07 12:03:00 +01:00
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):
2024-11-11 15:35:39 +01:00
return context.object and context.object.type == 'GREASEPENCIL'
2023-02-07 12:03:00 +01:00
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"}
2024-11-11 15:56:43 +01:00
print(f'Select/Set ref layer: {gp.name} > {gp.layers.active.name}')
color = act.channel_color
if self.mode == 'SELECT':
## NEED FOREACH TO APPLY SELECT
2023-02-07 12:03:00 +01:00
## 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:
2024-11-11 15:56:43 +01:00
# print(l.id_data.name, l.name, l.channel_color == act.channel_color)
# l.select = l.channel_color == act.channel_color
2023-02-07 12:03:00 +01:00
"""
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:
2024-11-11 15:56:43 +01:00
lays = [l for l in gpl if l.select] # exclude : l.name != 'background'
else:
2024-11-11 15:56:43 +01:00
lays = [l for l in gpl] # exclude : if l.name != 'background'
ct = 0
for l in lays:
2024-11-11 15:56:43 +01:00
old = l.name
if regex:
2024-11-11 15:56:43 +01:00
new = re.sub(target, replacement, l.name)
if old != new:
2024-11-11 15:56:43 +01:00
l.name = new
print('rename:', old, '-->', new)
ct += 1
continue
if prefix_only:
2024-11-11 15:56:43 +01:00
if not sep in l.name:
# only if separator exists
continue
2024-11-11 15:56:43 +01:00
splited = l.name.split(sep)
prefix = splited[0]
new_prefix = prefix.replace(target, replacement)
if prefix != new_prefix:
splited[0] = new_prefix
2024-11-11 15:56:43 +01:00
l.name = sep.join(splited)
print('rename:', old, '-->', l.name)
ct += 1
else:
2024-11-11 15:56:43 +01:00
new = l.name.replace(target, replacement)
if old != new:
2024-11-11 15:56:43 +01:00
l.name = 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):
2024-11-11 15:35:39 +01:00
return context.object and context.object.type == 'GREASEPENCIL'
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")
2023-02-07 12:03:00 +01:00
row_b= row.row()
row_b.prop(self, "prefix")
row_c= row.row()
2023-02-07 12:03:00 +01:00
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):
2024-11-11 17:27:57 +01:00
'''appended to DATA_PT_grease_pencil_layers'''
2023-02-07 12:03:00 +01:00
prefs = get_addon_prefs()
if not prefs.show_prefix_buttons:
return
if not len(prefs.prefixes.namespaces) and not len(prefs.suffixes.namespaces):
return
layout = self.layout
2024-11-11 15:47:33 +01:00
# {'EDIT_GREASE_PENCIL', 'PAINT_GREASE_PENCIL','SCULPT_GREASE_PENCIL','WEIGHT_GREASE_PENCIL', 'VERTEX_GPENCIL'}
# layout.separator()
col = layout.column()
line_limit = 8
2023-02-07 12:03:00 +01:00
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.tag)
op.prefix = namespace.tag
op.tooltip = namespace.name
2023-02-07 12:03:00 +01:00
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 ?
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'
"""
## name (description of layer content)
row = col.row(align=True)
row.prop(context.scene.gptoolprops, 'layer_name', text='')
2023-02-07 12:03:00 +01:00
## 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
2023-02-07 12:03:00 +01:00
# row.operator("gp.layer_name_build", text='', icon='EVENT_RETURN').desc = context.scene.gptoolprops.layer_name
if len(prefs.suffixes.namespaces):
ct = 0
# can't use enumerate cause there can be hided prefix
for namespace in prefs.suffixes.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.tag)
op.suffix = namespace.tag
op.tooltip = namespace.name
2023-02-07 12:03:00 +01:00
if ct > 0:
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
2024-11-11 15:35:39 +01:00
if st.mode != 'GREASEPENCIL':
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.create_empty_frames', icon='KEYFRAME')
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
2024-11-11 15:35:39 +01:00
if not ob or ob.type != 'GREASEPENCIL':
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
2024-11-11 15:56:43 +01:00
res = re.search(PATTERN, ob.data.layers.active.name.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')
2024-11-27 16:40:31 +01:00
## old gpv2
# def subscribe_layer_change():
# subscribe_to = (bpy.types.GreasePencilLayers, "active_index")
# bpy.msgbus.subscribe_rna(
# key=subscribe_to,
# # owner of msgbus subcribe (for clearing later)
# # owner=handle,
# 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'},
# )
def subscribe_layer_change():
2024-11-27 16:40:31 +01:00
subscribe_to = (bpy.types.GreasePencilv3Layers, "active")
bpy.msgbus.subscribe_rna(
key=subscribe_to,
# owner of msgbus subcribe (for clearing later)
# owner=handle,
2024-11-27 16:40:31 +01:00
owner=bpy.types.GreasePencilv3, # <-- 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'},
)
2024-11-27 16:40:31 +01:00
@persistent
def subscribe_layer_change_handler(dummy):
subscribe_layer_change()
##--- 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'}
2023-02-07 12:03:00 +01:00
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')
2023-02-07 12:03:00 +01:00
row = layout.row()
row.activate_init = True
row.label(icon='OUTLINER_DATA_GP_LAYER')
2024-11-27 16:40:31 +01:00
row.prop(context.object.data.layers.active, 'name', 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):
2024-11-11 15:35:39 +01:00
return context.object and context.object.type == 'GREASEPENCIL'
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):
2024-11-11 15:35:39 +01:00
return context.object and context.object.type == 'GREASEPENCIL'
def execute(self, context):
add_layer(context)
return {"FINISHED"}
addon_keymaps = []
def register_keymaps():
if bpy.app.background:
return
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))
2023-02-07 12:03:00 +01:00
##---# F2 rename calls
## Direct rename active layer in Paint mode
2024-11-11 17:49:22 +01:00
km = addon.keymaps.new(name = "Grease Pencil 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():
if bpy.app.background:
return
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_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)
2023-02-07 12:03:00 +01:00
2024-11-11 17:27:57 +01:00
bpy.types.DATA_PT_grease_pencil_layers.prepend(layer_name_builder_ui)
bpy.types.DOPESHEET_HT_header.append(gpencil_dopesheet_header)
bpy.types.GREASE_PENCIL_MT_grease_pencil_add_layer_extra.append(gpencil_layer_dropdown_menu)
bpy.app.handlers.load_post.append(subscribe_layer_change_handler)
register_keymaps()
# Directly set msgbus to work at first addon activation
bpy.app.timers.register(subscribe_layer_change, first_interval=1)
def unregister():
unregister_keymaps()
bpy.app.handlers.load_post.remove(subscribe_layer_change_handler)
bpy.types.GREASE_PENCIL_MT_grease_pencil_add_layer_extra.remove(gpencil_layer_dropdown_menu)
bpy.types.DOPESHEET_HT_header.remove(gpencil_dopesheet_header)
2024-11-11 17:27:57 +01:00
bpy.types.DATA_PT_grease_pencil_layers.remove(layer_name_builder_ui)
2023-02-07 12:03:00 +01:00
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
2023-02-07 12:03:00 +01:00
2024-11-27 16:40:31 +01:00
# Delete layer index trigger
# /!\ can remove msgbus made for other functions or other addons using same owner
bpy.msgbus.clear_by_owner(bpy.types.GreasePencilv3)