From 9bc6f0cd2dbc80d9d277cb6712d1947208f8cc68 Mon Sep 17 00:00:00 2001 From: Clement Ducarteron Date: Mon, 20 Mar 2023 19:05:22 +0100 Subject: [PATCH] Add template icon --- __init__.py | 4 + appdirs.py | 603 ++++++++++++++++++++++++++++++++++++ constants.py | 8 +- operators/operators.py | 33 +- panels.py | 44 ++- properties.py | 46 ++- resources/trackers/kitsu.py | 10 + sequencer_utils.py | 1 - 8 files changed, 686 insertions(+), 63 deletions(-) create mode 100644 appdirs.py diff --git a/__init__.py b/__init__.py index dd527d1..74d9369 100644 --- a/__init__.py +++ b/__init__.py @@ -21,6 +21,7 @@ from vse_toolbox import panels from vse_toolbox import preferences from vse_toolbox import properties from vse_toolbox import operators +from vse_toolbox.constants import ASSET_PREVIEWS from vse_toolbox.sequencer_utils import get_active_strip @@ -48,6 +49,9 @@ def register(): bpy.app.handlers.frame_change_post.append(get_active_strip) def unregister(): + + bpy.utils.previews.remove(ASSET_PREVIEWS) + if bpy.app.background: return diff --git a/appdirs.py b/appdirs.py new file mode 100644 index 0000000..20103ba --- /dev/null +++ b/appdirs.py @@ -0,0 +1,603 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2005-2010 ActiveState Software Inc. +# Copyright (c) 2013 Eddy Petrișor + +"""Utilities for determining application-specific dirs. + +See for details and usage. +""" +# Dev Notes: +# - MSDN on where to store app data files: +# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120 +# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html +# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + +__version__ = "1.4.4" +__version_info__ = tuple(int(segment) for segment in __version__.split(".")) + + +import sys +import os + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode = str + +if sys.platform.startswith('java'): + import platform + os_name = platform.java_ver()[3][0] + if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc. + system = 'win32' + elif os_name.startswith('Mac'): # "Mac OS X", etc. + system = 'darwin' + else: # "Linux", "SunOS", "FreeBSD", etc. + # Setting this to "linux2" is not ideal, but only Windows or Mac + # are actually checked for and the rest of the module expects + # *sys.platform* style strings. + system = 'linux2' +else: + system = sys.platform + + + +def user_data_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user data directories are: + Mac OS X: ~/Library/Application Support/ + Unix: ~/.local/share/ # or in $XDG_DATA_HOME, if defined + Win XP (not roaming): C:\Documents and Settings\\Application Data\\ + Win XP (roaming): C:\Documents and Settings\\Local Settings\Application Data\\ + Win 7 (not roaming): C:\Users\\AppData\Local\\ + Win 7 (roaming): C:\Users\\AppData\Roaming\\ + + For Unix, we follow the XDG spec and support $XDG_DATA_HOME. + That means, by default "~/.local/share/". + """ + if system == "win32": + if appauthor is None: + appauthor = appname + const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA" + path = os.path.normpath(_get_win_folder(const)) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Application Support/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_data_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of data dirs should be + returned. By default, the first item from XDG_DATA_DIRS is + returned, or '/usr/local/share/', + if XDG_DATA_DIRS is not set + + Typical site data directories are: + Mac OS X: /Library/Application Support/ + Unix: /usr/local/share/ or /usr/share/ + Win XP: C:\Documents and Settings\All Users\Application Data\\ + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + Win 7: C:\ProgramData\\ # Hidden, but writeable on Win 7. + + For Unix, this is using the $XDG_DATA_DIRS[0] default. + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + elif system == 'darwin': + path = os.path.expanduser('/Library/Application Support') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_DATA_DIRS + # only first, if multipath is False + path = os.getenv('XDG_DATA_DIRS', + os.pathsep.join(['/usr/local/share', '/usr/share'])) + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + if appname and version: + path = os.path.join(path, version) + return path + + +def user_config_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific config dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user config directories are: + Mac OS X: ~/Library/Preferences/ + Unix: ~/.config/ # or in $XDG_CONFIG_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME. + That means, by default "~/.config/". + """ + if system == "win32": + path = user_data_dir(appname, appauthor, None, roaming) + elif system == 'darwin': + path = os.path.expanduser('~/Library/Preferences/') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def site_config_dir(appname=None, appauthor=None, version=None, multipath=False): + r"""Return full path to the user-shared data dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "multipath" is an optional parameter only applicable to *nix + which indicates that the entire list of config dirs should be + returned. By default, the first item from XDG_CONFIG_DIRS is + returned, or '/etc/xdg/', if XDG_CONFIG_DIRS is not set + + Typical site config directories are: + Mac OS X: same as site_data_dir + Unix: /etc/xdg/ or $XDG_CONFIG_DIRS[i]/ for each value in + $XDG_CONFIG_DIRS + Win *: same as site_data_dir + Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.) + + For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False + + WARNING: Do not use this on Windows. See the Vista-Fail note above for why. + """ + if system == 'win32': + path = site_data_dir(appname, appauthor) + if appname and version: + path = os.path.join(path, version) + elif system == 'darwin': + path = os.path.expanduser('/Library/Preferences') + if appname: + path = os.path.join(path, appname) + else: + # XDG default for $XDG_CONFIG_DIRS + # only first, if multipath is False + path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg') + pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)] + if appname: + if version: + appname = os.path.join(appname, version) + pathlist = [os.sep.join([x, appname]) for x in pathlist] + + if multipath: + path = os.pathsep.join(pathlist) + else: + path = pathlist[0] + return path + + +def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific cache dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Cache" to the base app data dir for Windows. See + discussion below. + + Typical user cache directories are: + Mac OS X: ~/Library/Caches/ + Unix: ~/.cache/ (XDG default) + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Cache + Vista: C:\Users\\AppData\Local\\\Cache + + On Windows the only suggestion in the MSDN docs is that local settings go in + the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming + app data dir (the default returned by `user_data_dir` above). Apps typically + put cache data somewhere *under* the given dir here. Some examples: + ...\Mozilla\Firefox\Profiles\\Cache + ...\Acme\SuperApp\Cache\1.0 + OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value. + This can be disabled with the `opinion=False` option. + """ + if system == "win32": + if appauthor is None: + appauthor = appname + path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA")) + if appname: + if appauthor is not False: + path = os.path.join(path, appauthor, appname) + else: + path = os.path.join(path, appname) + if opinion: + path = os.path.join(path, "Cache") + elif system == 'darwin': + path = os.path.expanduser('~/Library/Caches') + if appname: + path = os.path.join(path, appname) + else: + path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache')) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_state_dir(appname=None, appauthor=None, version=None, roaming=False): + r"""Return full path to the user-specific state dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "roaming" (boolean, default False) can be set True to use the Windows + roaming appdata directory. That means that for users on a Windows + network setup for roaming profiles, this user data will be + sync'd on login. See + + for a discussion of issues. + + Typical user state directories are: + Mac OS X: same as user_data_dir + Unix: ~/.local/state/ # or in $XDG_STATE_HOME, if defined + Win *: same as user_data_dir + + For Unix, we follow this Debian proposal + to extend the XDG spec and support $XDG_STATE_HOME. + + That means, by default "~/.local/state/". + """ + if system in ["win32", "darwin"]: + path = user_data_dir(appname, appauthor, None, roaming) + else: + path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state")) + if appname: + path = os.path.join(path, appname) + if appname and version: + path = os.path.join(path, version) + return path + + +def user_log_dir(appname=None, appauthor=None, version=None, opinion=True): + r"""Return full path to the user-specific log dir for this application. + + "appname" is the name of application. + If None, just the system directory is returned. + "appauthor" (only used on Windows) is the name of the + appauthor or distributing body for this application. Typically + it is the owning company name. This falls back to appname. You may + pass False to disable it. + "version" is an optional version path element to append to the + path. You might want to use this if you want multiple versions + of your app to be able to run independently. If used, this + would typically be ".". + Only applied when appname is present. + "opinion" (boolean) can be False to disable the appending of + "Logs" to the base app data dir for Windows, and "log" to the + base cache dir for Unix. See discussion below. + + Typical user log directories are: + Mac OS X: ~/Library/Logs/ + Unix: ~/.cache//log # or under $XDG_CACHE_HOME if defined + Win XP: C:\Documents and Settings\\Local Settings\Application Data\\\Logs + Vista: C:\Users\\AppData\Local\\\Logs + + On Windows the only suggestion in the MSDN docs is that local settings + go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in + examples of what some windows apps use for a logs dir.) + + OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA` + value for Windows and appends "log" to the user cache dir for Unix. + This can be disabled with the `opinion=False` option. + """ + if system == "darwin": + path = os.path.join( + os.path.expanduser('~/Library/Logs'), + appname) + elif system == "win32": + path = user_data_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "Logs") + else: + path = user_cache_dir(appname, appauthor, version) + version = False + if opinion: + path = os.path.join(path, "log") + if appname and version: + path = os.path.join(path, version) + return path + + +class AppDirs(object): + """Convenience wrapper for getting application dirs.""" + def __init__(self, appname=None, appauthor=None, version=None, + roaming=False, multipath=False): + self.appname = appname + self.appauthor = appauthor + self.version = version + self.roaming = roaming + self.multipath = multipath + + @property + def user_data_dir(self): + return user_data_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_data_dir(self): + return site_data_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_config_dir(self): + return user_config_dir(self.appname, self.appauthor, + version=self.version, roaming=self.roaming) + + @property + def site_config_dir(self): + return site_config_dir(self.appname, self.appauthor, + version=self.version, multipath=self.multipath) + + @property + def user_cache_dir(self): + return user_cache_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_state_dir(self): + return user_state_dir(self.appname, self.appauthor, + version=self.version) + + @property + def user_log_dir(self): + return user_log_dir(self.appname, self.appauthor, + version=self.version) + + +#---- internal support stuff + +def _get_win_folder_from_registry(csidl_name): + """This is a fallback technique at best. I'm not sure if using the + registry for this guarantees us the correct answer for all CSIDL_* + names. + """ + if PY3: + import winreg as _winreg + else: + import _winreg + + shell_folder_name = { + "CSIDL_APPDATA": "AppData", + "CSIDL_COMMON_APPDATA": "Common AppData", + "CSIDL_LOCAL_APPDATA": "Local AppData", + }[csidl_name] + + key = _winreg.OpenKey( + _winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" + ) + dir, type = _winreg.QueryValueEx(key, shell_folder_name) + return dir + + +def _get_win_folder_with_ctypes(csidl_name): + import ctypes + + csidl_const = { + "CSIDL_APPDATA": 26, + "CSIDL_COMMON_APPDATA": 35, + "CSIDL_LOCAL_APPDATA": 28, + }[csidl_name] + + buf = ctypes.create_unicode_buffer(1024) + ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf) + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in buf: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf2 = ctypes.create_unicode_buffer(1024) + if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024): + buf = buf2 + + return buf.value + +def _get_win_folder_with_jna(csidl_name): + import array + from com.sun import jna + from com.sun.jna.platform import win32 + + buf_size = win32.WinDef.MAX_PATH * 2 + buf = array.zeros('c', buf_size) + shell = win32.Shell32.INSTANCE + shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf) + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + # Downgrade to short path name if have highbit chars. See + # . + has_high_char = False + for c in dir: + if ord(c) > 255: + has_high_char = True + break + if has_high_char: + buf = array.zeros('c', buf_size) + kernel = win32.Kernel32.INSTANCE + if kernel.GetShortPathName(dir, buf, buf_size): + dir = jna.Native.toString(buf.tostring()).rstrip("\0") + + return dir + +def _get_win_folder_from_environ(csidl_name): + env_var_name = { + "CSIDL_APPDATA": "APPDATA", + "CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE", + "CSIDL_LOCAL_APPDATA": "LOCALAPPDATA", + }[csidl_name] + + return os.environ[env_var_name] + +if system == "win32": + try: + from ctypes import windll + except ImportError: + try: + import com.sun.jna + except ImportError: + try: + if PY3: + import winreg as _winreg + else: + import _winreg + except ImportError: + _get_win_folder = _get_win_folder_from_environ + else: + _get_win_folder = _get_win_folder_from_registry + else: + _get_win_folder = _get_win_folder_with_jna + else: + _get_win_folder = _get_win_folder_with_ctypes + + +#---- self test code + +if __name__ == "__main__": + appname = "MyApp" + appauthor = "MyCompany" + + props = ("user_data_dir", + "user_config_dir", + "user_cache_dir", + "user_state_dir", + "user_log_dir", + "site_data_dir", + "site_config_dir") + + print("-- app dirs %s --" % __version__) + + print("-- app dirs (with optional 'version')") + dirs = AppDirs(appname, appauthor, version="1.0") + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'version')") + dirs = AppDirs(appname, appauthor) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (without optional 'appauthor')") + dirs = AppDirs(appname) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) + + print("\n-- app dirs (with disabled 'appauthor')") + dirs = AppDirs(appname, appauthor=False) + for prop in props: + print("%s: %s" % (prop, getattr(dirs, prop))) diff --git a/constants.py b/constants.py index dd00d60..f4d7254 100644 --- a/constants.py +++ b/constants.py @@ -1,3 +1,5 @@ +import appdirs +import bpy import os from pathlib import Path @@ -15,7 +17,7 @@ EDIT_SUFFIXES = ['.xml', '.edl'] MOVIE_SUFFIXES = ['.mov', '.mp4'] SOUND_SUFFIXES = ['.mp3', '.aaf', '.flac'] +CONFIG_DIR = Path(appdirs.user_config_dir(__package__.split('.')[0])) +PREVIEWS_DIR = CONFIG_DIR / 'thumbnails' -# TRACKER_URL = os.environ.get("TRACKER_URL", "") -# TRACKER_LOGIN = os.environ.get("TRACKER_LOGIN", "") -# TRACKER_PASSWORD = os.environ.get("TRACKER_PASSWORD", "") \ No newline at end of file +ASSET_PREVIEWS = bpy.utils.previews.new() \ No newline at end of file diff --git a/operators/operators.py b/operators/operators.py index 20d0ecd..09e66d6 100644 --- a/operators/operators.py +++ b/operators/operators.py @@ -19,11 +19,14 @@ from bpy.types import ( from pathlib import Path from vse_toolbox.constants import ( ASSETS, + ASSET_PREVIEWS, + CONFIG_DIR, EDITS, - MOVIES, - SOUNDS, EDIT_SUFFIXES, + MOVIES, MOVIE_SUFFIXES, + PREVIEWS_DIR, + SOUNDS, SOUND_SUFFIXES, ) from vse_toolbox.sequencer_utils import ( @@ -268,8 +271,8 @@ class VSETB_OT_load_assets(Operator): 'props':'OBJECT_DATAMODE', 'sets':'SMOOTHCURVE', } - # return [(e.name, e.norm_name, '', i) for i, e in enumerate(items)] - return [(e.name, e.name, '', icons[e.asset_type], i) for i, e in enumerate(items)] + return [(e.norm_name, e.norm_name.title(), '', icons[e.asset_type], i) for i, e in enumerate(items)] + # return [(e.norm_name, e.norm_name.title(), '', ASSET_PREVIEWS[e.preview].icon_id, i) for i, e in enumerate(items)] def execute(self, context): settings = get_settings() @@ -283,13 +286,26 @@ class VSETB_OT_load_assets(Operator): assets = tracker.get_assets(project['id']) if not assets: self.report({'ERROR'}, f'No Assets found for {project.name}.') + for asset_data in assets: asset = project.assets.add() - asset.name = asset_data['name'] - # asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.title) + + asset.norm_name = norm_name(asset_data['name'], separator=' ', format=str.lower) + asset.name = asset.norm_name + asset.tracker_name = asset_data['name'] + asset.id = asset_data['id'] asset.asset_type = asset_data['asset_type'] - + + asset.nombss = asset_data['data'].get('nombss', '') + preview_id = asset_data.get('preview_file_id') + if preview_id: + asset.preview = preview_id + preview_path = Path(PREVIEWS_DIR / project.name.lower() / preview_id).with_suffix('.png') + tracker.download_preview(preview_id, preview_path) + + ASSET_PREVIEWS.load(preview_id, preview_path.as_posix(), 'IMAGE') + ASSETS.extend(self.get_items(items=project.assets)) self.report({'INFO'}, f'Assets for {project.name} successfully loaded') @@ -540,7 +556,8 @@ class VSETB_OT_casting_add(Operator): item = active_strip.casting.add() item.name = self.asset_name - item.asset_type = project.assets[self.asset_name].asset_type + for k, v in project.assets[self.asset_name].items(): + setattr(item, k, v) active_strip.casting.update() diff --git a/panels.py b/panels.py index 3ab3e58..82024f6 100644 --- a/panels.py +++ b/panels.py @@ -4,6 +4,7 @@ import bpy from bpy.types import Panel from pathlib import Path from vse_toolbox.bl_utils import get_addon_prefs, get_settings +from vse_toolbox.constants import ASSET_PREVIEWS from vse_toolbox.sequencer_utils import get_active_strip class VSETB_main: @@ -99,19 +100,6 @@ class VSETB_PT_casting(VSETB_main, Panel): bl_label = "Shot Casting" bl_parent_id = "VSETB_PT_main" - # def draw_header(self, context): - # settings = get_settings() - # project = settings.active_project - - # if not project or not project.assets: - # ico = 'ERROR' - # else: - # ico = 'REFRESH' - - # layout = self.layout - # row = layout.row() - # row.operator('vse_toolbox.load_assets', icon=ico, text='') - def draw(self, context): layout = self.layout scn = context.scene @@ -140,19 +128,23 @@ class VSETB_PT_casting(VSETB_main, Panel): row = layout.row() row.label(text=f"Current Shot: {Path(active_strip.name).stem}") row = layout.row() - row.template_list( - "VSETB_UL_casting", "shot_casting", - active_strip, "casting", - active_strip, "casting_index" - ) - - col = row.column(align=True) - - col.operator('vse_toolbox.casting_add', icon='ADD', text="") - col.operator('vse_toolbox.casting_actions', icon='REMOVE', text="").action = 'REMOVE' - col.separator() - col.operator('vse_toolbox.casting_actions', icon='TRIA_UP', text="").action = 'UP' - col.operator('vse_toolbox.casting_actions', icon='TRIA_DOWN', text="").action = 'DOWN' + col = row.column() + col.template_list("VSETB_UL_casting", "shot_casting", active_strip, "casting", active_strip, "casting_index") + + if active_strip.casting: + active_asset = active_strip.casting[active_strip.casting_index] + ico = ASSET_PREVIEWS.get(active_asset.preview) + if ico: + box = col.box() + box.template_icon(icon_value=ico.icon_id, scale=7.5) + + col_tool = row.column(align=True) + col_tool.operator('vse_toolbox.casting_add', icon='ADD', text="") + col_tool.operator('vse_toolbox.casting_actions', icon='REMOVE', text="").action = 'REMOVE' + col_tool.separator() + col_tool.operator('vse_toolbox.casting_actions', icon='TRIA_UP', text="").action = 'UP' + col_tool.operator('vse_toolbox.casting_actions', icon='TRIA_DOWN', text="").action = 'DOWN' + #TODO Add Nombre instance (IntProperty) classes=( diff --git a/properties.py b/properties.py index 7745db7..2aebdef 100644 --- a/properties.py +++ b/properties.py @@ -14,7 +14,7 @@ from bpy.props import ( from bpy.types import PropertyGroup, UIList from pprint import pprint as pp from vse_toolbox.bl_utils import get_addon_prefs, get_settings -from vse_toolbox.constants import TRACKERS +from vse_toolbox.constants import ASSET_PREVIEWS, TRACKERS from vse_toolbox.file_utils import norm_str @@ -55,14 +55,19 @@ class Episode(PropertyGroup): class Asset(PropertyGroup): + name : StringProperty(default='') id : StringProperty(default='') norm_name : StringProperty(default='') asset_type : StringProperty(default='') + tracker_name : StringProperty(default='') + nombss : StringProperty(default='') + preview : StringProperty(default='') + def __iter__(self): + return (getattr(self, p) for p in self.bl_rna.properties.keys() if p not in ('rna_type', 'name')) -class CastingItem(PropertyGroup): - name : StringProperty(default='') - asset_type : StringProperty(default='') +class AssetCasting(Asset): + pass class Project(PropertyGroup): @@ -100,30 +105,26 @@ class Projects(PropertyGroup): class VSETB_UL_casting(UIList): """Demo UIList.""" - order_by_type : BoolProperty(default=True) + order_by_type : BoolProperty(default=False) def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): settings = get_settings() project = settings.active_project - # We could write some code to decide which icon to use here... - icons = { - 'camera':'CAMERA_DATA', - 'chars':'COMMUNITY', - 'props':'OBJECT_DATAMODE', - 'sets':'SMOOTHCURVE', - } - + + ico = ASSET_PREVIEWS.get(item.preview) + params = {'icon_value': ico.icon_id} if ico else {'icon': 'BLANK1'} + # Make sure your code supports all 3 layout types if self.layout_type in {'DEFAULT', 'COMPACT'}: - split = layout.split(factor=0.6) - split.label(text=f"{item.name}") - split.label(text=f"{item.asset_type.title()}", icon=icons[item.asset_type]) + layout.label(**params) + split = layout.split(factor=0.7) + split.label(text=f"{item.norm_name.title()}") + split.label(text=f"{item.asset_type.title()}") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - # layout.label(text="", icon = custom_icon) layout.label(text="") def draw_filter(self, context, layout): @@ -133,14 +134,9 @@ class VSETB_UL_casting(UIList): subrow.prop(self, "filter_name", text="") subrow.prop(self, "use_filter_invert", text="", icon='ARROW_LEFTRIGHT') - # subrow.separator() - # subrow.prop(self, "use_filter_sort_alpha", text="", icon='SORTALPHA') - # subrow.prop(self, "use_filter_sort_reverse", text="", icon='SORT_ASC') - subrow.separator() subrow.prop(self, "order_by_type", text="Order by Type", icon='MESH_DATA') - def filter_items(self, context, data, propname): """Filter and order items in the list.""" @@ -158,7 +154,7 @@ class VSETB_UL_casting(UIList): if self.order_by_type: _sort = [(idx, asset) for idx, asset in enumerate(items)] sort_items = helper_funcs.sort_items_helper - ordered = sort_items(_sort, lambda x: (x[1].name, x[1].asset_type)) + ordered = sort_items(_sort, lambda x: (x[1].asset_type, x[1].name)) return filtered, ordered @@ -203,7 +199,7 @@ classes=( Asset, Episode, Project, - CastingItem, + AssetCasting, VSETB_UL_casting, VSETB_PGT_settings, ) @@ -214,7 +210,7 @@ def register(): bpy.utils.register_class(cls) bpy.types.WindowManager.vsetb_settings = PointerProperty(type=VSETB_PGT_settings) - bpy.types.Sequence.casting = CollectionProperty(type=CastingItem) + bpy.types.Sequence.casting = CollectionProperty(type=AssetCasting) bpy.types.Sequence.casting_index = IntProperty(name='Casting Index', default=0) def unregister(): diff --git a/resources/trackers/kitsu.py b/resources/trackers/kitsu.py index ca14c60..4cf4c27 100644 --- a/resources/trackers/kitsu.py +++ b/resources/trackers/kitsu.py @@ -53,6 +53,16 @@ class Kitsu(Tracker): return assets + def download_preview(self, preview_id, filepath): + if isinstance(filepath, str): + filepath = Path(filepath) + + if filepath.exists(): + return + + filepath.parent.mkdir(parents=True, exist_ok=True) + gazu.files.download_preview_file_thumbnail(preview_id, filepath.as_posix()) + def connect(self, url=None, login=None, password=None): '''Connect to kitsu api using provided url, login and password''' diff --git a/sequencer_utils.py b/sequencer_utils.py index 4cbda45..5eca4cf 100644 --- a/sequencer_utils.py +++ b/sequencer_utils.py @@ -121,7 +121,6 @@ def import_edit(filepath, adapter="cmx_3600", clean_sequencer=False): for child in track.each_child(shallow_search=True): # FIXME Exclude Gaps for now. Gaps are Transitions, Blank Spaces... - # if type(child) != Clip: if not isinstance(child, Clip): continue