857 lines
31 KiB
Python
Executable File
857 lines
31 KiB
Python
Executable File
# SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
||
bl_info = {
|
||
"name": "GP toolbox",
|
||
"description": "Tool set for Grease Pencil in animation production",
|
||
"author": "Samuel Bernou, Christophe Seux",
|
||
"version": (4, 0, 4),
|
||
"blender": (4, 3, 0),
|
||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||
"warning": "",
|
||
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox",
|
||
"tracker_url": "https://git.autourdeminuit.com/autour_de_minuit/gp_toolbox/issues",
|
||
"category": "3D View",
|
||
}
|
||
|
||
from pathlib import Path
|
||
from shutil import which
|
||
from sys import modules
|
||
from .utils import get_addon_prefs, draw_kmi
|
||
|
||
## GMIC
|
||
from .GP_guided_colorize import GP_colorize
|
||
|
||
## direct tools
|
||
from . import OP_breakdowner
|
||
from . import OP_temp_cutter
|
||
from . import OP_playblast_bg
|
||
from . import OP_playblast
|
||
from . import OP_helpers
|
||
from . import OP_keyframe_jump
|
||
from . import OP_cursor_snap_canvas
|
||
from . import OP_palettes
|
||
from . import OP_palettes_linker
|
||
from . import OP_brushes
|
||
from . import OP_file_checker
|
||
from . import OP_copy_paste
|
||
from . import OP_realign
|
||
# from . import OP_flat_reproject # Disabled
|
||
from . import OP_depth_move
|
||
from . import OP_key_duplicate_send
|
||
from . import OP_layer_manager
|
||
from . import OP_layer_picker
|
||
from . import OP_layer_nav
|
||
from . import OP_material_picker
|
||
from . import OP_git_update
|
||
from . import OP_layer_namespace
|
||
from . import OP_pseudo_tint
|
||
from . import OP_follow_curve
|
||
from . import OP_material_move_to_layer
|
||
# from . import OP_eraser_brush
|
||
# from . import TOOL_eraser_brush
|
||
from . import handler_draw_cam
|
||
from . import keymaps
|
||
|
||
|
||
from . import UI_tools
|
||
|
||
from .properties import (
|
||
GP_PG_ToolsSettings,
|
||
GP_PG_FixSettings,
|
||
GP_PG_namespaces,
|
||
)
|
||
|
||
from bpy.props import (FloatProperty,
|
||
BoolProperty,
|
||
EnumProperty,
|
||
StringProperty,
|
||
IntProperty,
|
||
PointerProperty
|
||
)
|
||
|
||
import bpy
|
||
import os
|
||
from bpy.app.handlers import persistent
|
||
from pathlib import Path
|
||
# from .eyedrop import EyeDropper
|
||
# from .properties import load_icons,remove_icons
|
||
|
||
|
||
### prefs
|
||
# def set_palette_path(self, context):
|
||
# print('value set')
|
||
# self.palette_path = Path(bpy.path.abspath(self["palette_path"])).as_posix()
|
||
|
||
|
||
@persistent
|
||
def remap_relative(dummy):
|
||
# try:
|
||
all_path = [lib for lib in bpy.utils.blend_paths(local=True)]
|
||
bpy.ops.file.make_paths_relative()
|
||
for i, lib in enumerate(bpy.utils.blend_paths(local=True)):
|
||
if all_path[i] != lib:
|
||
print('Remapped:', all_path[i], '\n>> ', lib)
|
||
# except Exception as e:
|
||
# print(e)
|
||
|
||
def remap_on_save_update(self, context):
|
||
pref = get_addon_prefs()
|
||
if pref.use_relative_remap_on_save:
|
||
if not 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
||
bpy.app.handlers.save_pre.append(remap_relative)
|
||
|
||
else:
|
||
if 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
||
bpy.app.handlers.save_pre.remove(remap_relative)
|
||
|
||
|
||
## precise eraser
|
||
# def update_use_precise_eraser(self, context):
|
||
# km, kmi = TOOL_eraser_brush.addon_keymaps[0]
|
||
# kmi.active = self.use_precise_eraser
|
||
|
||
class GPTB_prefs(bpy.types.AddonPreferences):
|
||
bl_idname = __name__
|
||
|
||
## precise eraser
|
||
# use_precise_eraser : BoolProperty(
|
||
# name='Precise Eraser',
|
||
# default=False,
|
||
# update=update_use_precise_eraser)
|
||
|
||
## tabs
|
||
|
||
pref_tabs : EnumProperty(
|
||
items=(('PREF', "Preferences", "Change some preferences of the modal"),
|
||
('KEYS', "Shortcuts", "Customize addon shortcuts"),
|
||
('MAN_OPS', "Operators", "Operator to add Manually"),
|
||
('CHECKS', "Check List", "Customise what should happend when hitting 'check fix' button"),
|
||
# ('UPDATE', "Update", "Check and apply updates"),
|
||
# ('TUTO', "Tutorial", "How to use the tool"),
|
||
# ('KEYMAP', "Keymap", "customise the default keymap"),
|
||
),
|
||
default='PREF')
|
||
|
||
## addon prefs
|
||
|
||
#--# PROJECT PREFERENCES #--#
|
||
# subtype (string) – Enumerator in ['FILE_PATH', 'DIR_PATH', 'FILE_NAME', 'BYTE_STRING', 'PASSWORD', 'NONE'].
|
||
|
||
# update variables
|
||
is_git_repo : BoolProperty(default=False)
|
||
has_git : BoolProperty(default=False)
|
||
|
||
## fps
|
||
|
||
use_relative_remap_on_save : BoolProperty(
|
||
name="Relative Remap On Save",
|
||
description="Always remap all external path to relative when saving\nNeed blender restart if changed",
|
||
default=False,
|
||
update=remap_on_save_update
|
||
)
|
||
|
||
fps : IntProperty(
|
||
name='Frame Rate',
|
||
description="Fps of the project, Used to conform the file when you use Check file operator",
|
||
default=24,
|
||
min=1,
|
||
max=10000
|
||
)
|
||
|
||
## output settings for automated renders
|
||
output_parent_level = IntProperty(
|
||
name='Parent level',
|
||
description="Go up in folder to define a render path relative to the file in upper directotys",
|
||
default=0,
|
||
min=0,
|
||
max=20
|
||
)
|
||
|
||
output_path : StringProperty(
|
||
name="Output path",
|
||
description="Path relative to blend to place render",
|
||
default="//render", maxlen=0, subtype='DIR_PATH')
|
||
|
||
playblast_path : StringProperty(
|
||
name="Playblast Path",
|
||
description="Path to folder for playblasts output",
|
||
default="//playblast", maxlen=0, subtype='DIR_PATH')
|
||
|
||
use_env_palettes : BoolProperty(
|
||
name="Use Project Palettes",
|
||
description="Load the palette path in environnement at startup (key 'PALETTES')",
|
||
default=True,
|
||
)
|
||
|
||
palette_path : StringProperty(
|
||
name="Palettes directory",
|
||
description="Path to palette containing palette.json files to save and load",
|
||
default="", maxlen=0, subtype='DIR_PATH')#, update = set_palette_path
|
||
|
||
warn_base_palette : BoolProperty(
|
||
name="Warn if base palette isn't loaded",
|
||
description="Display a button to load palette base.json if current grease pencil has a no 'line' and 'invisible' materials",
|
||
default=True,
|
||
)
|
||
|
||
mat_link_exclude : StringProperty(
|
||
name="Materials Link Exclude",
|
||
description="List of material name to exclude when using palette linker (separate multiple value with comma, ex: line, rough)",
|
||
default="line,", maxlen=0)
|
||
|
||
use_env_brushes : BoolProperty(
|
||
name="Use Project Brushes",
|
||
description="Load the brushes path in environnement at startup (key 'BRUSHES')",
|
||
default=True,
|
||
)
|
||
|
||
brush_path : StringProperty(
|
||
name="Brushes directory",
|
||
description="Path to brushes containing the blends holding the brushes",
|
||
default="//", maxlen=0, subtype='DIR_PATH')#, update = set_palette_path
|
||
|
||
## namespace
|
||
separator : StringProperty(
|
||
name="Separator",
|
||
description="Character delimiter to use for detecting namespace (prefix), default is '_', space if nothing specified",
|
||
default="_", maxlen=0, subtype='NONE')
|
||
|
||
## Old one string comma separated prefix/suffix list
|
||
# prefixes : StringProperty(
|
||
# name="Layers Prefixes",
|
||
# description="List of prefixes (two capital letters) available for layers(ex: AN,CO,CL)",
|
||
# default="", maxlen=0)
|
||
|
||
# suffixes : StringProperty(
|
||
# name="Layers Suffixes",
|
||
# description="List of suffixes (two capital letters) available for layers(ex: OL,UL)",
|
||
# default="", maxlen=0)
|
||
|
||
prefixes : PointerProperty(type=GP_PG_namespaces)
|
||
suffixes : PointerProperty(type=GP_PG_namespaces)
|
||
|
||
|
||
# use_env_namespace : BoolProperty(
|
||
# name="Use Project namespace",
|
||
# description="Ovewrite prefix/suffix with Project values defined in environnement at startup\n(key 'PREFIXES and SUFFIXES')",
|
||
# default=True,
|
||
# )
|
||
|
||
show_prefix_buttons : BoolProperty(
|
||
name="Show Prefix Buttons",
|
||
description="Show prefix and suffix buttons above layer stack",
|
||
default=True,
|
||
)
|
||
|
||
## Playblast prefs
|
||
playblast_auto_play : BoolProperty(
|
||
name="Playblast auto play",
|
||
description="Open rendered playblast when finished",
|
||
default=True,
|
||
)
|
||
|
||
playblast_auto_open_folder : BoolProperty(
|
||
name="Playblast auto open location",
|
||
description="Open folder of rendered playblast when finished",
|
||
default=False,
|
||
)
|
||
|
||
## render settings
|
||
render_obj_exclusion : StringProperty(
|
||
name="GP obj exclude filter",
|
||
description="List comma separated words to exclude from render list",
|
||
default="old,rough,trash,test")#, subtype='FILE_PATH')
|
||
|
||
render_res_x : IntProperty(
|
||
name='Resolution X',
|
||
description="Resolution on X",
|
||
default=2048,
|
||
min=1,
|
||
max=10000
|
||
)
|
||
render_res_y : IntProperty(
|
||
name='Resolution Y',
|
||
description="Resolution on Y",
|
||
default=1080,
|
||
min=1,
|
||
max=10000
|
||
)
|
||
|
||
## KF jumper
|
||
kfj_use_shortcut: BoolProperty(
|
||
name = "Use Keyframe Jump Shortcut",
|
||
description = "Auto bind shotcut for keyframe jump (else you can bind manually using 'screen.gp_keyframe_jump' id_name)",
|
||
default = True)
|
||
|
||
kfj_prev_keycode : StringProperty(
|
||
name="Jump Prev Shortcut",
|
||
description="Shortcut to trigger previous keyframe jump",
|
||
default="F5")
|
||
|
||
kfj_prev_shift: BoolProperty(
|
||
name = "Shift",
|
||
description = "add shift",
|
||
default = False)
|
||
|
||
kfj_prev_alt: BoolProperty(
|
||
name = "Alt",
|
||
description = "add alt",
|
||
default = False)
|
||
|
||
kfj_prev_ctrl: BoolProperty(
|
||
name = "combine with ctrl",
|
||
description = "add ctrl",
|
||
default = False)
|
||
|
||
kfj_next_keycode : StringProperty(
|
||
name="Jump Next Shortcut",
|
||
description="Shortcut to trigger keyframe jump",
|
||
default="F6")
|
||
|
||
kfj_next_shift: BoolProperty(
|
||
name = "Shift",
|
||
description = "add shift",
|
||
default = False)
|
||
|
||
kfj_next_alt: BoolProperty(
|
||
name = "Alt",
|
||
description = "add alt",
|
||
default = False)
|
||
|
||
kfj_next_ctrl: BoolProperty(
|
||
name = "combine with ctrl",
|
||
description = "add ctrl",
|
||
default = False)
|
||
|
||
|
||
fixprops: bpy.props.PointerProperty(type = GP_PG_FixSettings)
|
||
|
||
## GP Layer navigator
|
||
|
||
nav_use_fade : BoolProperty(
|
||
name='Fade Inactive Layers',
|
||
description='Fade Inactive layers to determine active layer in a glimpse',
|
||
default=True)
|
||
|
||
nav_fade_val : FloatProperty(
|
||
name='Fade Value',
|
||
description='Fade value for other layers when navigating (0=invisible)',
|
||
default=0.1, min=0.0, max=0.95, step=1, precision=2)
|
||
|
||
nav_limit : FloatProperty(
|
||
name='Fade Duration',
|
||
description='Time of other layer faded when using layer navigation',
|
||
default=1.4, min=0.1, max=5, step=2, precision=1, subtype='TIME', unit='TIME')
|
||
|
||
nav_use_fade_in : BoolProperty(
|
||
name='Progressive Fade Back',
|
||
description='Use a fade on other layer when navigating',
|
||
default=True)
|
||
|
||
nav_fade_in_time : FloatProperty(
|
||
name='Fade-In Time',
|
||
description='Duration of the fade',
|
||
default=0.5, min=0.1, max=5, step=2, precision=2, subtype='TIME', unit='TIME')
|
||
|
||
nav_interval : FloatProperty(
|
||
name='Refresh Rate',
|
||
description='Refresh rate for fade updating (upper value means stepped fade)',
|
||
default=0.04, min=0.01, max=0.5, step=3, precision=2, subtype='TIME', unit='TIME')
|
||
|
||
## Temp cutter
|
||
# temp_cutter_use_shortcut: BoolProperty(
|
||
# name = "Use temp cutter Shortcut",
|
||
# description = "Auto assign shortcut for temp_cutter",
|
||
# default = True)
|
||
|
||
def draw_namespaces_list(self, layout, template_list, pg_name, rows=4):
|
||
'''Get layout, property group to draw and default row number'''
|
||
|
||
pg = getattr(self, pg_name)
|
||
row = layout.row(align=True)
|
||
row.template_list(template_list, "", pg, "namespaces", pg, "idx", rows=rows)
|
||
subcol = row.column(align=True) # Lateral right
|
||
subcol.operator("gptb.add_namespace_entry", icon="ADD", text="").propname=pg_name
|
||
subcol.operator("gptb.remove_namespace_entry", icon="REMOVE", text="").propname=pg_name
|
||
subcol.separator()
|
||
op_move = subcol.operator("gptb.move_item", icon="TRIA_UP", text="")
|
||
op_move.propname = pg_name
|
||
op_move.direction = 'UP'
|
||
op_move = subcol.operator("gptb.move_item", icon="TRIA_DOWN", text="")
|
||
op_move.propname = pg_name
|
||
op_move.direction = 'DOWN'
|
||
|
||
## Reset entry (Not needed anymore)
|
||
# subcol.separator()
|
||
# subcol.operator('prefs.reset_gp_toolbox_env', text='', icon='LOOP_BACK').mode = 'PREFIXES'
|
||
|
||
def draw(self, context):
|
||
layout = self.layout## random color
|
||
# layout.use_property_split = True
|
||
|
||
row= layout.row(align=True)
|
||
row.prop(self, "pref_tabs", expand=True)
|
||
|
||
|
||
if self.pref_tabs == 'PREF':
|
||
box = layout.box()
|
||
box.label(text='Project settings')
|
||
|
||
## Render
|
||
# box.label(text='Render option:')
|
||
box.prop(self, 'fps')
|
||
row = box.row(align = True)
|
||
row.label(text='Render Resolution')
|
||
row.prop(self, 'render_res_x', text='Width')
|
||
row.prop(self, 'render_res_y', text='Height')
|
||
|
||
box.prop(self, 'use_relative_remap_on_save')
|
||
box.prop(self, "render_obj_exclusion", icon='FILTER')#
|
||
|
||
subbox = box.box()
|
||
subbox.label(text='Project folders:')
|
||
|
||
## Palette
|
||
subbox.prop(self, 'use_env_palettes', text='Use Palettes Environnement Path')
|
||
subbox.prop(self, 'palette_path')
|
||
subbox.prop(self, 'warn_base_palette')
|
||
|
||
subbox.prop(self, 'mat_link_exclude')
|
||
|
||
## Brushes
|
||
subbox.prop(self, 'use_env_brushes', text='Use Brushes Environnement Path')
|
||
subbox.prop(self, 'brush_path')
|
||
|
||
## render output
|
||
subbox.prop(self, 'output_path')
|
||
|
||
## namespace
|
||
subbox = box.box()
|
||
subbox.label(text='Namespace:')
|
||
subbox.prop(self, 'separator')
|
||
subrow = subbox.row()
|
||
subrow.prop(self, 'show_prefix_buttons', text='Use Prefixes Toggles')
|
||
|
||
if self.show_prefix_buttons:
|
||
rowrow = subrow.row()
|
||
# Reset Names From Projects
|
||
rowrow.alignment = 'RIGHT'
|
||
rowrow.operator('gptb.reset_project_namespaces', text='', icon='BRUSH_DATA')
|
||
"""
|
||
row = subbox.row()
|
||
row.prop(self, 'prefixes')
|
||
row.operator('prefs.reset_gp_toolbox_env', text='', icon='LOOP_BACK').mode = 'PREFIXES'
|
||
row = subbox.row(align=True)
|
||
row.prop(self, 'suffixes')
|
||
row.operator('prefs.reset_gp_toolbox_env', text='', icon='LOOP_BACK').mode = 'SUFFIXES'
|
||
"""
|
||
|
||
## Collection UI list version
|
||
self.draw_namespaces_list(subbox, 'GPTB_UL_namespace_list', 'prefixes', rows=4)
|
||
subbox.separator()
|
||
self.draw_namespaces_list(subbox, 'GPTB_UL_namespace_list_suffix', 'suffixes', rows=2)
|
||
|
||
|
||
### TODO add render settings
|
||
|
||
# layout.separator()## Playblast
|
||
box = layout.box()
|
||
box.label(text='Playblast options:')
|
||
box.prop(self, 'playblast_auto_play')
|
||
box.prop(self, 'playblast_auto_open_folder')
|
||
box.prop(self, 'playblast_path')
|
||
|
||
# box.separator()## Keyframe jumper
|
||
|
||
## Keyframe jump now displayed in Shortcut Tab
|
||
# box = layout.box()
|
||
# box.label(text='Keyframe Jump options:')
|
||
|
||
# box.prop(self, "kfj_use_shortcut", text='Bind shortcuts')
|
||
# if self.kfj_use_shortcut:
|
||
# prompt = '[TYPE SHORTCUT TO USE (can be with modifiers)]'
|
||
# if self.kfj_prev_keycode:
|
||
# mods = '+'.join([m for m, b in [('Ctrl', self.kfj_prev_ctrl), ('Shift', self.kfj_prev_shift), ('Alt', self.kfj_prev_alt)] if b])
|
||
# text = f'{mods}+{self.kfj_prev_keycode}' if mods else self.kfj_prev_keycode
|
||
# text = f'Jump Keyframe Prev: {text} (Click to change)'
|
||
# else:
|
||
# text = prompt
|
||
# ops = box.operator('prefs.shortcut_rebinder', text=text, icon='FILE_REFRESH')
|
||
# ops.s_keycode = 'kfj_prev_keycode'
|
||
# ops.s_ctrl = 'kfj_prev_ctrl'
|
||
# ops.s_shift = 'kfj_prev_shift'
|
||
# ops.s_alt = 'kfj_prev_alt'
|
||
|
||
# if self.kfj_next_keycode:
|
||
# mods = '+'.join([m for m, b in [('Ctrl', self.kfj_next_ctrl), ('Shift', self.kfj_next_shift), ('Alt', self.kfj_next_alt)] if b])
|
||
# text = f'{mods}+{self.kfj_next_keycode}' if mods else self.kfj_next_keycode
|
||
# text = f'Jump Keyframe Next: {text} (Click to change)'
|
||
# else:
|
||
# text = prompt
|
||
# ops = box.operator('prefs.shortcut_rebinder', text=text, icon='FILE_REFRESH')
|
||
# ops.s_keycode = 'kfj_next_keycode'
|
||
# ops.s_ctrl = 'kfj_next_ctrl'
|
||
# ops.s_shift = 'kfj_next_shift'
|
||
# ops.s_alt = 'kfj_next_alt'
|
||
|
||
# else:
|
||
# box.label(text="No Jump hotkey auto set. Following operators needs to be set manually", icon="ERROR")
|
||
# box.label(text="screen.gp_keyframe_jump - preferably in 'screen' category to jump from any editor")
|
||
|
||
box = layout.box()
|
||
box.label(text='Tools options:')
|
||
|
||
subbox= box.box()
|
||
subbox.label(text='Layer Navigation')
|
||
col = subbox.column()
|
||
col.prop(self, 'nav_use_fade')
|
||
if self.nav_use_fade:
|
||
row = col.row()
|
||
row.prop(self, 'nav_fade_val')
|
||
row.prop(self, 'nav_limit')
|
||
row = subbox.row(align=False)
|
||
row.prop(self, 'nav_use_fade_in')
|
||
if self.nav_use_fade_in:
|
||
row.prop(self, 'nav_fade_in_time', text='Fade Back Time')
|
||
# row.prop(self, 'nav_interval') # Do not expose refresh rate for now, not usefull to user...
|
||
|
||
# box.prop(self, 'use_precise_eraser') # precise eraser
|
||
|
||
if self.is_git_repo:
|
||
box = layout.box()
|
||
box.label(text='Addon Update')
|
||
if self.is_git_repo and self.has_git:
|
||
box.operator('gptb.git_pull', text='Check / Get Last Update', icon='PLUGIN')
|
||
else:
|
||
box.label(text='Toolbox can be updated using git')
|
||
row = box.row()
|
||
row.operator('wm.url_open', text='Download and install git here', icon='URL').url = 'https://git-scm.com/download/'
|
||
row.label(text='then restart blender')
|
||
|
||
if self.pref_tabs == 'KEYS':
|
||
# layout.label(text='Shortcuts :')
|
||
box = layout.box()
|
||
box.label(text='Shortcuts added by GP toolbox with context scope:')
|
||
## not available directly :
|
||
## keymaps.addon_keymaps <<- one two three on sculpt, not exposed
|
||
## OP_temp_cutter # not active by defaut
|
||
## TOOL_eraser_brush.addon_keymaps # has a checkbox in
|
||
|
||
prev_key_category = ''
|
||
for kms in [
|
||
OP_keyframe_jump.addon_keymaps,
|
||
OP_copy_paste.addon_keymaps,
|
||
OP_breakdowner.addon_keymaps,
|
||
OP_key_duplicate_send.addon_keymaps,
|
||
OP_layer_picker.addon_keymaps,
|
||
OP_material_picker.addon_keymaps,
|
||
OP_layer_nav.addon_keymaps,
|
||
# OP_layer_manager.addon_keymaps, # Do not display, wm.call_panel call panel ops mixed with natives shortcut (F2)
|
||
]:
|
||
|
||
ct = 0
|
||
for akm, akmi in kms:
|
||
km = bpy.context.window_manager.keyconfigs.user.keymaps.get(akm.name)
|
||
if not km:
|
||
continue
|
||
key_category = km.name
|
||
# kmi = km.keymap_items.get(akmi.idname) # get only first idname when multiple entry
|
||
kmi = None
|
||
|
||
## numbering hack, need a better way to find multi idname user keymaps
|
||
id_ct = 0
|
||
for km_item in km.keymap_items:
|
||
if km_item.idname == akmi.idname:
|
||
if ct > id_ct:
|
||
id_ct +=1
|
||
continue
|
||
|
||
kmi = km_item
|
||
ct += 1
|
||
break
|
||
|
||
if not kmi:
|
||
continue
|
||
|
||
## show keymap category (ideally grouped by category)
|
||
if not prev_key_category:
|
||
if key_category:
|
||
box.label(text=key_category)
|
||
elif key_category and key_category != prev_key_category: # check if has changed singe
|
||
box.label(text=key_category)
|
||
|
||
draw_kmi(km, kmi, box)
|
||
prev_key_category = key_category
|
||
|
||
box.separator()
|
||
|
||
if self.pref_tabs == 'MAN_OPS':
|
||
# layout.separator()## notes
|
||
# layout.label(text='Notes:')
|
||
layout.label(text='Following operators ID have to be set manually in keymap if needed :')
|
||
|
||
## keyframe jump
|
||
box = layout.box()
|
||
box.label(text='GP keyframe jump (consider only GP keyframe, multiple options available at setup)')
|
||
row = box.row()
|
||
row.label(text='screen.gp_keyframe_jump')
|
||
row.operator('wm.copytext', icon='COPYDOWN').text = 'screen.gp_keyframe_jump'
|
||
|
||
# layout.separator()
|
||
## Snap cursor to GP
|
||
box = layout.box()
|
||
box.label(text='Snap cursor to GP canvas (if not autoset)')
|
||
row = box.row()
|
||
row.label(text='Look for default 3d snap operators by searching "view3d.cursor3d"')
|
||
row.operator('wm.copytext', text='Copy "view3d.cursor3d"', icon='COPYDOWN').text = 'view3d.cursor3d'
|
||
row = box.row()
|
||
row.label(text='Replace wanted by "view3d.cusor_snap"')
|
||
row.operator('wm.copytext', text='Copy "view3d.cusor_snap"', icon='COPYDOWN').text = 'view3d.cusor_snap'
|
||
box.label(text='Or just create a new shortcut using cursor_snap')
|
||
|
||
## Clear keyframe
|
||
box = layout.box()
|
||
box.label(text='Clear active frame (delete all strokes without deleting the frame)')
|
||
row = box.row()
|
||
row.label(text='gp.clear_active_frame')
|
||
row.operator('wm.copytext', icon='COPYDOWN').text = 'gp.clear_active_frame'
|
||
|
||
## user prefs
|
||
box = layout.box()
|
||
box.label(text='Note: You can access user pref file and startup file in config folder')
|
||
box.operator("wm.path_open", text='Open config location').filepath = bpy.utils.user_resource('CONFIG')
|
||
|
||
if self.pref_tabs == 'CHECKS':
|
||
layout.label(text='Following checks will be made when clicking "Check File" button:')
|
||
col = layout.column()
|
||
col.use_property_split = True
|
||
# col.prop(self.fixprops, 'check_only')
|
||
col.label(text='The Popup list possible fixes, you can then use the "Apply Fixes"', icon='INFO')
|
||
# col.label(text='Use Ctrl + Click on "Check File" to abply directly', icon='BLANK1')
|
||
col.separator()
|
||
col.prop(self.fixprops, 'lock_main_cam')
|
||
col.prop(self.fixprops, 'set_scene_res', text=f'Reset Scene Resolution (to {self.render_res_x}x{self.render_res_y})')
|
||
col.prop(self.fixprops, 'set_res_percentage')
|
||
col.prop(self.fixprops, 'set_fps', text=f'Reset FPS (to {self.fps})')
|
||
col.prop(self.fixprops, 'set_slider_n_sync')
|
||
col.prop(self.fixprops, 'check_front_axis')
|
||
col.prop(self.fixprops, 'check_placement')
|
||
col.prop(self.fixprops, 'set_gp_use_lights_off')
|
||
col.prop(self.fixprops, 'set_pivot_median_point')
|
||
col.prop(self.fixprops, 'disable_guide')
|
||
col.prop(self.fixprops, 'list_disabled_anim')
|
||
col.prop(self.fixprops, 'list_obj_vis_conflict')
|
||
col.prop(self.fixprops, 'list_gp_mod_vis_conflict')
|
||
col.prop(self.fixprops, 'list_broken_mod_targets')
|
||
col.prop(self.fixprops, 'autokey_add_n_replace')
|
||
col.prop(self.fixprops, 'remove_redundant_strokes')
|
||
#-# col.prop(self.fixprops, 'set_cursor_type')
|
||
|
||
# col = layout.column()
|
||
# col.use_property_split = True
|
||
col.prop(self.fixprops, "select_active_tool", icon='RESTRICT_SELECT_OFF')
|
||
col.prop(self.fixprops, "file_path_type")
|
||
col.prop(self.fixprops, "lock_object_mode")
|
||
# row.label(text='lock the active camera if not a draw cam (and if not "layout" in blendfile name)')
|
||
|
||
# if self.pref_tabs == 'UPDATE':
|
||
# addon_updater_ops.update_settings_ui(self, context)
|
||
|
||
|
||
|
||
### --- ENV_PROP ---
|
||
def set_namespace_env(name_env, prop_group):
|
||
tag_list = os.getenv(name_env)
|
||
current_pfix = []
|
||
project_pfix = []
|
||
|
||
if tag_list and tag_list.strip():
|
||
## Force clear (clear also hide): prop_group.namespaces.clear()
|
||
|
||
## Get current tag list
|
||
tag_list = tag_list.strip(', ').split(',')
|
||
|
||
current_pfix = [n.tag for n in prop_group.namespaces if n.tag]
|
||
# for n in prop_group.namespaces:
|
||
# print(n.tag, n.name)
|
||
|
||
for p in tag_list:
|
||
tag = p.split(':')[0].strip()
|
||
project_pfix.append(tag)
|
||
name = '' if not ':' in p else p.split(':')[1].strip()
|
||
item = None
|
||
if tag not in current_pfix:
|
||
item = prop_group.namespaces.add()
|
||
item.tag = tag
|
||
item.name = name
|
||
# print('Loaded project tag:', tag, name)
|
||
elif name:
|
||
# get the tag and apply name
|
||
item = next((n for n in prop_group.namespaces if n.tag == tag), None)
|
||
if item: # and not item.name.strip()
|
||
item.name = name
|
||
# print('Loaded name:', name)
|
||
|
||
if item:
|
||
item.is_project = True
|
||
|
||
else:
|
||
tag_list = []
|
||
|
||
# "release" suffix that are not in project anymore
|
||
for n in prop_group.namespaces:
|
||
n.is_project = n.tag in project_pfix
|
||
|
||
def set_env_properties():
|
||
|
||
|
||
prefs = get_addon_prefs()
|
||
|
||
fps = os.getenv('FPS')
|
||
prefs.fps = int(fps) if fps else prefs.fps
|
||
|
||
render_width = os.getenv('RENDER_WIDTH')
|
||
prefs.render_res_x = int(render_width) if render_width else prefs.render_res_x
|
||
|
||
render_height = os.getenv('RENDER_HEIGHT')
|
||
prefs.render_res_y = int(render_height) if render_height else prefs.render_res_y
|
||
|
||
palettes = os.getenv('PALETTES')
|
||
if prefs.use_env_palettes:
|
||
prefs.palette_path = palettes if palettes else prefs.palette_path
|
||
|
||
brushes = os.getenv('BRUSHES')
|
||
if prefs.use_env_brushes:
|
||
prefs.brush_path = brushes if brushes else prefs.brush_path
|
||
|
||
# if prefs.use_env_namespace:
|
||
|
||
## Old method with direct string assignment (now a property group)
|
||
# prefix_list = os.getenv('PREFIXES')
|
||
# prefs.prefixes = prefix_list if prefix_list else prefs.prefixes
|
||
# suffix_list = os.getenv('SUFFIXES')
|
||
# prefs.suffixes = suffix_list if suffix_list else prefs.suffixes
|
||
|
||
set_namespace_env('PREFIXES', prefs.prefixes)
|
||
set_namespace_env('SUFFIXES', prefs.suffixes)
|
||
|
||
separator = os.getenv('SEPARATOR')
|
||
prefs.separator = separator if separator else prefs.separator
|
||
|
||
|
||
class GPTB_set_env_settings(bpy.types.Operator):
|
||
"""manually reset prefs from project environnement setttings"""
|
||
bl_idname = "prefs.reset_gp_toolbox_env"
|
||
bl_label = "Reset prefs from project environnement settings (if any)"
|
||
|
||
mode : bpy.props.StringProperty(default='ALL', options={'SKIP_SAVE'}) # 'HIDDEN',
|
||
|
||
def execute(self, context):
|
||
prefs = get_addon_prefs()
|
||
if self.mode == 'ALL':
|
||
set_env_properties()
|
||
elif self.mode == 'PREFIXES':
|
||
prefix_list = os.getenv('PREFIXES')
|
||
if not prefix_list:
|
||
self.report({'ERROR'}, 'No prefix preset to load from project environnement')
|
||
return {'CANCELLED'}
|
||
set_namespace_env('PREFIXES', prefs.prefixes)
|
||
|
||
elif self.mode == 'SUFFIXES':
|
||
suffix_list = os.getenv('SUFFIXES')
|
||
if not suffix_list:
|
||
self.report({'ERROR'}, 'No suffix preset to load from project environnement')
|
||
return {'CANCELLED'}
|
||
set_namespace_env('SUFFIXES', prefs.suffixes)
|
||
|
||
return {'FINISHED'}
|
||
|
||
|
||
### --- REGISTER ---
|
||
|
||
# class GP_PG_ToolsSettings(bpy.types.PropertyGroup) :
|
||
# autotint_offset = bpy.props.IntProperty(name="Tint hue offset", description="offset the tint by this value for better color", default=0, min=-5000, max=5000, soft_min=-999, soft_max=999, step=1)#, subtype='PERCENTAGE'
|
||
|
||
|
||
classes = (
|
||
GPTB_set_env_settings,
|
||
GPTB_prefs,
|
||
)
|
||
|
||
addon_modules = (
|
||
OP_helpers,
|
||
OP_pseudo_tint,
|
||
OP_keyframe_jump,
|
||
OP_file_checker,
|
||
OP_breakdowner,
|
||
OP_temp_cutter,
|
||
GP_colorize,
|
||
OP_playblast_bg,
|
||
OP_playblast,
|
||
OP_palettes,
|
||
OP_palettes_linker,
|
||
OP_brushes,
|
||
OP_cursor_snap_canvas,
|
||
OP_copy_paste,
|
||
# OP_flat_reproject # Disabled,
|
||
OP_realign,
|
||
OP_depth_move,
|
||
OP_key_duplicate_send,
|
||
OP_layer_namespace,
|
||
OP_layer_manager,
|
||
OP_material_picker,
|
||
OP_git_update,
|
||
OP_layer_picker,
|
||
OP_layer_nav,
|
||
OP_follow_curve,
|
||
OP_material_move_to_layer,
|
||
# OP_eraser_brush,
|
||
# TOOL_eraser_brush, # experimental eraser brush
|
||
handler_draw_cam,
|
||
UI_tools,
|
||
keymaps,
|
||
)
|
||
|
||
def register():
|
||
# Register property group first
|
||
properties.register()
|
||
for cls in classes:
|
||
bpy.utils.register_class(cls)
|
||
|
||
for mod in addon_modules:
|
||
mod.register()
|
||
|
||
bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings)
|
||
|
||
set_env_properties()
|
||
|
||
## add handler (if option is on)
|
||
prefs = get_addon_prefs()
|
||
if prefs.use_relative_remap_on_save:
|
||
if not 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
||
bpy.app.handlers.save_pre.append(remap_relative)
|
||
|
||
## Change a variable in prefs if a '.git is detected'
|
||
prefs.is_git_repo = (Path(__file__).parent / '.git').exists()
|
||
prefs.has_git = bool(which('git'))
|
||
|
||
|
||
|
||
def unregister():
|
||
if 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
|
||
bpy.app.handlers.save_pre.remove(remap_relative)
|
||
|
||
for mod in reversed(addon_modules):
|
||
mod.unregister()
|
||
|
||
for cls in reversed(classes):
|
||
bpy.utils.unregister_class(cls)
|
||
|
||
properties.unregister()
|
||
|
||
del bpy.types.Scene.gptoolprops
|
||
|
||
|
||
if __name__ == "__main__":
|
||
register()
|