# 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()