gp_toolbox/__init__.py

857 lines
31 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.

# 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, 2),
"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='If dry run is checked, no modification is done', icon='INFO')
col.label(text='Use Ctrl + Click on "Check File" button to invert the behavior', 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()