# 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 . bl_info = { "name": "GP toolbox", "description": "Set of tools for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", "version": (1, 5, 4), "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 .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_brushes from . import OP_file_checker from . import OP_render 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_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"), ('MAN_OPS', "Operator", "Operator to add Manually"), # ('TUTO', "Tutorial", "How to use the tool"), ('CHECKS', "Check List", "Customise what should happend when hitting 'check fix' button"), ('UPDATE', "Update", "Check and apply updates"), # ('KEYMAP', "Keymap", "customise the default keymap"), ), default='PREF') ## addon pref updater props auto_check_update : BoolProperty( name="Auto-check for Update", description="If enabled, auto-check for updates using an interval", default=False, ) updater_intrval_months : IntProperty( name='Months', description="Number of months between checking for updates", default=0, min=0 ) updater_intrval_days : IntProperty( name='Days', description="Number of days between checking for updates", default=7, min=0, max=31 ) updater_intrval_hours : IntProperty( name='Hours', description="Number of hours between checking for updates", default=0, min=0, max=23 ) updater_intrval_minutes : IntProperty( name='Minutes', description="Number of minutes between checking for updates", default=0, min=0, max=59 ) ## 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 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 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) ## 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') ## Brushes subbox.prop(self, 'use_env_brushes', text='Use Brushes Environnement Path') subbox.prop(self, 'brush_path') ## render output subbox.prop(self, 'output_path') ## render output subbox = box.box() subbox.label(text='Namespace:') subbox.prop(self, 'separator') subbox.prop(self, 'prefixes') ### 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 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 == '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, '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, 'set_cursor_type') 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, 'autokey_add_n_replace') col.prop(self.fixprops, "select_active_tool", icon='RESTRICT_SELECT_OFF') # 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_height = os.getenv('RENDER_HEIGHT') prefs.render_res_x = int(render_height) if render_height else prefs.render_res_x render_width = os.getenv('RENDER_WIDTH') prefs.render_res_y = int(render_width) if render_width 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 prefix_list = os.getenv('PREFIXES') prefs.prefixes = prefix_list if prefix_list else prefs.prefixes separator = os.getenv('SEPARATOR') prefs.separator = separator if separator else prefs.separator ### --- 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_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_brushes.register() OP_cursor_snap_canvas.register() OP_render.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() 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() OP_eraser_brush.unregister() TOOL_eraser_brush.unregister() OP_layer_manager.unregister() OP_key_duplicate_send.unregister() OP_depth_move.unregister() OP_realign.unregister() OP_copy_paste.unregister() OP_render.unregister() OP_cursor_snap_canvas.unregister() OP_brushes.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()