gp_toolbox/__init__.py

699 lines
25 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
bl_info = {
"name": "GP toolbox",
"description": "Tool set for Grease Pencil in animation production",
"author": "Samuel Bernou, Christophe Seux",
"version": (1, 8, 0),
"blender": (2, 91, 0),
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
"warning": "",
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox",
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_toolbox/-/issues",
"category": "3D View",
}
# from . import addon_updater_ops
# from .utils import *
from .utils import get_addon_prefs, draw_kmi
# from .functions import *
## 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_depth_move
from . import OP_key_duplicate_send
from . import OP_layer_manager
from . import OP_layer_picker
from . import OP_material_picker
from . import OP_eraser_brush
from . import TOOL_eraser_brush
from . import handler_draw_cam
from . import keymaps
from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers
from . import UI_tools
from .properties import GP_PG_ToolsSettings, GP_PG_FixSettings
from bpy.props import (FloatProperty,
BoolProperty,
EnumProperty,
StringProperty,
IntProperty)
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)
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__
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'].
## 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')
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,
)
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')
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)
# 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=False,
)
## 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)
## Temp cutter
# temp_cutter_use_shortcut: BoolProperty(
# name = "Use temp cutter Shortcut",
# description = "Auto assign shortcut for temp_cutter",
# default = True)
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')
## 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')
subbox.prop(self, 'show_prefix_buttons', text='Use Prefixes Toggles')
if self.show_prefix_buttons:
# subbox.prop(self, 'use_env_namespace')
row = subbox.row()
row.prop(self, 'prefixes')
row.operator('prefs.reset_gp_toolbox_env', text='', icon='LOOP_BACK').mode = 'PREFIXES'
row = subbox.row()
row.prop(self, 'suffixes')
row.operator('prefs.reset_gp_toolbox_env', text='', icon='LOOP_BACK').mode = 'SUFFIXES'
### 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.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:')
box.prop(self, 'use_precise_eraser')
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 = ''
kmi_see_list = []
for kms in [
OP_keyframe_jump.addon_keymaps,
OP_copy_paste.addon_keymaps,
OP_breakdowner.breakdowner_addon_keymaps,
OP_key_duplicate_send.addon_keymaps,
OP_layer_picker.addon_keymaps,
OP_material_picker.addon_keymaps,
]:
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()
# row = col.row()
col.prop(self.fixprops, 'check_only')
col.label(text='If dry run is checked, no modification is done')
col.label(text='Note: you can use Ctrl+Click on Check file button to invert the behavior')
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_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, 'autokey_add_n_replace')
#-# col.prop(self.fixprops, 'set_cursor_type')
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_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:
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
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'}
prefs.prefixes = prefix_list if prefix_list else 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'}
prefs.suffixes = suffix_list if suffix_list else 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 = (
GP_PG_FixSettings,
GP_PG_ToolsSettings,
GPTB_set_env_settings,
GPTB_prefs,
GPT_OT_auto_tint_gp_layers,
)
# register, unregister = bpy.utils.register_classes_factory(classes)
def register():
# addon_updater_ops.register(bl_info)
# bpy.types.Scene.gpfixprops = bpy.props.PointerProperty(type = GP_PG_FixSettings) # used in prefs
for cls in classes:
bpy.utils.register_class(cls)
OP_helpers.register()
OP_keyframe_jump.register()
OP_file_checker.register()
OP_breakdowner.register()
OP_temp_cutter.register()
GP_colorize.register()## GP_guided_colorize.
OP_playblast_bg.register()
OP_playblast.register()
OP_palettes.register()
OP_palettes_linker.register()
OP_brushes.register()
OP_cursor_snap_canvas.register()
OP_copy_paste.register()
OP_realign.register()
OP_depth_move.register()
OP_key_duplicate_send.register()
OP_layer_manager.register()
OP_eraser_brush.register()
OP_material_picker.register()
OP_layer_picker.register()
TOOL_eraser_brush.register()
handler_draw_cam.register()
UI_tools.register()
keymaps.register()
bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings)
set_env_properties()
## add handler (if option is on)
if get_addon_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)
def unregister():
# remove handler
if 'remap_relative' in [hand.__name__ for hand in bpy.app.handlers.save_pre]:
bpy.app.handlers.save_pre.remove(remap_relative)
keymaps.unregister()
# addon_updater_ops.unregister()
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
UI_tools.unregister()
handler_draw_cam.unregister()
TOOL_eraser_brush.unregister()
OP_layer_picker.unregister()
OP_material_picker.unregister()
OP_eraser_brush.unregister()
OP_layer_manager.unregister()
OP_key_duplicate_send.unregister()
OP_depth_move.unregister()
OP_realign.unregister()
OP_copy_paste.unregister()
OP_cursor_snap_canvas.unregister()
OP_brushes.unregister()
OP_palettes_linker.unregister()
OP_palettes.unregister()
OP_file_checker.unregister()
OP_helpers.unregister()
OP_keyframe_jump.unregister()
OP_breakdowner.unregister()
OP_temp_cutter.unregister()
GP_colorize.unregister()## GP_guided_colorize.
OP_playblast_bg.unregister()
OP_playblast.unregister()
# del bpy.types.Scene.gpfixprops
del bpy.types.Scene.gptoolprops
if __name__ == "__main__":
register()