diff --git a/.gitignore b/.gitignore index f1295e32eb..2ea7d93c5c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ Network Trash Folder Temporary Items .apdisk + # CX_Freeze ########### /build @@ -68,7 +69,7 @@ coverage.xml node_modules/ package-lock.json -pype/premiere/ppro/js/debug.log +openpype/premiere/ppro/js/debug.log # IDEA diff --git a/.gitmodules b/.gitmodules index 31e7764741..d286419ee5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,9 +5,9 @@ [submodule "repos/avalon-unreal-integration"] path = repos/avalon-unreal-integration url = git@github.com:pypeclub/avalon-unreal-integration.git -[submodule "pype/modules/ftrack/python2_vendor/ftrack-python-api"] - path = pype/modules/ftrack/python2_vendor/ftrack-python-api +[submodule "openpype/modules/ftrack/python2_vendor/ftrack-python-api"] + path = openpype/modules/ftrack/python2_vendor/ftrack-python-api url = https://bitbucket.org/ftrack/ftrack-python-api.git -[submodule "pype/modules/ftrack/python2_vendor/arrow"] - path = pype/modules/ftrack/python2_vendor/arrow +[submodule "openpype/modules/ftrack/python2_vendor/arrow"] + path = openpype/modules/ftrack/python2_vendor/arrow url = git@github.com:arrow-py/arrow.git \ No newline at end of file diff --git a/README.md b/README.md index 456655bfb9..aae79f2358 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,49 @@ -Pype +OpenPype ==== Introduction ------------ -Multi-platform open-source pipeline built around the [Avalon](https://getavalon.github.io/) platform, -expanding it with extra features and integrations. Pype connects asset database, project management -and time tracking into a single modular system. It has tight integration -with [ftrack](https://www.ftrack.com/en/), but it can also run independently. +Open-source pipeline for visual effects and animation built on top of the [Avalon](https://getavalon.github.io/) framework, expanding it with extra features and integrations. OpenPype connects your DCCs, asset database, project management and time tracking into a single system. It has a tight integration with [ftrack](https://www.ftrack.com/en/), but can also run independently or be integrated into a different project management solution. -To get all the key information about the project, go to [PYPE.club](http://pype.club) +OpenPype provides a robust platform for your studio, without the worry of a vendor lock. You will always have full access to the source-code and your project database will run locally or in the cloud of your choice. + + +To get all the information about the project, go to [OpenPype.io](http://openpype.io) Requirements ------------ -Pype will run on most typical hardware configurations commonly found in studios around the world. -It is installed on artist computer and can take up 3Gb of space depending on number of versions -and other dependencies. -For well functioning [ftrack](https://www.ftrack.com/en/) event server, we recommend a -linux virtual server with [Ubuntu](https://ubuntu.com/) or [CentosOS](https://www.centos.org/). -CPU and RAM allocation need differ based on the studio size, but a 2GB of RAM, with a -dual core CPU and around 4GB of storage should suffice. +We aim to closely follow [**VFX Reference Platform**](https://vfxplatform.com/) -Pype needs running [mongodb](https://www.mongodb.com/) server with good connectivity as it is -heavily used by Pype. Depending on project size and number of artists working connection speed and -latency influence performance experienced by artists. If remote working is required, this mongodb -server must be accessible from Internet or cloud solution can be used. Reasonable backup plan -or high availability options are recommended. +OpenPype is written in Python 3 with specific elements still running in Python2 until all DCCs are fully updated. To see the list of those, that are not quite there yet, go to [VFX Python3 tracker](https://vfxpy.com/) -Building Pype +The main things you will need to run and build OpenPype are: + +- **Terminal** in your OS + - PowerShell 5.0+ (Windows) + - Bash (Linux) +- [**Python 3.7.8**](#python) or higher +- [**MongoDB**](#database) + + +It can be built and ran on all common platforms. We develop and test on the following: + +- **Windows** 10 +- **Linux** + - **Ubuntu** 20.04 LTS + - **Centos** 7 +- **Mac OSX** + - **10.15** Catalina + - **11.1** Big Sur (using Rosetta2) + +For more details on requirements visit [requirements documentation](https://openpype.io/docs/dev_requirements) + +Building OpenPype ------------- -To build Pype you currently need [Python 3.7](https://www.python.org/downloads/) as we are following +To build OpenPype you currently need [Python 3.7](https://www.python.org/downloads/) as we are following [vfx platform](https://vfxplatform.com). Because of some Linux distros comes with newer Python version already, you need to install **3.7** version and make use of it. You can use perhaps [pyenv](https://github.com/pyenv/pyenv) for this on Linux. @@ -44,27 +55,27 @@ development tools like [CMake](https://cmake.org/) and [Visual Studio](https://v #### Clone repository: ```sh -git clone --recurse-submodules git@github.com:pypeclub/pype.git +git clone --recurse-submodules git@github.com:Pypeclub/OpenPype.git ``` -#### To build Pype: +#### To build OpenPype: 1) Run `.\tools\create_env.ps1` to create virtual environment in `.\venv` -2) Run `.\tools\build.ps1` to build pype executables in `.\build\` +2) Run `.\tools\build.ps1` to build OpenPype executables in `.\build\` -To create distributable Pype versions, run `./tools/create_zip.ps1` - that will -create zip file with name `pype-vx.x.x.zip` parsed from current pype repository and +To create distributable OpenPype versions, run `./tools/create_zip.ps1` - that will +create zip file with name `openpype-vx.x.x.zip` parsed from current OpenPype repository and copy it to user data dir, or you can specify `--path /path/to/zip` to force it there. -You can then point **Igniter** - Pype setup tool - to directory containing this zip and +You can then point **Igniter** - OpenPype setup tool - to directory containing this zip and it will install it on current computer. -Pype is build using [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze itself and all dependencies. +OpenPype is build using [CX_Freeze](https://cx-freeze.readthedocs.io/en/latest) to freeze itself and all dependencies. ### macOS You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll need also other tools to build -some Pype dependencies like [CMake](https://cmake.org/) and **XCode Command Line Tools** (or some other build system). +some OpenPype dependencies like [CMake](https://cmake.org/) and **XCode Command Line Tools** (or some other build system). Easy way of installing everything necessary is to use [Homebrew](https://brew.sh): @@ -81,7 +92,7 @@ brew install cmake 3) Install [pyenv](https://github.com/pyenv/pyenv): ```sh brew install pyenv -echo 'eval "$(pypenv init -)"' >> ~/.zshrc +echo 'eval "$(pyenv init -)"' >> ~/.zshrc pyenv init exec "$SHELL" PATH=$(pyenv root)/shims:$PATH @@ -98,14 +109,14 @@ pyenv install 3.7.9 5) Set local Python version ```sh -# switch to Pype source directory +# switch to OpenPype source directory pyenv local 3.7.9 ``` -#### To build Pype: +#### To build OpenPype: 1) Run `.\tools\create_env.sh` to create virtual environment in `.\venv` -2) Run `.\tools\build.sh` to build Pype executables in `.\build\` +2) Run `.\tools\build.sh` to build OpenPype executables in `.\build\` ### Linux @@ -115,7 +126,7 @@ You will need [Python 3.7](https://www.python.org/downloads/) and [git](https:// To build Python related stuff, you need Python header files installed (`python3-dev` on Ubuntu for example). You'll need also other tools to build -some Pype dependencies like [CMake](https://cmake.org/). Python 3 should be part of all modern distributions. You can use your package manager to install **git** and **cmake**. +some OpenPype dependencies like [CMake](https://cmake.org/). Python 3 should be part of all modern distributions. You can use your package manager to install **git** and **cmake**.
@@ -127,7 +138,7 @@ sudo apt install build-essential checkinstall sudo apt install git cmake curl ``` #### Note: -In case you run in error about `xcb` when running Pype, +In case you run in error about `xcb` when running OpenPype, you'll need also additional libraries for Qt5: ```sh @@ -144,7 +155,7 @@ sudo yum install qit cmake ``` #### Note: -In case you run in error about `xcb` when running Pype, +In case you run in error about `xcb` when running OpenPype, you'll need also additional libraries for Qt5: ```sh @@ -153,7 +164,7 @@ sudo yum install qt5-qtbase-devel
-Use pyenv to install Python version for Pype build +Use pyenv to install Python version for OpenPype build You will need **bzip2**, **readline** and **sqlite3** libraries. @@ -177,8 +188,8 @@ exec $SHELL # install Python 3.7.9 pyenv install -v 3.7.9 -# change path to pype 3 -cd /path/to/pype-3 +# change path to OpenPype 3 +cd /path/to/openpype-3 # set local python version pyenv local 3.7.9 @@ -186,42 +197,42 @@ pyenv local 3.7.9 ```
-#### To build Pype: +#### To build OpenPype: 1) Run `.\tools\create_env.sh` to create virtual environment in `.\venv` -2) Run `.\tools\build.sh` to build Pype executables in `.\build\` +2) Run `.\tools\build.sh` to build OpenPype executables in `.\build\` -Running Pype +Running OpenPype ------------ -Pype can by executed either from live sources (this repository) or from +OpenPype can by executed either from live sources (this repository) or from *"frozen code"* - executables that can be build using steps described above. -If Pype is executed from live sources, it will use Pype version included in them. If -it is executed from frozen code it will try to find latest Pype version installed locally +If OpenPype is executed from live sources, it will use OpenPype version included in them. If +it is executed from frozen code it will try to find latest OpenPype version installed locally on current computer and if it is not found, it will ask for its location. On that location -pype can be either in directories or zip files. Pype will try to find latest version and -install it to user data directory (on Windows to `%LOCALAPPDATA%\pypeclub\pype`, on Linux -`~/.local/share/pype` and on macOS in `~/Library/Application Support/pype`). +OpenPype can be either in directories or zip files. OpenPype will try to find latest version and +install it to user data directory (on Windows to `%LOCALAPPDATA%\pypeclub\openpype`, on Linux +`~/.local/share/openpype` and on macOS in `~/Library/Application Support/openpype`). ### From sources -Pype can be run directly from sources by activating virtual environment: +OpenPype can be run directly from sources by activating virtual environment: ```sh poetry run python start.py tray ``` -This will use current Pype version with sources. You can override this with `--use-version=x.x.x` and -then Pype will try to find locally installed specified version (present in user data directory). +This will use current OpenPype version with sources. You can override this with `--use-version=x.x.x` and +then OpenPype will try to find locally installed specified version (present in user data directory). ### From frozen code -You need to build Pype first. This will produce two executables - `pype_gui(.exe)` and `pype_console(.exe)`. +You need to build OpenPype first. This will produce two executables - `openpype_gui(.exe)` and `openpype_console(.exe)`. First one will act as GUI application and will not create console (useful in production environments). The second one will create console and will write output there - useful for headless application and -debugging purposes. If you need pype version installed, just run `./tools/create_zip(.ps1|.sh)` without -arguments and it will create zip file that pype can use. +debugging purposes. If you need OpenPype version installed, just run `./tools/create_zip(.ps1|.sh)` without +arguments and it will create zip file that OpenPype can use. Building documentation diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 58d59afe88..2f305e24e3 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Bootstrap Pype repositories.""" +"""Bootstrap OpenPype repositories.""" import functools import logging as log import os @@ -14,8 +14,8 @@ from zipfile import ZipFile, BadZipFile from appdirs import user_data_dir from speedcopy import copyfile -from .user_settings import PypeSettingsRegistry -from .tools import get_pype_path_from_db +from .user_settings import OpenPypeSettingsRegistry +from .tools import get_openpype_path_from_db LOG_INFO = 0 @@ -24,8 +24,8 @@ LOG_ERROR = 3 @functools.total_ordering -class PypeVersion: - """Class for storing information about Pype version. +class OpenPypeVersion: + """Class for storing information about OpenPype version. Attributes: major (int): [1].2.3-client-variant @@ -33,7 +33,7 @@ class PypeVersion: subversion (int): 1.2.[3]-client-variant client (str): 1.2.3-[client]-variant variant (str): 1.2.3-client-[variant] - path (str): path to Pype + path (str): path to OpenPype """ major = 0 @@ -191,37 +191,37 @@ class PypeVersion: @staticmethod def version_in_str(string: str) -> Tuple: - """Find Pype version in given string. + """Find OpenPype version in given string. Args: string (str): string to search. Returns: - tuple: True/False and PypeVersion if found. + tuple: True/False and OpenPypeVersion if found. """ try: - result = PypeVersion._decompose_version(string) + result = OpenPypeVersion._decompose_version(string) except ValueError: return False, None - return True, PypeVersion(major=result[0], - minor=result[1], - subversion=result[2], - variant=result[3], - client=result[4]) + return True, OpenPypeVersion(major=result[0], + minor=result[1], + subversion=result[2], + variant=result[3], + client=result[4]) class BootstrapRepos: - """Class for bootstrapping local Pype installation. + """Class for bootstrapping local OpenPype installation. Attributes: - data_dir (Path): local Pype installation directory. + data_dir (Path): local OpenPype installation directory. live_repo_dir (Path): path to repos directory if running live, otherwise `None`. - registry (PypeSettingsRegistry): Pype registry object. + registry (OpenPypeSettingsRegistry): OpenPype registry object. zip_filter (list): List of files to exclude from zip - pype_filter (list): list of top level directories not to include in - zip in Pype repository. + openpype_filter (list): list of top level directories not to + include in zip in OpenPype repository. """ @@ -236,13 +236,13 @@ class BootstrapRepos: """ # vendor and app used to construct user data dir self._vendor = "pypeclub" - self._app = "pype" + self._app = "openpype" self._log = log.getLogger(str(__class__)) self.data_dir = Path(user_data_dir(self._app, self._vendor)) - self.registry = PypeSettingsRegistry() + self.registry = OpenPypeSettingsRegistry() self.zip_filter = [".pyc", "__pycache__"] - self.pype_filter = [ - "build", "docs", "tests", "repos", "tools", "venv" + self.openpype_filter = [ + "build", "docs", "tests", "tools", "venv", "coverage" ] self._message = message @@ -262,11 +262,11 @@ class BootstrapRepos: @staticmethod def get_version_path_from_list(version: str, version_list: list) -> Path: - """Get path for specific version in list of Pype versions. + """Get path for specific version in list of OpenPype versions. Args: version (str): Version string to look for (1.2.4-staging) - version_list (list of PypeVersion): list of version to search. + version_list (list of OpenPypeVersion): list of version to search. Returns: Path: Path to given version. @@ -278,31 +278,32 @@ class BootstrapRepos: @staticmethod def get_local_live_version() -> str: - """Get version of local Pype.""" + """Get version of local OpenPype.""" version = {} - path = Path(os.path.dirname(__file__)).parent / "pype" / "version.py" + path = Path(os.path.dirname(__file__)).parent / "openpype" / "version.py" with open(path, "r") as fp: exec(fp.read(), version) return version["__version__"] @staticmethod def get_version(repo_dir: Path) -> Union[str, None]: - """Get version of Pype in given directory. + """Get version of OpenPype in given directory. - Note: in frozen Pype installed in user data dir, this must point - one level deeper as it is `pype-version-v3.0.0/pype/pype/version.py` + Note: in frozen OpenPype installed in user data dir, this must point + one level deeper as it is: + `openpype-version-v3.0.0/openpype/version.py` Args: - repo_dir (Path): Path to Pype repo. + repo_dir (Path): Path to OpenPype repo. Returns: str: version string. - None: if Pype is not found. + None: if OpenPype is not found. """ # try to find version - version_file = Path(repo_dir) / "pype" / "version.py" + version_file = Path(repo_dir) / "openpype" / "version.py" if not version_file.exists(): return None @@ -313,22 +314,22 @@ class BootstrapRepos: return version['__version__'] def create_version_from_live_code( - self, repo_dir: Path = None) -> Union[PypeVersion, None]: - """Copy zip created from Pype repositories to user data dir. + self, repo_dir: Path = None) -> Union[OpenPypeVersion, None]: + """Copy zip created from OpenPype repositories to user data dir. - This detect Pype version either in local "live" Pype repository - or in user provided path. Then it will zip it in temporary directory - and finally it will move it to destination which is user data - directory. Existing files will be replaced. + This detect OpenPype version either in local "live" OpenPype + repository or in user provided path. Then it will zip it in temporary + directory and finally it will move it to destination which is user + data directory. Existing files will be replaced. Args: - repo_dir (Path, optional): Path to Pype repository. + repo_dir (Path, optional): Path to OpenPype repository. Returns: Path: path of installed repository file. """ - # if repo dir is not set, we detect local "live" Pype repository + # if repo dir is not set, we detect local "live" OpenPype repository # version and use it as a source. Otherwise repo_dir is user # entered location. if not repo_dir: @@ -338,7 +339,7 @@ class BootstrapRepos: version = self.get_version(repo_dir) if not version: - self._print("Pype not found.", LOG_ERROR) + self._print("OpenPype not found.", LOG_ERROR) return # create destination directory @@ -348,20 +349,20 @@ class BootstrapRepos: # create zip inside temporary directory. with tempfile.TemporaryDirectory() as temp_dir: temp_zip = \ - Path(temp_dir) / f"pype-v{version}.zip" + Path(temp_dir) / f"openpype-v{version}.zip" self._print(f"creating zip: {temp_zip}") - self._create_pype_zip(temp_zip, repo_dir) + self._create_openpype_zip(temp_zip, repo_dir.parent) if not os.path.exists(temp_zip): self._print("make archive failed.", LOG_ERROR) return None destination = self._move_zip_to_data_dir(temp_zip) - return PypeVersion(version=version, path=destination) + return OpenPypeVersion(version=version, path=destination) def _move_zip_to_data_dir(self, zip_file) -> Union[None, Path]: - """Move zip with Pype version to user data directory. + """Move zip with OpenPype version to user data directory. Args: zip_file (Path): Path to zip file. @@ -404,148 +405,110 @@ class BootstrapRepos: result.append(item) return result - def create_version_from_frozen_code(self) -> Union[None, PypeVersion]: - """Create Pype version from *frozen* code distributed by installer. + def create_version_from_frozen_code(self) -> Union[None, OpenPypeVersion]: + """Create OpenPype version from *frozen* code distributed by installer. - This should be real edge case for those wanting to try out Pype + This should be real edge case for those wanting to try out OpenPype without setting up whole infrastructure but is strongly discouraged in studio setup as this use local version independent of others that can be out of date. Returns: - :class:`PypeVersion` zip file to be installed. + :class:`OpenPypeVersion` zip file to be installed. """ frozen_root = Path(sys.executable).parent - repo_dir = frozen_root / "repos" - repo_list = self._filter_dir( - repo_dir, self.zip_filter) - # from frozen code we need igniter, pype, schema vendor - pype_list = self._filter_dir( - frozen_root / "pype", self.zip_filter) - pype_list += self._filter_dir( + # from frozen code we need igniter, openpype, schema vendor + openpype_list = self._filter_dir( + frozen_root / "openpype", self.zip_filter) + openpype_list += self._filter_dir( frozen_root / "igniter", self.zip_filter) - pype_list += self._filter_dir( + openpype_list += self._filter_dir( + frozen_root / "repos", self.zip_filter) + openpype_list += self._filter_dir( frozen_root / "schema", self.zip_filter) - pype_list += self._filter_dir( + openpype_list += self._filter_dir( frozen_root / "vendor", self.zip_filter) - pype_list.append(frozen_root / "README.md") - pype_list.append(frozen_root / "LICENSE") + openpype_list.append(frozen_root / "LICENSE") version = self.get_version(frozen_root) # create zip inside temporary directory. with tempfile.TemporaryDirectory() as temp_dir: temp_zip = \ - Path(temp_dir) / f"pype-v{version}.zip" + Path(temp_dir) / f"openpype-v{version}.zip" self._print(f"creating zip: {temp_zip}") with ZipFile(temp_zip, "w") as zip_file: progress = 0 - repo_inc = 48.0 / float(len(repo_list)) + openpype_inc = 98.0 / float(len(openpype_list)) file: Path - for file in repo_list: - progress += repo_inc - self._progress_callback(int(progress)) - - # archive name is relative to repos dir - arc_name = file.relative_to(repo_dir) - zip_file.write(file, arc_name) - - pype_inc = 48.0 / float(len(pype_list)) - file: Path - for file in pype_list: - progress += pype_inc + for file in openpype_list: + progress += openpype_inc self._progress_callback(int(progress)) arc_name = file.relative_to(frozen_root.parent) # we need to replace first part of path which starts with - # something like `exe.win/linux....` with `pype` as this - # is expected by Pype in zip archive. - arc_name = Path("pype").joinpath(*arc_name.parts[1:]) + # something like `exe.win/linux....` with `openpype` as + # this is expected by OpenPype in zip archive. + arc_name = Path().joinpath(*arc_name.parts[1:]) zip_file.write(file, arc_name) destination = self._move_zip_to_data_dir(temp_zip) - return PypeVersion(version=version, path=destination) + return OpenPypeVersion(version=version, path=destination) - def _create_pype_zip( - self, - zip_path: Path, include_dir: Path, - include_pype: bool = True) -> None: - """Pack repositories and Pype into zip. + def _create_openpype_zip(self, zip_path: Path, openpype_path: Path) -> None: + """Pack repositories and OpenPype into zip. We are using :mod:`zipfile` instead :meth:`shutil.make_archive` because we need to decide what file and directories to include in zip and what not. They are determined by :attr:`zip_filter` on file level - and :attr:`pype_filter` on top level directory in Pype repository. + and :attr:`openpype_filter` on top level directory in OpenPype + repository. Args: - zip_path (str): path to zip file. - include_dir (Path): repo directories to include. - include_pype (bool): add Pype module itself. + zip_path (Path): Path to zip file. + openpype_path (Path): Path to OpenPype sources. """ - include_dir = include_dir.resolve() + openpype_list = [] + openpype_inc = 0 - pype_list = [] - # get filtered list of files in repositories (repos directory) - repo_list = self._filter_dir(include_dir, self.zip_filter) - # count them - repo_files = len(repo_list) + # get filtered list of file in Pype repository + openpype_list = self._filter_dir(openpype_path, self.zip_filter) + openpype_files = len(openpype_list) - # there must be some files, otherwise `include_dir` path is wrong - assert repo_files != 0, f"No repositories to include in {include_dir}" - pype_inc = 0 - if include_pype: - # get filtered list of file in Pype repository - pype_list = self._filter_dir(include_dir.parent, self.zip_filter) - pype_files = len(pype_list) - repo_inc = 48.0 / float(repo_files) - pype_inc = 48.0 / float(pype_files) - else: - repo_inc = 98.0 / float(repo_files) + openpype_inc = 98.0 / float(openpype_files) with ZipFile(zip_path, "w") as zip_file: progress = 0 + openpype_root = openpype_path.resolve() + # generate list of filtered paths + dir_filter = [openpype_root / f for f in self.openpype_filter] + file: Path - for file in repo_list: - progress += repo_inc + for file in openpype_list: + progress += openpype_inc self._progress_callback(int(progress)) - # archive name is relative to repos dir - arc_name = file.relative_to(include_dir) - zip_file.write(file, arc_name) + # if file resides in filtered path, skip it + is_inside = None + df: Path + for df in dir_filter: + try: + is_inside = file.resolve().relative_to(df) + except ValueError: + pass - # add pype itself - if include_pype: - pype_root = include_dir.parent.resolve() - # generate list of filtered paths - dir_filter = [pype_root / f for f in self.pype_filter] + if is_inside: + continue - file: Path - for file in pype_list: - progress += pype_inc - self._progress_callback(int(progress)) + processed_path = file + self._print(f"- processing {processed_path}") - # if file resides in filtered path, skip it - is_inside = None - df: Path - for df in dir_filter: - try: - is_inside = file.resolve().relative_to(df) - except ValueError: - pass - - if is_inside: - continue - - processed_path = file - self._print(f"- processing {processed_path}") - - zip_file.write(file, - "pype" / file.relative_to(pype_root)) + zip_file.write(file, file.relative_to(openpype_root)) # test if zip is ok zip_file.testzip() @@ -553,10 +516,10 @@ class BootstrapRepos: @staticmethod def add_paths_from_archive(archive: Path) -> None: - """Add first-level directories as paths to :mod:`sys.path`. + """Add first-level directory and 'repos' as paths to :mod:`sys.path`. - This will enable Python to import modules is second-level directories - in zip file. + This will enable Python to import OpenPype and modules in `repos` + submodule directory in zip file. Adding to both `sys.path` and `PYTHONPATH`, skipping duplicates. @@ -574,21 +537,29 @@ class BootstrapRepos: name_list = zip_file.namelist() roots = [] + paths = [] for item in name_list: - root = item.split("/")[0] + if not item.startswith("repos/"): + continue + + root = item.split("/")[1] + if root not in roots: roots.append(root) - sys.path.insert(0, f"{archive}{os.path.sep}{root}") + paths.append( + f"{archive}{os.path.sep}repos{os.path.sep}{root}") + sys.path.insert(0, paths[-1]) + sys.path.insert(0, f"{archive}") pythonpath = os.getenv("PYTHONPATH", "") - paths = pythonpath.split(os.pathsep) - paths += roots + python_paths = pythonpath.split(os.pathsep) + python_paths += paths - os.environ["PYTHONPATH"] = os.pathsep.join(paths) + os.environ["PYTHONPATH"] = os.pathsep.join(python_paths) @staticmethod def add_paths_from_directory(directory: Path) -> None: - """Add first level directories as paths to :mod:`sys.path`. + """Add repos first level directories as paths to :mod:`sys.path`. This works the same as :meth:`add_paths_from_archive` but in specified directory. @@ -599,6 +570,8 @@ class BootstrapRepos: directory (Path): path to directory. """ + sys.path.insert(0, directory.as_posix()) + directory = directory / "repos" if not directory.exists() and not directory.is_dir(): raise ValueError("directory is invalid") @@ -616,57 +589,57 @@ class BootstrapRepos: os.environ["PYTHONPATH"] = os.pathsep.join(paths) - def find_pype( + def find_openpype( self, - pype_path: Union[Path, str] = None, + openpype_path: Union[Path, str] = None, staging: bool = False, - include_zips: bool = False) -> Union[List[PypeVersion], None]: - """Get ordered dict of detected Pype version. + include_zips: bool = False) -> Union[List[OpenPypeVersion], None]: + """Get ordered dict of detected OpenPype version. - Resolution order for Pype is following: + Resolution order for OpenPype is following: - 1) First we test for ``PYPE_PATH`` environment variable - 2) We try to find ``pypePath`` in registry setting + 1) First we test for ``OPENPYPE_PATH`` environment variable + 2) We try to find ``openPypePath`` in registry setting 3) We use user data directory Args: - pype_path (Path or str, optional): Try to find Pype on the given - path or url. + openpype_path (Path or str, optional): Try to find OpenPype on + the given path or url. staging (bool, optional): Filter only staging version, skip them otherwise. include_zips (bool, optional): If set True it will try to find - Pype in zip files in given directory. + OpenPype in zip files in given directory. Returns: - dict of Path: Dictionary of detected Pype version. + dict of Path: Dictionary of detected OpenPype version. Key is version, value is path to zip file. - None: if Pype is not found. + None: if OpenPype is not found. Todo: - implement git/url support as Pype location, so it would be - possible to enter git url, Pype would check it out and if it is + implement git/url support as OpenPype location, so it would be + possible to enter git url, OpenPype would check it out and if it is ok install it as normal version. """ - if pype_path and not isinstance(pype_path, Path): + if openpype_path and not isinstance(openpype_path, Path): raise NotImplementedError( - ("Finding Pype in non-filesystem locations is" + ("Finding OpenPype in non-filesystem locations is" " not implemented yet.")) dir_to_search = self.data_dir - # if we have pype_path specified, search only there. - if pype_path: - dir_to_search = pype_path + # if we have openpype_path specified, search only there. + if openpype_path: + dir_to_search = openpype_path else: - if os.getenv("PYPE_PATH"): - if Path(os.getenv("PYPE_PATH")).exists(): - dir_to_search = Path(os.getenv("PYPE_PATH")) + if os.getenv("OPENPYPE_PATH"): + if Path(os.getenv("OPENPYPE_PATH")).exists(): + dir_to_search = Path(os.getenv("OPENPYPE_PATH")) else: try: registry_dir = Path( - str(self.registry.get_item("pypePath"))) + str(self.registry.get_item("openPypePath"))) if registry_dir.exists(): dir_to_search = registry_dir @@ -674,22 +647,22 @@ class BootstrapRepos: # nothing found in registry, we'll use data dir pass - pype_versions = self.get_pype_versions(dir_to_search, staging) + openpype_versions = self.get_openpype_versions(dir_to_search, staging) # remove zip file version if needed. if not include_zips: - pype_versions = [ - v for v in pype_versions if v.path.suffix != ".zip" + openpype_versions = [ + v for v in openpype_versions if v.path.suffix != ".zip" ] - return pype_versions + return openpype_versions def process_entered_location(self, location: str) -> Union[Path, None]: """Process user entered location string. It decides if location string is mongodb url or path. - If it is mongodb url, it will connect and load ``PYPE_PATH`` from - there and use it as path to Pype. In it is _not_ mongodb url, it + If it is mongodb url, it will connect and load ``OPENPYPE_PATH`` from + there and use it as path to OpenPype. In it is _not_ mongodb url, it is assumed we have a path, this is tested and zip file is produced and installed using :meth:`create_version_from_live_code`. @@ -697,51 +670,52 @@ class BootstrapRepos: location (str): User entered location. Returns: - Path: to Pype zip produced from this location. + Path: to OpenPype zip produced from this location. None: Zipping failed. """ - pype_path = None - # try to get pype path from mongo. + openpype_path = None + # try to get OpenPype path from mongo. if location.startswith("mongodb"): - pype_path = get_pype_path_from_db(location) - if not pype_path: - self._print("cannot find PYPE_PATH in settings.") + pype_path = get_openpype_path_from_db(location) + if not openpype_path: + self._print("cannot find OPENPYPE_PATH in settings.") return None # if not successful, consider location to be fs path. - if not pype_path: - pype_path = Path(location) + if not openpype_path: + openpype_path = Path(location) # test if this path does exist. - if not pype_path.exists(): - self._print(f"{pype_path} doesn't exists.") + if not openpype_path.exists(): + self._print(f"{openpype_path} doesn't exists.") return None # test if entered path isn't user data dir - if self.data_dir == pype_path: + if self.data_dir == openpype_path: self._print("cannot point to user data dir", LOG_ERROR) return None - # find pype zip files in location. There can be - # either "live" Pype repository, or multiple zip files or even - # multiple pype version directories. This process looks into zip + # find openpype zip files in location. There can be + # either "live" OpenPype repository, or multiple zip files or even + # multiple OpenPype version directories. This process looks into zip # files and directories and tries to parse `version.py` file. - versions = self.find_pype(pype_path, include_zips=True) + versions = self.find_openpype(openpype_path, include_zips=True) if versions: - self._print(f"found Pype in [ {pype_path} ]") + self._print(f"found OpenPype in [ {openpype_path} ]") self._print(f"latest version found is [ {versions[-1]} ]") return self.install_version(versions[-1]) - # if we got here, it means that location is "live" Pype repository. - # we'll create zip from it and move it to user data dir. - live_pype = self.create_version_from_live_code(pype_path) - if not live_pype.path.exists(): - self._print(f"installing zip {live_pype} failed.", LOG_ERROR) + # if we got here, it means that location is "live" + # OpenPype repository. We'll create zip from it and move it to user + # data dir. + live_openpype = self.create_version_from_live_code(openpype_path) + if not live_openpype.path.exists(): + self._print(f"installing zip {live_openpype} failed.", LOG_ERROR) return None # install it - return self.install_version(live_pype) + return self.install_version(live_openpype) def _print(self, message: str, @@ -769,11 +743,11 @@ class BootstrapRepos: return self._log.info(message, exc_info=exc_info) - def extract_pype(self, version: PypeVersion) -> Union[Path, None]: - """Extract zipped Pype version to user data directory. + def extract_openpype(self, version: OpenPypeVersion) -> Union[Path, None]: + """Extract zipped OpenPype version to user data directory. Args: - version (PypeVersion): Version of Pype. + version (OpenPypeVersion): Version of OpenPype. Returns: Path: path to extracted version. @@ -819,40 +793,41 @@ class BootstrapRepos: is_inside = path.resolve().relative_to( self.data_dir) except ValueError: - # if relative path cannot be calculated, Pype version is not + # if relative path cannot be calculated, OpenPype version is not # inside user data dir pass return is_inside def install_version(self, - pype_version: PypeVersion, + openpype_version: OpenPypeVersion, force: bool = False) -> Path: - """Install Pype version to user data directory. + """Install OpenPype version to user data directory. Args: - pype_version (PypeVersion): Pype version to install. + oepnpype_version (OpenPypeVersion): OpenPype version to install. force (bool, optional): Force overwrite existing version. Returns: - Path: Path to installed Pype. + Path: Path to installed OpenPype. Raises: - PypeVersionExists: If not forced and this version already exist + OpenPypeVersionExists: If not forced and this version already exist in user data directory. - PypeVersionInvalid: If version to install is invalid. - PypeVersionIOError: If copying or zipping fail. + OpenPypeVersionInvalid: If version to install is invalid. + OpenPypeVersionIOError: If copying or zipping fail. """ - if self.is_inside_user_data(pype_version.path) and not pype_version.path.is_file(): # noqa - raise PypeVersionExists("Pype already inside user data dir") + if self.is_inside_user_data(openpype_version.path) and not openpype_version.path.is_file(): # noqa + raise OpenPypeVersionExists( + "OpenPype already inside user data dir") # determine destination directory name # for zip file strip suffix, in case of dir use whole dir name - if pype_version.path.is_dir(): - dir_name = pype_version.path.name + if openpype_version.path.is_dir(): + dir_name = openpype_version.path.name else: - dir_name = pype_version.path.stem + dir_name = openpype_version.path.stem destination = self.data_dir / dir_name @@ -864,82 +839,82 @@ class BootstrapRepos: self._print( f"cannot remove already existing {destination}", LOG_ERROR, exc_info=True) - raise PypeVersionIOError( + raise OpenPypeVersionIOError( f"cannot remove existing {destination}") from e elif destination.exists() and not force: - raise PypeVersionExists(f"{destination} already exist.") + raise OpenPypeVersionExists(f"{destination} already exist.") else: # create destination parent directories even if they don't exist. destination.mkdir(parents=True) # version is directory - if pype_version.path.is_dir(): + if openpype_version.path.is_dir(): # create zip inside temporary directory. self._print("Creating zip from directory ...") with tempfile.TemporaryDirectory() as temp_dir: temp_zip = \ - Path(temp_dir) / f"pype-v{pype_version}.zip" + Path(temp_dir) / f"openpype-v{openpype_version}.zip" self._print(f"creating zip: {temp_zip}") - self._create_pype_zip(temp_zip, pype_version.path) + self._create_openpype_zip(temp_zip, openpype_version.path) if not os.path.exists(temp_zip): self._print("make archive failed.", LOG_ERROR) - raise PypeVersionIOError("Zip creation failed.") + raise OpenPypeVersionIOError("Zip creation failed.") # set zip as version source - pype_version.path = temp_zip + openpype_version.path = temp_zip - elif pype_version.path.is_file(): + elif openpype_version.path.is_file(): # check if file is zip (by extension) - if pype_version.path.suffix.lower() != ".zip": - raise PypeVersionInvalid("Invalid file format") + if openpype_version.path.suffix.lower() != ".zip": + raise OpenPypeVersionInvalid("Invalid file format") - if not self.is_inside_user_data(pype_version.path): + if not self.is_inside_user_data(openpype_version.path): try: # copy file to destination self._print("Copying zip to destination ...") - _destination_zip = destination.parent / pype_version.path.name + _destination_zip = destination.parent / openpype_version.path.name # noqa: E501 copyfile( - pype_version.path.as_posix(), + openpype_version.path.as_posix(), _destination_zip.as_posix()) except OSError as e: self._print( "cannot copy version to user data directory", LOG_ERROR, exc_info=True) - raise PypeVersionIOError(( - f"can't copy version {pype_version.path.as_posix()} " + raise OpenPypeVersionIOError(( + f"can't copy version {openpype_version.path.as_posix()} " f"to destination {destination.parent.as_posix()}")) from e # extract zip there self._print("extracting zip to destination ...") - with ZipFile(pype_version.path, "r") as zip_ref: + with ZipFile(openpype_version.path, "r") as zip_ref: zip_ref.extractall(destination) return destination - def _is_pype_in_dir(self, - dir_item: Path, - detected_version: PypeVersion) -> bool: - """Test if path item is Pype version matching detected version. + def _is_openpype_in_dir(self, + dir_item: Path, + detected_version: OpenPypeVersion) -> bool: + """Test if path item is OpenPype version matching detected version. If item is directory that might (based on it's name) - contain Pype version, check if it really does contain - Pype and that their versions matches. + contain OpenPype version, check if it really does contain + OpenPype and that their versions matches. Args: dir_item (Path): Directory to test. - detected_version (PypeVersion): Pype version detected from name. + detected_version (OpenPypeVersion): OpenPype version detected + from name. Returns: - True if it is valid Pype version, False otherwise. + True if it is valid OpenPype version, False otherwise. """ try: - # add one 'pype' level as inside dir there should + # add one 'openpype' level as inside dir there should # be many other repositories. - version_str = BootstrapRepos.get_version( - dir_item / "pype") - version_check = PypeVersion(version=version_str) + version_str = BootstrapRepos.get_version(dir_item) + version_check = OpenPypeVersion(version=version_str) except ValueError: self._print( f"cannot determine version from {dir_item}", True) @@ -955,21 +930,21 @@ class BootstrapRepos: return False return True - def _is_pype_in_zip(self, - zip_item: Path, - detected_version: PypeVersion) -> bool: - """Test if zip path is Pype version matching detected version. + def _is_openpype_in_zip(self, + zip_item: Path, + detected_version: OpenPypeVersion) -> bool: + """Test if zip path is OpenPype version matching detected version. - Open zip file, look inside and parse version from Pype + Open zip file, look inside and parse version from OpenPype inside it. If there is none, or it is different from version specified in file name, skip it. Args: zip_item (Path): Zip file to test. - detected_version (PypeVersion): Pype version detected from name. + detected_version (OpenPypeVersion): Pype version detected from name. Returns: - True if it is valid Pype version, False otherwise. + True if it is valid OpenPype version, False otherwise. """ # skip non-zip files @@ -979,10 +954,10 @@ class BootstrapRepos: try: with ZipFile(zip_item, "r") as zip_file: with zip_file.open( - "pype/pype/version.py") as version_file: + "openpype/version.py") as version_file: zip_version = {} exec(version_file.read(), zip_version) - version_check = PypeVersion( + version_check = OpenPypeVersion( version=zip_version["__version__"]) version_main = version_check.get_main_version() # noqa: E501 @@ -999,70 +974,72 @@ class BootstrapRepos: self._print(f"{zip_item} is not a zip file", True) return False except KeyError: - self._print("Zip does not contain Pype", True) + self._print("Zip does not contain OpenPype", True) return False return True - def get_pype_versions(self, pype_dir: Path, staging: bool = False) -> list: - """Get all detected Pype versions in directory. + def get_openpype_versions(self, + openpype_dir: Path, + staging: bool = False) -> list: + """Get all detected OpenPype versions in directory. Args: - pype_dir (Path): Directory to scan. + openpype_dir (Path): Directory to scan. staging (bool, optional): Find staging versions if True. Returns: - list of PypeVersion + list of OpenPypeVersion Throws: ValueError: if invalid path is specified. """ - if not pype_dir.exists() and not pype_dir.is_dir(): + if not openpype_dir.exists() and not openpype_dir.is_dir(): raise ValueError("specified directory is invalid") - _pype_versions = [] + _openpype_versions = [] # iterate over directory in first level and find all that might - # contain Pype. - for item in pype_dir.iterdir(): + # contain OpenPype. + for item in openpype_dir.iterdir(): # if file, strip extension, in case of dir not. name = item.name if item.is_dir() else item.stem - result = PypeVersion.version_in_str(name) + result = OpenPypeVersion.version_in_str(name) if result[0]: - detected_version: PypeVersion + detected_version: OpenPypeVersion detected_version = result[1] - if item.is_dir() and not self._is_pype_in_dir( + if item.is_dir() and not self._is_openpype_in_dir( item, detected_version ): continue - if item.is_file() and not self._is_pype_in_zip( + if item.is_file() and not self._is_openpype_in_zip( item, detected_version ): continue detected_version.path = item if staging and detected_version.is_staging(): - _pype_versions.append(detected_version) + _openpype_versions.append(detected_version) if not staging and not detected_version.is_staging(): - _pype_versions.append(detected_version) + _openpype_versions.append(detected_version) - return sorted(_pype_versions) + return sorted(_openpype_versions) -class PypeVersionExists(Exception): - """Exception for handling existing Pype version.""" +class OpenPypeVersionExists(Exception): + """Exception for handling existing OpenPype version.""" pass -class PypeVersionInvalid(Exception): - """Exception for handling invalid Pype version.""" +class OpenPypeVersionInvalid(Exception): + """Exception for handling invalid OpenPype version.""" pass -class PypeVersionIOError(Exception): - """Exception for handling IO errors in Pype version.""" +class OpenPypeVersionIOError(Exception): + """Exception for handling IO errors in OpenPype version.""" pass diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index 0eb518c2e3..2cc0ed8448 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -11,9 +11,9 @@ from .install_thread import InstallThread, InstallResult from .tools import ( validate_path_string, validate_mongo_connection, - get_pype_path_from_db + get_openpype_path_from_db ) -from .user_settings import PypeSettingsRegistry +from .user_settings import OpenPypeSettingsRegistry from .version import __version__ @@ -42,17 +42,21 @@ class InstallDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(InstallDialog, self).__init__(parent) - self.registry = PypeSettingsRegistry() + self.registry = OpenPypeSettingsRegistry() self.mongo_url = "" try: - self.mongo_url = os.getenv("PYPE_MONGO", "") or self.registry.get_secure_item("pypeMongo") # noqa: E501 + self.mongo_url = ( + os.getenv("OPENPYPE_MONGO", "") + or self.registry.get_secure_item("openPypeMongo") + ) except ValueError: pass - self.setWindowTitle(f"Pype Igniter {__version__} - Pype installation") + self.setWindowTitle( + f"OpenPype Igniter {__version__} - OpenPype installation") self._icon_path = os.path.join( - os.path.dirname(__file__), 'pype_icon.png') + os.path.dirname(__file__), 'openpype_icon.png') icon = QtGui.QIcon(self._icon_path) self.setWindowIcon(icon) self.setWindowFlags( @@ -81,7 +85,7 @@ class InstallDialog(QtWidgets.QDialog): os.path.join( os.path.dirname(__file__), 'RobotoMono-Regular.ttf') ) - self._pype_run_ready = False + self._openpype_run_ready = False self._init_ui() @@ -97,35 +101,35 @@ class InstallDialog(QtWidgets.QDialog): # Main info # -------------------------------------------------------------------- self.main_label = QtWidgets.QLabel( - """Welcome to Pype + """Welcome to OpenPype

- We've detected Pype is not configured yet. But don't worry, + We've detected OpenPype is not configured yet. But don't worry, this is as easy as setting one or two things.

""") self.main_label.setWordWrap(True) self.main_label.setStyleSheet("color: rgb(200, 200, 200);") - # Pype path info + # OpenPype path info # -------------------------------------------------------------------- - self.pype_path_label = QtWidgets.QLabel( - """This is Path to studio location where Pype versions + self.openpype_path_label = QtWidgets.QLabel( + """This is Path to studio location where OpenPype versions are stored. It will be pre-filled if your MongoDB connection is already set and your studio defined this location.

- Leave it empty if you want to install Pype version that comes with - this installation. + Leave it empty if you want to install OpenPype version that + comes with this installation.

- If you want to just try Pype without installing, hit the middle - button that states "run without installation". + If you want to just try OpenPype without installing, hit the + middle button that states "run without installation".

""" ) - self.pype_path_label.setWordWrap(True) - self.pype_path_label.setStyleSheet("color: rgb(150, 150, 150);") + self.openpype_path_label.setWordWrap(True) + self.openpype_path_label.setStyleSheet("color: rgb(150, 150, 150);") # Path/Url box | Select button # -------------------------------------------------------------------- @@ -135,7 +139,7 @@ class InstallDialog(QtWidgets.QDialog): input_layout.setContentsMargins(0, 10, 0, 10) self.user_input = FocusHandlingLineEdit() - self.user_input.setPlaceholderText("Path to Pype versions") + self.user_input.setPlaceholderText("Path to OpenPype versions") self.user_input.textChanged.connect(self._path_changed) self.user_input.setStyleSheet( ("color: rgb(233, 233, 233);" @@ -148,7 +152,7 @@ class InstallDialog(QtWidgets.QDialog): self._btn_select = QtWidgets.QPushButton("Select") self._btn_select.setToolTip( - "Select Pype repository" + "Select OpenPype repository" ) self._btn_select.setStyleSheet( ("color: rgb(64, 64, 64);" @@ -282,13 +286,13 @@ class InstallDialog(QtWidgets.QDialog): # -------------------------------------------------------------------- bottom_widget = QtWidgets.QWidget() bottom_layout = QtWidgets.QHBoxLayout() - pype_logo_label = QtWidgets.QLabel("pype logo") - pype_logo = QtGui.QPixmap(self._icon_path) - # pype_logo.scaled( - # pype_logo_label.width(), - # pype_logo_label.height(), QtCore.Qt.KeepAspectRatio) - pype_logo_label.setPixmap(pype_logo) - pype_logo_label.setContentsMargins(10, 0, 0, 10) + openpype_logo_label = QtWidgets.QLabel("openpype logo") + openpype_logo = QtGui.QPixmap(self._icon_path) + # openpype_logo.scaled( + # openpype_logo_label.width(), + # openpype_logo_label.height(), QtCore.Qt.KeepAspectRatio) + openpype_logo_label.setPixmap(openpype_logo) + openpype_logo_label.setContentsMargins(10, 0, 0, 10) # install button - - - - - - - - - - - - - - - - - - - - - - - - - - - self.install_button = QtWidgets.QPushButton("Install") @@ -298,7 +302,7 @@ class InstallDialog(QtWidgets.QDialog): "padding: 0.5em;") ) self.install_button.setMinimumSize(64, 24) - self.install_button.setToolTip("Install Pype") + self.install_button.setToolTip("Install OpenPype") self.install_button.clicked.connect(self._on_ok_clicked) # run from current button - - - - - - - - - - - - - - - - - - - - - - @@ -325,7 +329,7 @@ class InstallDialog(QtWidgets.QDialog): bottom_layout.setContentsMargins(0, 10, 10, 0) bottom_layout.setAlignment(QtCore.Qt.AlignVCenter) - bottom_layout.addWidget(pype_logo_label, 0, QtCore.Qt.AlignVCenter) + bottom_layout.addWidget(openpype_logo_label, 0, QtCore.Qt.AlignVCenter) bottom_layout.addStretch(1) bottom_layout.addWidget(self.install_button, 0, QtCore.Qt.AlignVCenter) bottom_layout.addWidget(self.run_button, 0, QtCore.Qt.AlignVCenter) @@ -405,7 +409,7 @@ class InstallDialog(QtWidgets.QDialog): ) # add all to main main.addWidget(self.main_label, 0) - main.addWidget(self.pype_path_label, 0) + main.addWidget(self.openpype_path_label, 0) main.addLayout(input_layout, 0) main.addWidget(self.mongo_label, 0) main.addWidget(self._mongo, 0) @@ -418,9 +422,9 @@ class InstallDialog(QtWidgets.QDialog): self.setLayout(main) - # if mongo url is ok, try to get pype path from there + # if mongo url is ok, try to get openpype path from there if self._mongo.validate_url() and len(self.path) == 0: - self.path = get_pype_path_from_db(self.mongo_url) + self.path = get_openpype_path_from_db(self.mongo_url) self.user_input.setText(self.path) def _on_select_clicked(self): @@ -473,7 +477,7 @@ class InstallDialog(QtWidgets.QDialog): else: self._mongo.set_valid() - if self._pype_run_ready: + if self._openpype_run_ready: self.done(3) return @@ -498,8 +502,8 @@ class InstallDialog(QtWidgets.QDialog): """Change button behaviour based on installation outcome.""" status = result.status if status >= 0: - self.install_button.setText("Run installed Pype") - self._pype_run_ready = True + self.install_button.setText("Run installed OpenPype") + self._openpype_run_ready = True def _update_progress(self, progress: int): self._progress_bar.setValue(progress) @@ -636,7 +640,7 @@ class PathValidator(MongoValidator): """Validate path to be accepted by Igniter. Args: - path (str): path to Pype. + path (str): path to OpenPype. pos (int): current position. Returns: @@ -646,7 +650,7 @@ class PathValidator(MongoValidator): """ # allow empty path as that will use current version coming with - # Pype Igniter + # OpenPype Igniter if len(path) == 0: return self._return_state( QValidator.State.Acceptable, "Use version with Igniter", path) diff --git a/igniter/install_thread.py b/igniter/install_thread.py index a184a19d36..bf5d541056 100644 --- a/igniter/install_thread.py +++ b/igniter/install_thread.py @@ -8,10 +8,10 @@ from Qt.QtCore import QThread, Signal, QObject # noqa from .bootstrap_repos import ( BootstrapRepos, - PypeVersionInvalid, - PypeVersionIOError, - PypeVersionExists, - PypeVersion + OpenPypeVersionInvalid, + OpenPypeVersionIOError, + OpenPypeVersionExists, + OpenPypeVersion ) from .tools import validate_mongo_connection @@ -26,9 +26,9 @@ class InstallResult(QObject): class InstallThread(QThread): """Install Worker thread. - This class takes care of finding Pype version on user entered path + This class takes care of finding OpenPype version on user entered path (or loading this path from database). If nothing is entered by user, - Pype will create its zip files from repositories that comes with it. + OpenPype will create its zip files from repositories that comes with it. If path contains plain repositories, they are zipped and installed to user data dir. @@ -49,65 +49,67 @@ class InstallThread(QThread): def run(self): """Thread entry point. - Using :class:`BootstrapRepos` to either install Pype as zip files + Using :class:`BootstrapRepos` to either install OpenPype as zip files or copy them from location specified by user or retrieved from database. """ - self.message.emit("Installing Pype ...", False) + self.message.emit("Installing OpenPype ...", False) - # find local version of Pype + # find local version of OpenPype bs = BootstrapRepos( progress_callback=self.set_progress, message=self.message) local_version = bs.get_local_live_version() - # if user did entered nothing, we install Pype from local version. + # if user did entered nothing, we install OpenPype from local version. # zip content of `repos`, copy it to user data dir and append # version to it. if not self._path: # user did not entered url if not self._mongo: # it not set in environment - if not os.getenv("PYPE_MONGO"): + if not os.getenv("OPENPYPE_MONGO"): # try to get it from settings registry try: - self._mongo = bs.registry.get_secure_item("pypeMongo") + self._mongo = bs.registry.get_secure_item( + "openPypeMongo") except ValueError: self.message.emit( "!!! We need MongoDB URL to proceed.", True) self.finished.emit(InstallResult(-1)) return else: - self._mongo = os.getenv("PYPE_MONGO") + self._mongo = os.getenv("OPENPYPE_MONGO") else: self.message.emit("Saving mongo connection string ...", False) - bs.registry.set_secure_item("pypeMongo", self._mongo) + bs.registry.set_secure_item("openPypeMongo", self._mongo) - os.environ["PYPE_MONGO"] = self._mongo + os.environ["OPENPYPE_MONGO"] = self._mongo self.message.emit( - f"Detecting installed Pype versions in {bs.data_dir}", False) - detected = bs.find_pype(include_zips=True) + f"Detecting installed OpenPype versions in {bs.data_dir}", + False) + detected = bs.find_openpype(include_zips=True) if detected: - if PypeVersion( + if OpenPypeVersion( version=local_version, path=Path()) < detected[-1]: self.message.emit(( f"Latest installed version {detected[-1]} is newer " f"then currently running {local_version}" ), False) - self.message.emit("Skipping Pype install ...", False) + self.message.emit("Skipping OpenPype install ...", False) if detected[-1].path.suffix.lower() == ".zip": - bs.extract_pype(detected[-1]) + bs.extract_openpype(detected[-1]) self.finished.emit(InstallResult(0)) return - if PypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa + if OpenPypeVersion(version=local_version).get_main_version() == detected[-1].get_main_version(): # noqa self.message.emit(( f"Latest installed version is the same as " f"currently running {local_version}" ), False) - self.message.emit("Skipping Pype install ...", False) + self.message.emit("Skipping OpenPype install ...", False) self.finished.emit(InstallResult(0)) return @@ -118,17 +120,17 @@ class InstallThread(QThread): else: if getattr(sys, 'frozen', False): self.message.emit("None detected.", True) - self.message.emit(("We will use Pype coming with " + self.message.emit(("We will use OpenPype coming with " "installer."), False) - pype_version = bs.create_version_from_frozen_code() - if not pype_version: + openpype_version = bs.create_version_from_frozen_code() + if not openpype_version: self.message.emit( - f"!!! Install failed - {pype_version}", True) + f"!!! Install failed - {openpype_version}", True) self.finished.emit(InstallResult(-1)) return - self.message.emit(f"Using: {pype_version}", False) - bs.install_version(pype_version) - self.message.emit(f"Installed as {pype_version}", False) + self.message.emit(f"Using: {openpype_version}", False) + bs.install_version(openpype_version) + self.message.emit(f"Installed as {openpype_version}", False) self.progress.emit(100) self.finished.emit(InstallResult(1)) return @@ -136,39 +138,39 @@ class InstallThread(QThread): self.message.emit("None detected.", False) self.message.emit( - f"We will use local Pype version {local_version}", False) + f"We will use local OpenPype version {local_version}", False) - local_pype = bs.create_version_from_live_code() - if not local_pype: + local_openpype = bs.create_version_from_live_code() + if not local_openpype: self.message.emit( - f"!!! Install failed - {local_pype}", True) + f"!!! Install failed - {local_openpype}", True) self.finished.emit(InstallResult(-1)) return try: - bs.install_version(local_pype) - except (PypeVersionExists, - PypeVersionInvalid, - PypeVersionIOError) as e: + bs.install_version(local_openpype) + except (OpenPypeVersionExists, + OpenPypeVersionInvalid, + OpenPypeVersionIOError) as e: self.message.emit(f"Installed failed: ", True) self.message.emit(str(e), True) self.finished.emit(InstallResult(-1)) return - self.message.emit(f"Installed as {local_pype}", False) + self.message.emit(f"Installed as {local_openpype}", False) self.progress.emit(100) return else: # if we have mongo connection string, validate it, set it to - # user settings and get PYPE_PATH from there. + # user settings and get OPENPYPE_PATH from there. if self._mongo: if not validate_mongo_connection(self._mongo): self.message.emit( f"!!! invalid mongo url {self._mongo}", True) self.finished.emit(InstallResult(-1)) return - bs.registry.set_secure_item("pypeMongo", self._mongo) - os.environ["PYPE_MONGO"] = self._mongo + bs.registry.set_secure_item("openPypeMongo", self._mongo) + os.environ["OPENPYPE_MONGO"] = self._mongo self.message.emit(f"processing {self._path}", True) repo_file = bs.process_entered_location(self._path) diff --git a/igniter/openpype.ico b/igniter/openpype.ico new file mode 100644 index 0000000000..f0c15accc4 Binary files /dev/null and b/igniter/openpype.ico differ diff --git a/igniter/openpype_icon.png b/igniter/openpype_icon.png new file mode 100644 index 0000000000..6eae8abca3 Binary files /dev/null and b/igniter/openpype_icon.png differ diff --git a/igniter/pype.ico b/igniter/pype.ico deleted file mode 100644 index 746fc36ba2..0000000000 Binary files a/igniter/pype.ico and /dev/null differ diff --git a/igniter/pype_icon.png b/igniter/pype_icon.png deleted file mode 100644 index c17d6ee4c1..0000000000 Binary files a/igniter/pype_icon.png and /dev/null differ diff --git a/igniter/terminal_splash.py b/igniter/terminal_splash.py index 1a7645571e..1d85fd3927 100644 --- a/igniter/terminal_splash.py +++ b/igniter/terminal_splash.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""Pype terminal animation.""" +"""OpenPype terminal animation.""" import blessed from pathlib import Path from time import sleep @@ -15,7 +15,7 @@ except AttributeError: def play_animation(): - """Play ASCII art Pype animation.""" + """Play ASCII art OpenPype animation.""" if NO_TERMINAL: return print(term.home + term.clear) diff --git a/igniter/tools.py b/igniter/tools.py index 4ed4ae67f4..ff2db6bc7e 100644 --- a/igniter/tools.py +++ b/igniter/tools.py @@ -2,10 +2,12 @@ """Tools used in **Igniter** GUI. Functions ``compose_url()`` and ``decompose_url()`` are the same as in -``pype.lib`` and they are here to avoid importing pype module before its +``openpype.lib`` and they are here to avoid importing OpenPype module before its version is decided. """ +import sys +import os from typing import Dict, Union from urllib.parse import urlparse, parse_qs from pathlib import Path @@ -130,6 +132,7 @@ def validate_mongo_connection(cnx: str) -> (bool, str): try: client = MongoClient(**mongo_args) client.server_info() + client.close() except ServerSelectionTimeoutError as e: return False, f"Cannot connect to server {cnx} - {e}" except ValueError: @@ -161,7 +164,7 @@ def validate_mongo_string(mongo: str) -> (bool, str): def validate_path_string(path: str) -> (bool, str): - """Validate string if it is path to Pype repository. + """Validate string if it is path to OpenPype repository. Args: path (str): Path to validate. @@ -185,73 +188,68 @@ def validate_path_string(path: str) -> (bool, str): return True, "valid path" -def load_environments(sections: list = None) -> dict: - """Load environments from Pype. +def get_openpype_global_settings(url: str) -> dict: + """Load global settings from Mongo database. - This will load environments from database, process them with - :mod:`acre` and return them as flattened dictionary. - - Args: - sections (list, optional): load specific types - - Returns; - dict of str: loaded and processed environments. - - """ - import acre - - from pype import settings - - all_env = settings.get_environments() - merged_env = {} - - sections = sections or all_env.keys() - - for section in sections: - try: - parsed_env = acre.parse(all_env[section]) - except AttributeError: - continue - merged_env = acre.append(merged_env, parsed_env) - - return acre.compute(merged_env, cleanup=True) - - -def get_pype_path_from_db(url: str) -> Union[str, None]: - """Get Pype path from database. - - We are loading data from database `pype` and collection `settings`. + We are loading data from database `openpype` and collection `settings`. There we expect document type `global_settings`. Args: - url (str): mongodb url. + url (str): MongoDB url. Returns: - path to Pype or None if not found - + dict: With settings data. Empty dictionary is returned if not found. """ try: components = decompose_url(url) except RuntimeError: - return None - mongo_args = { + return {} + mongo_kwargs = { "host": compose_url(**components), "serverSelectionTimeoutMS": 2000 } port = components.get("port") if port is not None: - mongo_args["port"] = int(port) + mongo_kwargs["port"] = int(port) try: - client = MongoClient(**mongo_args) + # Create mongo connection + client = MongoClient(**mongo_kwargs) + # Access settings collection + col = client["openpype"]["settings"] + # Query global settings + global_settings = col.find_one({"type": "global_settings"}) or {} + # Close Mongo connection + client.close() + except Exception: - return None + # TODO log traceback or message + return {} - db = client.pype - col = db.settings + return global_settings.get("data") or {} - global_settings = col.find_one({"type": "global_settings"}, {"data": 1}) - if not global_settings: - return None - global_settings.get("data", {}) - return global_settings.get("pype_path", {}).get(platform.system().lower()) + +def get_openpype_path_from_db(url: str) -> Union[str, None]: + """Get OpenPype path from global settings. + + Args: + url (str): mongodb url. + + Returns: + path to OpenPype or None if not found + """ + global_settings = get_openpype_global_settings(url) + paths = ( + global_settings + .get("openpype_path", {}) + .get(platform.system().lower()) + ) or [] + # For cases when `openpype_path` is a single path + if paths and isinstance(paths, str): + paths = [paths] + + # Loop over paths and return only existing + for path in paths: + if os.path.exists(path): + return path + return None diff --git a/igniter/user_settings.py b/igniter/user_settings.py index 00ce68cb0b..77fb8b5ae5 100644 --- a/igniter/user_settings.py +++ b/igniter/user_settings.py @@ -118,7 +118,7 @@ class ASettingRegistry(): """Delete item from settings. Note: - see :meth:`pype.lib.user_settings.ARegistrySettings.delete_item` + see :meth:`openpype.lib.user_settings.ARegistrySettings.delete_item` """ pass @@ -211,12 +211,12 @@ class IniSettingRegistry(ASettingRegistry): # type: (str, str) -> IniSettingRegistry super(IniSettingRegistry, self).__init__(name) # get registry file - version = os.getenv("PYPE_VERSION", "N/A") + version = os.getenv("OPENPYPE_VERSION", "N/A") self._registry_file = os.path.join(path, "{}.ini".format(name)) if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: print("# Settings registry", cfg) - print("# Generated by Pype {}".format(version), cfg) + print("# Generated by OpenPype {}".format(version), cfg) now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") print("# {}".format(now), cfg) @@ -350,7 +350,7 @@ class IniSettingRegistry(ASettingRegistry): """Delete item from default section. Note: - See :meth:`~pype.lib.IniSettingsRegistry.delete_item_from_section` + See :meth:`~openpype.lib.IniSettingsRegistry.delete_item_from_section` """ self.delete_item_from_section("MAIN", name) @@ -367,7 +367,7 @@ class JSONSettingRegistry(ASettingRegistry): now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") header = { "__metadata__": { - "pype-version": os.getenv("PYPE_VERSION", "N/A"), + "openpype-version": os.getenv("OPENPYPE_VERSION", "N/A"), "generated": now }, "registry": {} @@ -385,7 +385,7 @@ class JSONSettingRegistry(ASettingRegistry): """Get item value from registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.get_item` + See :meth:`openpype.lib.JSONSettingRegistry.get_item` """ with open(self._registry_file, mode="r") as cfg: @@ -418,7 +418,7 @@ class JSONSettingRegistry(ASettingRegistry): """Set item value to registry json. Note: - See :meth:`pype.lib.JSONSettingRegistry.set_item` + See :meth:`openpype.lib.JSONSettingRegistry.set_item` """ with open(self._registry_file, "r+") as cfg: @@ -450,8 +450,8 @@ class JSONSettingRegistry(ASettingRegistry): json.dump(data, cfg, indent=4) -class PypeSettingsRegistry(JSONSettingRegistry): - """Class handling Pype general settings registry. +class OpenPypeSettingsRegistry(JSONSettingRegistry): + """Class handling OpenPype general settings registry. Attributes: vendor (str): Name used for path construction. @@ -461,6 +461,7 @@ class PypeSettingsRegistry(JSONSettingRegistry): def __init__(self): self.vendor = "pypeclub" - self.product = "pype" + self.product = "openpype" path = appdirs.user_data_dir(self.product, self.vendor) - super(PypeSettingsRegistry, self).__init__("pype_settings", path) + super(OpenPypeSettingsRegistry, self).__init__( + "openpype_settings", path) diff --git a/igniter/version.py b/igniter/version.py index 3c627aaa1a..8c8ffdccb7 100644 --- a/igniter/version.py +++ b/igniter/version.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- """Definition of Igniter version.""" -__version__ = "1.0.0" +__version__ = "1.0.0-beta" diff --git a/pype/__init__.py b/openpype/__init__.py similarity index 100% rename from pype/__init__.py rename to openpype/__init__.py diff --git a/pype/__main__.py b/openpype/__main__.py similarity index 100% rename from pype/__main__.py rename to openpype/__main__.py diff --git a/pype/action.py b/openpype/action.py similarity index 100% rename from pype/action.py rename to openpype/action.py diff --git a/pype/api.py b/openpype/api.py similarity index 97% rename from pype/api.py rename to openpype/api.py index 37e878580a..ce18097eca 100644 --- a/pype/api.py +++ b/openpype/api.py @@ -24,7 +24,7 @@ from .lib import ( get_latest_version, get_global_environments, get_local_site_id, - change_pype_mongo_url + change_openpype_mongo_url ) from .lib.mongo import ( @@ -120,5 +120,5 @@ __all__ = [ "get_global_environments", "get_local_site_id", - "change_pype_mongo_url" + "change_openpype_mongo_url" ] diff --git a/pype/cli.py b/openpype/cli.py similarity index 97% rename from pype/cli.py rename to openpype/cli.py index f67cf10ea1..c6da88cbc1 100644 --- a/pype/cli.py +++ b/openpype/cli.py @@ -93,7 +93,7 @@ def eventserver(debug, provided credentials will be stored for later use. """ if debug: - os.environ['PYPE_DEBUG'] = "3" + os.environ['OPENPYPE_DEBUG'] = "3" PypeCommands().launch_eventservercli( ftrack_url, @@ -139,7 +139,7 @@ def publish(debug, paths): More than one path is allowed. """ if debug: - os.environ['PYPE_DEBUG'] = '3' + os.environ['OPENPYPE_DEBUG'] = '3' PypeCommands.publish(list(paths)) @@ -164,7 +164,7 @@ def texturecopy(debug, project, asset, path): Nothing is written to database. """ if debug: - os.environ['PYPE_DEBUG'] = '3' + os.environ['OPENPYPE_DEBUG'] = '3' PypeCommands().texture_copy(project, asset, path) @@ -178,7 +178,7 @@ def texturecopy(debug, project, asset, path): default=lambda: os.environ.get('AVALON_TASK', '')) @click.option("--tools", help="List of tools to add") @click.option("--user", help="Pype user name", - default=lambda: os.environ.get('PYPE_USERNAME', '')) + default=lambda: os.environ.get('OPENPYPE_USERNAME', '')) @click.option("-fs", "--ftrack-server", help="Registered application name", @@ -214,7 +214,7 @@ def launch(app, project, asset, task, os.environ["FTRACK_API_KEY"] = ftrack_key if user: - os.environ["PYPE_USERNAME"] = user + os.environ["OPENPYPE_USERNAME"] = user # test required if not project or not asset or not task: diff --git a/pype/hooks/pre_add_last_workfile_arg.py b/openpype/hooks/pre_add_last_workfile_arg.py similarity index 95% rename from pype/hooks/pre_add_last_workfile_arg.py rename to openpype/hooks/pre_add_last_workfile_arg.py index e19a884eff..377026da4a 100644 --- a/pype/hooks/pre_add_last_workfile_arg.py +++ b/openpype/hooks/pre_add_last_workfile_arg.py @@ -1,5 +1,5 @@ import os -from pype.lib import PreLaunchHook +from openpype.lib import PreLaunchHook class AddLastWorkfileToLaunchArgs(PreLaunchHook): diff --git a/pype/hooks/pre_global_host_data.py b/openpype/hooks/pre_global_host_data.py similarity index 94% rename from pype/hooks/pre_global_host_data.py rename to openpype/hooks/pre_global_host_data.py index 74be208367..c669d91ad5 100644 --- a/pype/hooks/pre_global_host_data.py +++ b/openpype/hooks/pre_global_host_data.py @@ -1,5 +1,5 @@ -from pype.api import Anatomy -from pype.lib import ( +from openpype.api import Anatomy +from openpype.lib import ( PreLaunchHook, EnvironmentPrepData, prepare_host_environments, @@ -32,7 +32,7 @@ class GlobalHostDataHook(PreLaunchHook): "project_name": self.data["project_name"], "asset_name": self.data["asset_name"], "task_name": self.data["task_name"], - "app_name": app.app_name, + "app": app, "dbcon": self.data["dbcon"], @@ -41,7 +41,6 @@ class GlobalHostDataHook(PreLaunchHook): "anatomy": self.data["anatomy"], - "settings_env": self.data.get("settings_env"), "env": self.launch_context.env, "log": self.log diff --git a/pype/hooks/pre_non_python_host_launch.py b/openpype/hooks/pre_non_python_host_launch.py similarity index 87% rename from pype/hooks/pre_non_python_host_launch.py rename to openpype/hooks/pre_non_python_host_launch.py index ad1a202d4e..c16a72c5e5 100644 --- a/pype/hooks/pre_non_python_host_launch.py +++ b/openpype/hooks/pre_non_python_host_launch.py @@ -1,11 +1,11 @@ import os -from pype.lib import ( +from openpype.lib import ( PreLaunchHook, get_pype_execute_args ) -from pype import PACKAGE_DIR as PYPE_DIR +from openpype import PACKAGE_DIR as OPENPYPE_DIR class NonPythonHostHook(PreLaunchHook): @@ -13,7 +13,7 @@ class NonPythonHostHook(PreLaunchHook): Non python host implementation do not launch host directly but use python script which launch the host. For these cases it is necessary to - prepend python (or pype) executable and script path before application's. + prepend python (or openpype) executable and script path before application's. """ app_groups = ["harmony", "photoshop", "aftereffects"] @@ -27,7 +27,7 @@ class NonPythonHostHook(PreLaunchHook): remainders.append(self.launch_context.launch_args.pop(0)) script_path = os.path.join( - PYPE_DIR, + OPENPYPE_DIR, "scripts", "non_python_host_launch.py" ) diff --git a/pype/hooks/pre_python2_vendor.py b/openpype/hooks/pre_python2_vendor.py similarity index 81% rename from pype/hooks/pre_python2_vendor.py rename to openpype/hooks/pre_python2_vendor.py index 6f34e44132..7aaf713dec 100644 --- a/pype/hooks/pre_python2_vendor.py +++ b/openpype/hooks/pre_python2_vendor.py @@ -1,20 +1,20 @@ import os -from pype.lib import PreLaunchHook +from openpype.lib import PreLaunchHook class PrePython2Vendor(PreLaunchHook): """Prepend python 2 dependencies for py2 hosts.""" - # WARNING This hook will probably be deprecated in Pype 3 - kept for test + # WARNING This hook will probably be deprecated in OpenPype 3 - kept for test order = 10 app_groups = ["hiero", "nuke", "nukex"] def execute(self): # Prepare vendor dir path self.log.info("adding global python 2 vendor") - pype_root = os.getenv("PYPE_ROOT") + pype_root = os.getenv("OPENPYPE_ROOT") python_2_vendor = os.path.join( pype_root, - "pype", + "openpype", "vendor", "python", "python_2" @@ -32,4 +32,3 @@ class PrePython2Vendor(PreLaunchHook): # Set new PYTHONPATH to launch context environments self.launch_context.env["PYTHONPATH"] = os.pathsep.join(python_paths) - \ No newline at end of file diff --git a/pype/hooks/pre_with_windows_shell.py b/openpype/hooks/pre_with_windows_shell.py similarity index 95% rename from pype/hooks/pre_with_windows_shell.py rename to openpype/hooks/pre_with_windows_shell.py index d675c9bf5b..5f0f03f13e 100644 --- a/pype/hooks/pre_with_windows_shell.py +++ b/openpype/hooks/pre_with_windows_shell.py @@ -1,5 +1,5 @@ import os -from pype.lib import PreLaunchHook +from openpype.lib import PreLaunchHook class LaunchWithWindowsShell(PreLaunchHook): diff --git a/pype/hosts/__init__.py b/openpype/hosts/__init__.py similarity index 100% rename from pype/hosts/__init__.py rename to openpype/hosts/__init__.py diff --git a/pype/hosts/aftereffects/__init__.py b/openpype/hosts/aftereffects/__init__.py similarity index 100% rename from pype/hosts/aftereffects/__init__.py rename to openpype/hosts/aftereffects/__init__.py diff --git a/pype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py similarity index 93% rename from pype/hosts/aftereffects/api/__init__.py rename to openpype/hosts/aftereffects/api/__init__.py index cd9ce6a835..ee73a0f52b 100644 --- a/pype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -5,15 +5,15 @@ import logging from avalon import io from avalon import api as avalon from avalon.vendor import Qt -from pype import lib +from openpype import lib import pyblish.api as pyblish -import pype.hosts.aftereffects +import openpype.hosts.aftereffects log = logging.getLogger("pype.hosts.aftereffects") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.aftereffects.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.aftereffects.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") diff --git a/pype/hosts/aftereffects/plugins/__init__.py b/openpype/hosts/aftereffects/plugins/__init__.py similarity index 100% rename from pype/hosts/aftereffects/plugins/__init__.py rename to openpype/hosts/aftereffects/plugins/__init__.py diff --git a/pype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py similarity index 92% rename from pype/hosts/aftereffects/plugins/create/create_render.py rename to openpype/hosts/aftereffects/plugins/create/create_render.py index 70abd36406..bb78e89a89 100644 --- a/pype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -1,4 +1,4 @@ -import pype.api +import openpype.api from avalon.vendor import Qt from avalon import aftereffects @@ -7,7 +7,7 @@ import logging log = logging.getLogger(__name__) -class CreateRender(pype.api.Creator): +class CreateRender(openpype.api.Creator): """Render folder for publish. Creates subsets in format 'familyTaskSubsetname', @@ -49,7 +49,7 @@ class CreateRender(pype.api.Creator): self.data["uuid"] = item.id # for SubsetManager stub.imprint(item, self.data) stub.set_label_color(item.id, 14) # Cyan options 0 - 16 - stub.rename_item(item, stub.PUBLISH_ICON + self.data["subset"]) + stub.rename_item(item.id, stub.PUBLISH_ICON + self.data["subset"]) def _show_msg(self, txt): msg = Qt.QtWidgets.QMessageBox() diff --git a/pype/hosts/aftereffects/plugins/load/load_background.py b/openpype/hosts/aftereffects/plugins/load/load_background.py similarity index 97% rename from pype/hosts/aftereffects/plugins/load/load_background.py rename to openpype/hosts/aftereffects/plugins/load/load_background.py index e6f8b6a032..9856abe3fe 100644 --- a/pype/hosts/aftereffects/plugins/load/load_background.py +++ b/openpype/hosts/aftereffects/plugins/load/load_background.py @@ -2,7 +2,7 @@ import re from avalon import api, aftereffects -from pype.lib import get_background_layers, get_unique_layer_name +from openpype.lib import get_background_layers, get_unique_layer_name stub = aftereffects.stub() diff --git a/pype/hosts/aftereffects/plugins/load/load_file.py b/openpype/hosts/aftereffects/plugins/load/load_file.py similarity index 97% rename from pype/hosts/aftereffects/plugins/load/load_file.py rename to openpype/hosts/aftereffects/plugins/load/load_file.py index 500a53a69b..e63a538e7b 100644 --- a/pype/hosts/aftereffects/plugins/load/load_file.py +++ b/openpype/hosts/aftereffects/plugins/load/load_file.py @@ -1,5 +1,5 @@ from avalon import api, aftereffects -from pype import lib +from openpype import lib import re stub = aftereffects.stub() @@ -88,7 +88,7 @@ class FileLoader(api.Loader): layer_name = container["namespace"] path = api.get_representation_path(representation) # with aftereffects.maintained_selection(): # TODO - stub.replace_item(layer, path, stub.LOADED_ICON + layer_name) + stub.replace_item(layer.id, path, stub.LOADED_ICON + layer_name) stub.imprint( layer, {"representation": str(representation["_id"]), "name": context["subset"], diff --git a/openpype/hosts/aftereffects/plugins/publish/add_publish_highlight.py b/openpype/hosts/aftereffects/plugins/publish/add_publish_highlight.py new file mode 100644 index 0000000000..2425f72e3e --- /dev/null +++ b/openpype/hosts/aftereffects/plugins/publish/add_publish_highlight.py @@ -0,0 +1,21 @@ +import pyblish.api + +from avalon import aftereffects + + +class AddPublishHighlight(pyblish.api.InstancePlugin): + """ + Revert back rendered comp name and add publish highlight + """ + + label = "Add render highlight" + order = pyblish.api.IntegratorOrder + 8.0 + hosts = ["aftereffects"] + families = ["render.farm"] + optional = True + + def process(self, instance): + stub = aftereffects.stub() + item = instance.data + # comp name contains highlight icon + stub.rename_item(item["comp_id"], item["comp_name"]) diff --git a/pype/hosts/aftereffects/plugins/publish/collect_audio.py b/openpype/hosts/aftereffects/plugins/publish/collect_audio.py similarity index 100% rename from pype/hosts/aftereffects/plugins/publish/collect_audio.py rename to openpype/hosts/aftereffects/plugins/publish/collect_audio.py diff --git a/pype/hosts/aftereffects/plugins/publish/collect_current_file.py b/openpype/hosts/aftereffects/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/aftereffects/plugins/publish/collect_current_file.py rename to openpype/hosts/aftereffects/plugins/publish/collect_current_file.py diff --git a/pype/hosts/aftereffects/plugins/publish/collect_render.py b/openpype/hosts/aftereffects/plugins/publish/collect_render.py similarity index 93% rename from pype/hosts/aftereffects/plugins/publish/collect_render.py rename to openpype/hosts/aftereffects/plugins/publish/collect_render.py index 7f7d5a52bc..4a124991fd 100644 --- a/pype/hosts/aftereffects/plugins/publish/collect_render.py +++ b/openpype/hosts/aftereffects/plugins/publish/collect_render.py @@ -1,5 +1,5 @@ -from pype.lib import abstract_collect_render -from pype.lib.abstract_collect_render import RenderInstance +from openpype.lib import abstract_collect_render +from openpype.lib.abstract_collect_render import RenderInstance import pyblish.api import attr import os @@ -23,6 +23,8 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): padding_width = 6 rendered_extension = 'png' + stub = aftereffects.stub() + def get_instances(self, context): instances = [] @@ -31,9 +33,9 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): asset_entity = context.data["assetEntity"] project_entity = context.data["projectEntity"] - compositions = aftereffects.stub().get_items(True) + compositions = self.stub.get_items(True) compositions_by_id = {item.id: item for item in compositions} - for inst in aftereffects.stub().get_metadata(): + for inst in self.stub.get_metadata(): schema = inst.get('schema') # loaded asset container skip it if schema and 'container' in schema: @@ -43,7 +45,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): raise ValueError("Couldn't find id, unable to publish. " + "Please recreate instance.") item_id = inst["members"][0] - work_area_info = aftereffects.stub().get_work_area(int(item_id)) + work_area_info = self.stub.get_work_area(int(item_id)) frameStart = work_area_info.workAreaStart frameEnd = round(work_area_info.workAreaStart + @@ -94,6 +96,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): instances.append(instance) + self.log.debug("instances::{}".format(instances)) return instances def get_expected_files(self, render_instance): @@ -113,7 +116,7 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): end = render_instance.frameEnd # pull file name from Render Queue Output module - render_q = aftereffects.stub().get_render_info() + render_q = self.stub.get_render_info() if not render_q: raise ValueError("No file extension set in Render Queue") _, ext = os.path.splitext(os.path.basename(render_q.file_name)) diff --git a/pype/hosts/aftereffects/plugins/publish/collect_workfile.py b/openpype/hosts/aftereffects/plugins/publish/collect_workfile.py similarity index 100% rename from pype/hosts/aftereffects/plugins/publish/collect_workfile.py rename to openpype/hosts/aftereffects/plugins/publish/collect_workfile.py diff --git a/pype/hosts/aftereffects/plugins/publish/extract_save_scene.py b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py similarity index 53% rename from pype/hosts/aftereffects/plugins/publish/extract_save_scene.py rename to openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py index e19065d086..5418ea6299 100644 --- a/pype/hosts/aftereffects/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/aftereffects/plugins/publish/extract_save_scene.py @@ -1,14 +1,15 @@ -import pype.api +import openpype.api from avalon import aftereffects -class ExtractSaveScene(pype.api.Extractor): +class ExtractSaveScene(openpype.api.Extractor): """Save scene before extraction.""" - order = pype.api.Extractor.order - 0.49 + order = openpype.api.Extractor.order - 0.48 label = "Extract Save Scene" hosts = ["aftereffects"] families = ["workfile"] def process(self, instance): - aftereffects.stub().save() + stub = aftereffects.stub() + stub.save() diff --git a/pype/hosts/aftereffects/plugins/publish/increment_workfile.py b/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py similarity index 89% rename from pype/hosts/aftereffects/plugins/publish/increment_workfile.py rename to openpype/hosts/aftereffects/plugins/publish/increment_workfile.py index ef49d01280..18ddf41366 100644 --- a/pype/hosts/aftereffects/plugins/publish/increment_workfile.py +++ b/openpype/hosts/aftereffects/plugins/publish/increment_workfile.py @@ -1,6 +1,6 @@ import pyblish.api -from pype.action import get_errored_plugins_from_data -from pype.lib import version_up +from openpype.action import get_errored_plugins_from_data +from openpype.lib import version_up from avalon import aftereffects diff --git a/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py new file mode 100644 index 0000000000..291f22e3b8 --- /dev/null +++ b/openpype/hosts/aftereffects/plugins/publish/remove_publish_highlight.py @@ -0,0 +1,23 @@ +import openpype.api +from avalon import aftereffects + + +class RemovePublishHighlight(openpype.api.Extractor): + """Clean utf characters which are not working in DL + + Published compositions are marked with unicode icon which causes + problems on specific render environments. Clean it first, sent to + rendering, add it later back to avoid confusion. + """ + + order = openpype.api.Extractor.order - 0.49 # just before save + label = "Clean render comp" + hosts = ["aftereffects"] + families = ["render.farm"] + + def process(self, instance): + stub = aftereffects.stub() + self.log.debug("instance::{}".format(instance.data)) + item = instance.data + comp_name = item["comp_name"].replace(stub.PUBLISH_ICON, '') + stub.rename_item(item["comp_id"], comp_name) diff --git a/pype/hosts/aftereffects/resources/template.aep b/openpype/hosts/aftereffects/resources/template.aep similarity index 100% rename from pype/hosts/aftereffects/resources/template.aep rename to openpype/hosts/aftereffects/resources/template.aep diff --git a/pype/hosts/blender/__init__.py b/openpype/hosts/blender/__init__.py similarity index 100% rename from pype/hosts/blender/__init__.py rename to openpype/hosts/blender/__init__.py diff --git a/pype/hosts/blender/api/__init__.py b/openpype/hosts/blender/api/__init__.py similarity index 93% rename from pype/hosts/blender/api/__init__.py rename to openpype/hosts/blender/api/__init__.py index 7fcd09201a..55c5b63f60 100644 --- a/pype/hosts/blender/api/__init__.py +++ b/openpype/hosts/blender/api/__init__.py @@ -7,9 +7,9 @@ import bpy from avalon import api as avalon from pyblish import api as pyblish -import pype.hosts.blender +import openpype.hosts.blender -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.blender.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") diff --git a/pype/hosts/blender/api/action.py b/openpype/hosts/blender/api/action.py similarity index 96% rename from pype/hosts/blender/api/action.py rename to openpype/hosts/blender/api/action.py index b140688670..f3426ac3cf 100644 --- a/pype/hosts/blender/api/action.py +++ b/openpype/hosts/blender/api/action.py @@ -2,7 +2,7 @@ import bpy import pyblish.api -from pype.api import get_errored_instances_from_context +from openpype.api import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): diff --git a/pype/hosts/blender/api/plugin.py b/openpype/hosts/blender/api/plugin.py similarity index 99% rename from pype/hosts/blender/api/plugin.py rename to openpype/hosts/blender/api/plugin.py index f216eb28be..eb88e7af63 100644 --- a/pype/hosts/blender/api/plugin.py +++ b/openpype/hosts/blender/api/plugin.py @@ -7,7 +7,7 @@ import bpy from avalon import api import avalon.blender -from pype.api import PypeCreatorMixin +from openpype.api import PypeCreatorMixin VALID_EXTENSIONS = [".blend", ".json"] diff --git a/openpype/hosts/blender/hooks/pre_pyside_install.py b/openpype/hosts/blender/hooks/pre_pyside_install.py new file mode 100644 index 0000000000..088a27566d --- /dev/null +++ b/openpype/hosts/blender/hooks/pre_pyside_install.py @@ -0,0 +1,148 @@ +import os +import subprocess +from openpype.lib import PreLaunchHook + + +class InstallPySideToBlender(PreLaunchHook): + """Install Qt binding to blender's python packages. + + Prelaunch hook does 2 things: + 1.) Blender's python packages are pushed to the beginning of PYTHONPATH. + 2.) Check if blender has installed PySide2 and will try to install if not. + + For pipeline implementation is required to have Qt binding installed in + blender's python packages. + + Prelaunch hook can work only on Windows right now. + """ + + app_groups = ["blender"] + platforms = ["windows"] + + def execute(self): + # Prelaunch hook is not crutial + try: + self.inner_execute() + except Exception: + self.log.warning( + "Processing of {} crashed.".format(self.__class__.__name__), + exc_info=True + ) + + def inner_execute(self): + # Get blender's python directory + executable = self.launch_context.executable.executable_path + # Blender installation contain subfolder named with it's version where + # python binaries are stored. + version_subfolder = self.launch_context.app_name.split("_")[1] + pythond_dir = os.path.join( + os.path.dirname(executable), + version_subfolder, + "python" + ) + + # Change PYTHONPATH to contain blender's packages as first + python_paths = [ + os.path.join(pythond_dir, "lib"), + os.path.join(pythond_dir, "lib", "site-packages"), + ] + python_path = self.launch_context.env.get("PYTHONPATH") or "" + for path in python_path.split(os.pathsep): + if path: + python_paths.append(path) + + self.launch_context.env["PYTHONPATH"] = os.pathsep.join(python_paths) + + # Get blender's python executable + python_executable = os.path.join(pythond_dir, "bin", "python.exe") + if not os.path.exists(python_executable): + self.log.warning( + "Couldn't find python executable for blender. {}".format( + executable + ) + ) + return + + # Check if PySide2 is installed and skip if yes + if self.is_pyside_installed(python_executable): + return + + # Install PySide2 in blender's python + self.install_pyside_windows(python_executable) + + def install_pyside_windows(self, python_executable): + """Install PySide2 python module to blender's python. + + Installation requires administration rights that's why it is required + to use "pywin32" module which can execute command's and ask for + administration rights. + """ + try: + import win32api + import win32con + import win32process + import win32event + import pywintypes + from win32comext.shell.shell import ShellExecuteEx + from win32comext.shell import shellcon + except Exception: + self.log.warning("Couldn't import \"pywin32\" modules") + return + + try: + # Parameters + # - use "-m pip" as module pip to install PySide2 and argument + # "--ignore-installed" is to force install module to blender's + # site-packages and make sure it is binary compatible + parameters = "-m pip install --ignore-installed PySide2" + + # Execute command and ask for administrator's rights + process_info = ShellExecuteEx( + nShow=win32con.SW_SHOWNORMAL, + fMask=shellcon.SEE_MASK_NOCLOSEPROCESS, + lpVerb="runas", + lpFile=python_executable, + lpParameters=parameters, + lpDirectory=os.path.dirname(python_executable) + ) + process_handle = process_info["hProcess"] + obj = win32event.WaitForSingleObject( + process_handle, win32event.INFINITE + ) + returncode = win32process.GetExitCodeProcess(process_handle) + if returncode == 0: + self.log.info( + "Successfully installed PySide2 module to blender." + ) + return + except pywintypes.error: + pass + + self.log.warning("Failed to instal PySide2 module to blender.") + + def is_pyside_installed(self, python_executable): + """Check if PySide2 module is in blender's pip list. + + Check that PySide2 is installed directly in blender's site-packages. + It is possible that it is installed in user's site-packages but that + may be incompatible with blender's python. + """ + # Get pip list from blender's python executable + args = [python_executable, "-m", "pip", "list"] + process = subprocess.Popen(args, stdout=subprocess.PIPE) + stdout, _ = process.communicate() + lines = stdout.decode().split("\r\n") + # Second line contain dashes that define maximum length of module name. + # Second column of dashes define maximum length of module version. + package_dashes, *_ = lines[1].split(" ") + package_len = len(package_dashes) + + # Got through printed lines starting at line 3 + for idx in range(2, len(lines)): + line = lines[idx] + if not line: + continue + package_name = line[0:package_len].strip() + if package_name.lower() == "pyside2": + return True + return False diff --git a/pype/hosts/blender/plugins/__init__.py b/openpype/hosts/blender/plugins/__init__.py similarity index 100% rename from pype/hosts/blender/plugins/__init__.py rename to openpype/hosts/blender/plugins/__init__.py diff --git a/pype/hosts/blender/plugins/create/__init__.py b/openpype/hosts/blender/plugins/create/__init__.py similarity index 100% rename from pype/hosts/blender/plugins/create/__init__.py rename to openpype/hosts/blender/plugins/create/__init__.py diff --git a/pype/hosts/blender/plugins/create/create_action.py b/openpype/hosts/blender/plugins/create/create_action.py similarity index 86% rename from pype/hosts/blender/plugins/create/create_action.py rename to openpype/hosts/blender/plugins/create/create_action.py index b8ad24e711..f7bb2bfc26 100644 --- a/pype/hosts/blender/plugins/create/create_action.py +++ b/openpype/hosts/blender/plugins/create/create_action.py @@ -3,11 +3,11 @@ import bpy from avalon import api -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin from avalon.blender import lib -class CreateAction(pype.hosts.blender.api.plugin.Creator): +class CreateAction(openpype.hosts.blender.api.plugin.Creator): """Action output for character rigs""" name = "actionMain" @@ -19,7 +19,7 @@ class CreateAction(pype.hosts.blender.api.plugin.Creator): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_animation.py b/openpype/hosts/blender/plugins/create/create_animation.py similarity index 79% rename from pype/hosts/blender/plugins/create/create_animation.py rename to openpype/hosts/blender/plugins/create/create_animation.py index 79744ad7e9..9aebf7e9b7 100644 --- a/pype/hosts/blender/plugins/create/create_animation.py +++ b/openpype/hosts/blender/plugins/create/create_animation.py @@ -3,10 +3,10 @@ import bpy from avalon import api, blender -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateAnimation(pype.hosts.blender.api.plugin.Creator): +class CreateAnimation(openpype.hosts.blender.api.plugin.Creator): """Animation output for character rigs""" name = "animationMain" @@ -17,7 +17,7 @@ class CreateAnimation(pype.hosts.blender.api.plugin.Creator): def process(self): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_camera.py b/openpype/hosts/blender/plugins/create/create_camera.py similarity index 80% rename from pype/hosts/blender/plugins/create/create_camera.py rename to openpype/hosts/blender/plugins/create/create_camera.py index 177d26e08b..c7fea30787 100644 --- a/pype/hosts/blender/plugins/create/create_camera.py +++ b/openpype/hosts/blender/plugins/create/create_camera.py @@ -4,10 +4,10 @@ import bpy from avalon import api from avalon.blender import lib -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateCamera(pype.hosts.blender.api.plugin.Creator): +class CreateCamera(openpype.hosts.blender.api.plugin.Creator): """Polygonal static geometry""" name = "cameraMain" @@ -19,7 +19,7 @@ class CreateCamera(pype.hosts.blender.api.plugin.Creator): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_layout.py b/openpype/hosts/blender/plugins/create/create_layout.py similarity index 86% rename from pype/hosts/blender/plugins/create/create_layout.py rename to openpype/hosts/blender/plugins/create/create_layout.py index f45b58d137..f72e364f50 100644 --- a/pype/hosts/blender/plugins/create/create_layout.py +++ b/openpype/hosts/blender/plugins/create/create_layout.py @@ -4,10 +4,10 @@ import bpy from avalon import api from avalon.blender import lib -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateLayout(pype.hosts.blender.api.plugin.Creator): +class CreateLayout(openpype.hosts.blender.api.plugin.Creator): """Layout output for character rigs""" name = "layoutMain" @@ -19,7 +19,7 @@ class CreateLayout(pype.hosts.blender.api.plugin.Creator): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_model.py b/openpype/hosts/blender/plugins/create/create_model.py similarity index 79% rename from pype/hosts/blender/plugins/create/create_model.py rename to openpype/hosts/blender/plugins/create/create_model.py index 7404b3a157..921d86513b 100644 --- a/pype/hosts/blender/plugins/create/create_model.py +++ b/openpype/hosts/blender/plugins/create/create_model.py @@ -4,10 +4,10 @@ import bpy from avalon import api from avalon.blender import lib -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateModel(pype.hosts.blender.api.plugin.Creator): +class CreateModel(openpype.hosts.blender.api.plugin.Creator): """Polygonal static geometry""" name = "modelMain" @@ -19,7 +19,7 @@ class CreateModel(pype.hosts.blender.api.plugin.Creator): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_rig.py b/openpype/hosts/blender/plugins/create/create_rig.py similarity index 87% rename from pype/hosts/blender/plugins/create/create_rig.py rename to openpype/hosts/blender/plugins/create/create_rig.py index d96a88f71d..116fb9f742 100644 --- a/pype/hosts/blender/plugins/create/create_rig.py +++ b/openpype/hosts/blender/plugins/create/create_rig.py @@ -4,10 +4,10 @@ import bpy from avalon import api from avalon.blender import lib -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateRig(pype.hosts.blender.api.plugin.Creator): +class CreateRig(openpype.hosts.blender.api.plugin.Creator): """Artist-friendly rig with controls to direct motion""" name = "rigMain" @@ -19,7 +19,7 @@ class CreateRig(pype.hosts.blender.api.plugin.Creator): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/create/create_setdress.py b/openpype/hosts/blender/plugins/create/create_setdress.py similarity index 75% rename from pype/hosts/blender/plugins/create/create_setdress.py rename to openpype/hosts/blender/plugins/create/create_setdress.py index 201893b3df..97c737c235 100644 --- a/pype/hosts/blender/plugins/create/create_setdress.py +++ b/openpype/hosts/blender/plugins/create/create_setdress.py @@ -1,10 +1,10 @@ import bpy from avalon import api, blender -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -class CreateSetDress(pype.hosts.blender.api.plugin.Creator): +class CreateSetDress(openpype.hosts.blender.api.plugin.Creator): """A grouped package of loaded content""" name = "setdressMain" @@ -16,7 +16,7 @@ class CreateSetDress(pype.hosts.blender.api.plugin.Creator): def process(self): asset = self.data["asset"] subset = self.data["subset"] - name = pype.hosts.blender.api.plugin.asset_name(asset, subset) + name = openpype.hosts.blender.api.plugin.asset_name(asset, subset) collection = bpy.data.collections.new(name=name) bpy.context.scene.collection.children.link(collection) self.data['task'] = api.Session.get('AVALON_TASK') diff --git a/pype/hosts/blender/plugins/load/__init__.py b/openpype/hosts/blender/plugins/load/__init__.py similarity index 100% rename from pype/hosts/blender/plugins/load/__init__.py rename to openpype/hosts/blender/plugins/load/__init__.py diff --git a/pype/hosts/blender/plugins/load/load_action.py b/openpype/hosts/blender/plugins/load/load_action.py similarity index 94% rename from pype/hosts/blender/plugins/load/load_action.py rename to openpype/hosts/blender/plugins/load/load_action.py index 79e42995b3..a9d8522220 100644 --- a/pype/hosts/blender/plugins/load/load_action.py +++ b/openpype/hosts/blender/plugins/load/load_action.py @@ -7,12 +7,12 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_action") +logger = logging.getLogger("openpype").getChild("blender").getChild("load_action") -class BlendActionLoader(pype.hosts.blender.api.plugin.AssetLoader): +class BlendActionLoader(openpype.hosts.blender.api.plugin.AssetLoader): """Load action from a .blend file. Warning: @@ -42,8 +42,8 @@ class BlendActionLoader(pype.hosts.blender.api.plugin.AssetLoader): libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = pype.hosts.blender.api.plugin.asset_name( + lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) + container_name = openpype.hosts.blender.api.plugin.asset_name( asset, subset, namespace ) @@ -149,7 +149,7 @@ class BlendActionLoader(pype.hosts.blender.api.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -247,7 +247,7 @@ class BlendActionLoader(pype.hosts.blender.api.plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/load/load_animation.py b/openpype/hosts/blender/plugins/load/load_animation.py similarity index 93% rename from pype/hosts/blender/plugins/load/load_animation.py rename to openpype/hosts/blender/plugins/load/load_animation.py index a1be6e99ed..4025fdfa74 100644 --- a/pype/hosts/blender/plugins/load/load_animation.py +++ b/openpype/hosts/blender/plugins/load/load_animation.py @@ -7,14 +7,14 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -logger = logging.getLogger("pype").getChild( +logger = logging.getLogger("openpype").getChild( "blender").getChild("load_animation") -class BlendAnimationLoader(pype.hosts.blender.api.plugin.AssetLoader): +class BlendAnimationLoader(openpype.hosts.blender.api.plugin.AssetLoader): """Load animations from a .blend file. Warning: @@ -105,8 +105,8 @@ class BlendAnimationLoader(pype.hosts.blender.api.plugin.AssetLoader): libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = pype.hosts.blender.api.plugin.asset_name( + lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) + container_name = openpype.hosts.blender.api.plugin.asset_name( asset, subset, namespace ) @@ -175,7 +175,7 @@ class BlendAnimationLoader(pype.hosts.blender.api.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -217,7 +217,7 @@ class BlendAnimationLoader(pype.hosts.blender.api.plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/load/load_camera.py b/openpype/hosts/blender/plugins/load/load_camera.py similarity index 93% rename from pype/hosts/blender/plugins/load/load_camera.py rename to openpype/hosts/blender/plugins/load/load_camera.py index e6aa11af7e..30300100e0 100644 --- a/pype/hosts/blender/plugins/load/load_camera.py +++ b/openpype/hosts/blender/plugins/load/load_camera.py @@ -7,12 +7,12 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.api.plugin +import openpype.hosts.blender.api.plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_camera") +logger = logging.getLogger("openpype").getChild("blender").getChild("load_camera") -class BlendCameraLoader(pype.hosts.blender.api.plugin.AssetLoader): +class BlendCameraLoader(openpype.hosts.blender.api.plugin.AssetLoader): """Load a camera from a .blend file. Warning: @@ -92,8 +92,8 @@ class BlendCameraLoader(pype.hosts.blender.api.plugin.AssetLoader): libpath = self.fname asset = context["asset"]["name"] subset = context["subset"]["name"] - lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset) - container_name = pype.hosts.blender.api.plugin.asset_name( + lib_container = openpype.hosts.blender.api.plugin.asset_name(asset, subset) + container_name = openpype.hosts.blender.api.plugin.asset_name( asset, subset, namespace ) @@ -162,7 +162,7 @@ class BlendCameraLoader(pype.hosts.blender.api.plugin.AssetLoader): assert libpath.is_file(), ( f"The file doesn't exist: {libpath}" ) - assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( + assert extension in openpype.hosts.blender.api.plugin.VALID_EXTENSIONS, ( f"Unsupported file: {libpath}" ) @@ -216,7 +216,7 @@ class BlendCameraLoader(pype.hosts.blender.api.plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/load/load_layout.py b/openpype/hosts/blender/plugins/load/load_layout.py similarity index 98% rename from pype/hosts/blender/plugins/load/load_layout.py rename to openpype/hosts/blender/plugins/load/load_layout.py index 8d4c9fb75c..73b12d8c25 100644 --- a/pype/hosts/blender/plugins/load/load_layout.py +++ b/openpype/hosts/blender/plugins/load/load_layout.py @@ -11,8 +11,8 @@ from typing import Dict, List, Optional from avalon import api, blender, pipeline import bpy -import pype.hosts.blender.api.plugin as plugin -from pype.lib import get_creator_by_name +import openpype.hosts.blender.api.plugin as plugin +from openpype.lib import get_creator_by_name class BlendLayoutLoader(plugin.AssetLoader): @@ -251,7 +251,7 @@ class BlendLayoutLoader(plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: @@ -648,7 +648,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py similarity index 98% rename from pype/hosts/blender/plugins/load/load_model.py rename to openpype/hosts/blender/plugins/load/load_model.py index f48c0f8f94..7297e459a6 100644 --- a/pype/hosts/blender/plugins/load/load_model.py +++ b/openpype/hosts/blender/plugins/load/load_model.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.api.plugin as plugin +import openpype.hosts.blender.api.plugin as plugin class BlendModelLoader(plugin.AssetLoader): @@ -211,7 +211,7 @@ class BlendModelLoader(plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py similarity index 98% rename from pype/hosts/blender/plugins/load/load_rig.py rename to openpype/hosts/blender/plugins/load/load_rig.py index 1cc722045c..c5690a6ab8 100644 --- a/pype/hosts/blender/plugins/load/load_rig.py +++ b/openpype/hosts/blender/plugins/load/load_rig.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional from avalon import api, blender import bpy -import pype.hosts.blender.api.plugin as plugin +import openpype.hosts.blender.api.plugin as plugin class BlendRigLoader(plugin.AssetLoader): @@ -267,7 +267,7 @@ class BlendRigLoader(plugin.AssetLoader): """Remove an existing container from a Blender scene. Arguments: - container (avalon-core:container-1.0): Container to remove, + container (openpype:container-1.0): Container to remove, from `host.ls()`. Returns: diff --git a/pype/hosts/blender/plugins/publish/__init__.py b/openpype/hosts/blender/plugins/publish/__init__.py similarity index 100% rename from pype/hosts/blender/plugins/publish/__init__.py rename to openpype/hosts/blender/plugins/publish/__init__.py diff --git a/pype/hosts/blender/plugins/publish/collect_current_file.py b/openpype/hosts/blender/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/blender/plugins/publish/collect_current_file.py rename to openpype/hosts/blender/plugins/publish/collect_current_file.py diff --git a/pype/hosts/blender/plugins/publish/collect_instances.py b/openpype/hosts/blender/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/blender/plugins/publish/collect_instances.py rename to openpype/hosts/blender/plugins/publish/collect_instances.py diff --git a/pype/hosts/blender/plugins/publish/extract_abc.py b/openpype/hosts/blender/plugins/publish/extract_abc.py similarity index 93% rename from pype/hosts/blender/plugins/publish/extract_abc.py rename to openpype/hosts/blender/plugins/publish/extract_abc.py index 949b28df51..6a89c6019b 100644 --- a/pype/hosts/blender/plugins/publish/extract_abc.py +++ b/openpype/hosts/blender/plugins/publish/extract_abc.py @@ -1,12 +1,12 @@ import os -import pype.api -import pype.hosts.blender.api.plugin +import openpype.api +import openpype.hosts.blender.api.plugin import bpy -class ExtractABC(pype.api.Extractor): +class ExtractABC(openpype.api.Extractor): """Extract as ABC.""" label = "Extract ABC" @@ -61,7 +61,7 @@ class ExtractABC(pype.api.Extractor): except: continue - new_context = pype.hosts.blender.api.plugin.create_blender_context( + new_context = openpype.hosts.blender.api.plugin.create_blender_context( active=selected[0], selected=selected) # We set the scale of the scene for the export diff --git a/pype/hosts/blender/plugins/publish/extract_animation_collection.py b/openpype/hosts/blender/plugins/publish/extract_animation_collection.py similarity index 96% rename from pype/hosts/blender/plugins/publish/extract_animation_collection.py rename to openpype/hosts/blender/plugins/publish/extract_animation_collection.py index 0cdd17cf1f..19dc59c5cd 100644 --- a/pype/hosts/blender/plugins/publish/extract_animation_collection.py +++ b/openpype/hosts/blender/plugins/publish/extract_animation_collection.py @@ -1,13 +1,13 @@ import os import json -import pype.api +import openpype.api import pyblish.api import bpy -class ExtractSetDress(pype.api.Extractor): +class ExtractSetDress(openpype.api.Extractor): """Extract setdress.""" label = "Extract SetDress" diff --git a/pype/hosts/blender/plugins/publish/extract_blend.py b/openpype/hosts/blender/plugins/publish/extract_blend.py similarity index 95% rename from pype/hosts/blender/plugins/publish/extract_blend.py rename to openpype/hosts/blender/plugins/publish/extract_blend.py index 6ba28d039a..890c8b5ffd 100644 --- a/pype/hosts/blender/plugins/publish/extract_blend.py +++ b/openpype/hosts/blender/plugins/publish/extract_blend.py @@ -1,10 +1,10 @@ import os import avalon.blender.workio -import pype.api +import openpype.api -class ExtractBlend(pype.api.Extractor): +class ExtractBlend(openpype.api.Extractor): """Extract a blend file.""" label = "Extract Blend" diff --git a/pype/hosts/blender/plugins/publish/extract_fbx.py b/openpype/hosts/blender/plugins/publish/extract_fbx.py similarity index 97% rename from pype/hosts/blender/plugins/publish/extract_fbx.py rename to openpype/hosts/blender/plugins/publish/extract_fbx.py index 231bfdde24..dc74348949 100644 --- a/pype/hosts/blender/plugins/publish/extract_fbx.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx.py @@ -1,11 +1,11 @@ import os -import pype.api +import openpype.api import bpy -class ExtractFBX(pype.api.Extractor): +class ExtractFBX(openpype.api.Extractor): """Extract as FBX.""" label = "Extract FBX" diff --git a/pype/hosts/blender/plugins/publish/extract_fbx_animation.py b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py similarity index 98% rename from pype/hosts/blender/plugins/publish/extract_fbx_animation.py rename to openpype/hosts/blender/plugins/publish/extract_fbx_animation.py index 9c421560f0..1036800705 100644 --- a/pype/hosts/blender/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/blender/plugins/publish/extract_fbx_animation.py @@ -1,13 +1,13 @@ import os -import pype.api +import openpype.api import bpy import bpy_extras import bpy_extras.anim_utils -class ExtractAnimationFBX(pype.api.Extractor): +class ExtractAnimationFBX(openpype.api.Extractor): """Extract as animation.""" label = "Extract FBX" diff --git a/pype/hosts/blender/plugins/publish/increment_workfile_version.py b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py similarity index 94% rename from pype/hosts/blender/plugins/publish/increment_workfile_version.py rename to openpype/hosts/blender/plugins/publish/increment_workfile_version.py index 5addca6392..db73842323 100644 --- a/pype/hosts/blender/plugins/publish/increment_workfile_version.py +++ b/openpype/hosts/blender/plugins/publish/increment_workfile_version.py @@ -16,7 +16,7 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin): assert all(result["success"] for result in context.data["results"]), ( "Publishing not succesfull so version is not increased.") - from pype.lib import version_up + from openpype.lib import version_up path = context.data["currentFile"] filepath = version_up(path) diff --git a/pype/hosts/blender/plugins/publish/integrate_animation.py b/openpype/hosts/blender/plugins/publish/integrate_animation.py similarity index 100% rename from pype/hosts/blender/plugins/publish/integrate_animation.py rename to openpype/hosts/blender/plugins/publish/integrate_animation.py diff --git a/pype/hosts/blender/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py similarity index 93% rename from pype/hosts/blender/plugins/publish/validate_mesh_has_uv.py rename to openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py index c415ea0e0d..1c73476fc8 100644 --- a/pype/hosts/blender/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_has_uv.py @@ -3,7 +3,7 @@ from typing import List import bpy import pyblish.api -import pype.hosts.blender.api.action +import openpype.hosts.blender.api.action class ValidateMeshHasUvs(pyblish.api.InstancePlugin): @@ -14,7 +14,7 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin): families = ["model"] category = "geometry" label = "Mesh Has UV's" - actions = [pype.hosts.blender.api.action.SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] optional = True @staticmethod diff --git a/pype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py similarity index 88% rename from pype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py rename to openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py index bbce37f3db..00159a2d36 100644 --- a/pype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/blender/plugins/publish/validate_mesh_no_negative_scale.py @@ -3,7 +3,7 @@ from typing import List import bpy import pyblish.api -import pype.hosts.blender.api.action +import openpype.hosts.blender.api.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): @@ -13,7 +13,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): hosts = ["blender"] families = ["model"] label = "Mesh No Negative Scale" - actions = [pype.hosts.blender.api.action.SelectInvalidAction] + actions = [openpype.hosts.blender.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance) -> List: diff --git a/openpype/hosts/blender/startup/init.py b/openpype/hosts/blender/startup/init.py new file mode 100644 index 0000000000..4b4e48fedc --- /dev/null +++ b/openpype/hosts/blender/startup/init.py @@ -0,0 +1,3 @@ +from openpype.hosts.blender import api + +api.install() diff --git a/pype/hosts/celaction/__init__.py b/openpype/hosts/celaction/__init__.py similarity index 100% rename from pype/hosts/celaction/__init__.py rename to openpype/hosts/celaction/__init__.py diff --git a/pype/hosts/celaction/api/__init__.py b/openpype/hosts/celaction/api/__init__.py similarity index 100% rename from pype/hosts/celaction/api/__init__.py rename to openpype/hosts/celaction/api/__init__.py diff --git a/pype/hosts/celaction/api/cli.py b/openpype/hosts/celaction/api/cli.py similarity index 91% rename from pype/hosts/celaction/api/cli.py rename to openpype/hosts/celaction/api/cli.py index f77bdea451..0a70610acb 100644 --- a/pype/hosts/celaction/api/cli.py +++ b/openpype/hosts/celaction/api/cli.py @@ -9,16 +9,16 @@ from avalon.tools import publish import pyblish.api import pyblish.util -from pype.api import Logger -import pype -import pype.hosts.celaction -from pype.hosts.celaction import api as celaction +from openpype.api import Logger +import openpype +import openpype.hosts.celaction +from openpype.hosts.celaction import api as celaction log = Logger().get_logger("Celaction_cli_publisher") publish_host = "celaction" -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.celaction.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.celaction.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") @@ -89,7 +89,7 @@ def main(): _prepare_publish_environments() # Registers pype's Global pyblish plugins - pype.install() + openpype.install() if os.path.exists(PUBLISH_PATH): log.info(f"Registering path: {PUBLISH_PATH}") diff --git a/pype/hosts/celaction/hooks/__init__.py b/openpype/hosts/celaction/hooks/__init__.py similarity index 100% rename from pype/hosts/celaction/hooks/__init__.py rename to openpype/hosts/celaction/hooks/__init__.py diff --git a/pype/hosts/celaction/hooks/pre_celaction_registers.py b/openpype/hosts/celaction/hooks/pre_celaction_registers.py similarity index 96% rename from pype/hosts/celaction/hooks/pre_celaction_registers.py rename to openpype/hosts/celaction/hooks/pre_celaction_registers.py index 40e8eaa6ff..e49e66f163 100644 --- a/pype/hosts/celaction/hooks/pre_celaction_registers.py +++ b/openpype/hosts/celaction/hooks/pre_celaction_registers.py @@ -1,8 +1,8 @@ import os import shutil import winreg -from pype.lib import PreLaunchHook -from pype.hosts.celaction import api as celaction +from openpype.lib import PreLaunchHook +from openpype.hosts.celaction import api as celaction class CelactionPrelaunchHook(PreLaunchHook): @@ -35,7 +35,7 @@ class CelactionPrelaunchHook(PreLaunchHook): winreg.KEY_ALL_ACCESS) # TODO: this will need to be checked more thoroughly - pype_exe = os.getenv("PYPE_EXECUTABLE") + pype_exe = os.getenv("OPENPYPE_EXECUTABLE") winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, pype_exe) diff --git a/pype/hosts/celaction/plugins/__init__.py b/openpype/hosts/celaction/plugins/__init__.py similarity index 100% rename from pype/hosts/celaction/plugins/__init__.py rename to openpype/hosts/celaction/plugins/__init__.py diff --git a/pype/hosts/celaction/plugins/publish/collect_audio.py b/openpype/hosts/celaction/plugins/publish/collect_audio.py similarity index 100% rename from pype/hosts/celaction/plugins/publish/collect_audio.py rename to openpype/hosts/celaction/plugins/publish/collect_audio.py diff --git a/pype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py similarity index 93% rename from pype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py rename to openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py index 463805ce4b..15c5ddaf1c 100644 --- a/pype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py +++ b/openpype/hosts/celaction/plugins/publish/collect_celaction_cli_kwargs.py @@ -1,5 +1,5 @@ import pyblish.api -from pype.hosts.celaction import api as celaction +from openpype.hosts.celaction import api as celaction class CollectCelactionCliKwargs(pyblish.api.Collector): diff --git a/pype/hosts/celaction/plugins/publish/collect_celaction_instances.py b/openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py similarity index 100% rename from pype/hosts/celaction/plugins/publish/collect_celaction_instances.py rename to openpype/hosts/celaction/plugins/publish/collect_celaction_instances.py diff --git a/pype/hosts/celaction/plugins/publish/collect_render_path.py b/openpype/hosts/celaction/plugins/publish/collect_render_path.py similarity index 100% rename from pype/hosts/celaction/plugins/publish/collect_render_path.py rename to openpype/hosts/celaction/plugins/publish/collect_render_path.py diff --git a/pype/hosts/celaction/plugins/publish/integrate_version_up.py b/openpype/hosts/celaction/plugins/publish/integrate_version_up.py similarity index 88% rename from pype/hosts/celaction/plugins/publish/integrate_version_up.py rename to openpype/hosts/celaction/plugins/publish/integrate_version_up.py index 140878e2b9..dc08127a8a 100644 --- a/pype/hosts/celaction/plugins/publish/integrate_version_up.py +++ b/openpype/hosts/celaction/plugins/publish/integrate_version_up.py @@ -1,5 +1,5 @@ import shutil -import pype +import openpype import pyblish.api @@ -12,7 +12,7 @@ class VersionUpScene(pyblish.api.ContextPlugin): def process(self, context): current_file = context.data.get('currentFile') - v_up = pype.lib.version_up(current_file) + v_up = openpype.lib.version_up(current_file) self.log.debug('Current file is: {}'.format(current_file)) self.log.debug('Version up: {}'.format(v_up)) diff --git a/pype/hosts/celaction/plugins/publish/submit_celaction_deadline.py b/openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py similarity index 100% rename from pype/hosts/celaction/plugins/publish/submit_celaction_deadline.py rename to openpype/hosts/celaction/plugins/publish/submit_celaction_deadline.py diff --git a/pype/hosts/celaction/resources/celaction_template_scene.scn b/openpype/hosts/celaction/resources/celaction_template_scene.scn similarity index 100% rename from pype/hosts/celaction/resources/celaction_template_scene.scn rename to openpype/hosts/celaction/resources/celaction_template_scene.scn diff --git a/pype/hosts/fusion/__init__.py b/openpype/hosts/fusion/__init__.py similarity index 100% rename from pype/hosts/fusion/__init__.py rename to openpype/hosts/fusion/__init__.py diff --git a/pype/hosts/fusion/api/__init__.py b/openpype/hosts/fusion/api/__init__.py similarity index 86% rename from pype/hosts/fusion/api/__init__.py rename to openpype/hosts/fusion/api/__init__.py index 61eaf44ddb..5581a0a9cb 100644 --- a/pype/hosts/fusion/api/__init__.py +++ b/openpype/hosts/fusion/api/__init__.py @@ -15,7 +15,7 @@ from .lib import ( update_frame_range ) -from .menu import launch_pype_menu +from .menu import launch_openpype_menu __all__ = [ @@ -34,5 +34,5 @@ __all__ = [ "update_frame_range", # menu - "launch_pype_menu", + "launch_openpype_menu", ] diff --git a/pype/hosts/fusion/api/lib.py b/openpype/hosts/fusion/api/lib.py similarity index 100% rename from pype/hosts/fusion/api/lib.py rename to openpype/hosts/fusion/api/lib.py diff --git a/pype/hosts/fusion/api/menu.py b/openpype/hosts/fusion/api/menu.py similarity index 86% rename from pype/hosts/fusion/api/menu.py rename to openpype/hosts/fusion/api/menu.py index c37f1daa49..3f04bf839b 100644 --- a/pype/hosts/fusion/api/menu.py +++ b/openpype/hosts/fusion/api/menu.py @@ -15,7 +15,7 @@ from avalon.tools import ( libraryloader ) -from pype.hosts.fusion.scripts import ( +from openpype.hosts.fusion.scripts import ( set_rendermode, duplicate_with_inputs ) @@ -49,11 +49,11 @@ class Spacer(QtWidgets.QWidget): self.setLayout(layout) -class PypeMenu(QtWidgets.QWidget): +class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) - self.setObjectName("PypeMenu") + self.setObjectName("OpenPypeMenu") self.setWindowFlags( QtCore.Qt.Window @@ -63,14 +63,14 @@ class PypeMenu(QtWidgets.QWidget): | QtCore.Qt.WindowStaysOnTopHint ) self.render_mode_widget = None - self.setWindowTitle("Pype") - workfiles_btn = QtWidgets.QPushButton("Workfiles", self) - create_btn = QtWidgets.QPushButton("Create", self) - publish_btn = QtWidgets.QPushButton("Publish", self) - load_btn = QtWidgets.QPushButton("Load", self) - inventory_btn = QtWidgets.QPushButton("Inventory", self) - libload_btn = QtWidgets.QPushButton("Library", self) - rendermode_btn = QtWidgets.QPushButton("Set render mode", self) + self.setWindowTitle("OpenPype") + workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) + create_btn = QtWidgets.QPushButton("Create ...", self) + publish_btn = QtWidgets.QPushButton("Publish ...", self) + load_btn = QtWidgets.QPushButton("Load ...", self) + inventory_btn = QtWidgets.QPushButton("Inventory ...", self) + libload_btn = QtWidgets.QPushButton("Library ...", self) + rendermode_btn = QtWidgets.QPushButton("Set render mode ...", self) duplicate_with_inputs_btn = QtWidgets.QPushButton( "Duplicate with input connections", self ) @@ -156,11 +156,11 @@ class PypeMenu(QtWidgets.QWidget): print("Clicked Reset Resolution") -def launch_pype_menu(): +def launch_openpype_menu(): app = QtWidgets.QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) - pype_menu = PypeMenu() + pype_menu = OpenPypeMenu() stylesheet = load_stylesheet() pype_menu.setStyleSheet(stylesheet) diff --git a/pype/hosts/fusion/api/menu_style.qss b/openpype/hosts/fusion/api/menu_style.qss similarity index 96% rename from pype/hosts/fusion/api/menu_style.qss rename to openpype/hosts/fusion/api/menu_style.qss index df4fd7e949..12c474b070 100644 --- a/pype/hosts/fusion/api/menu_style.qss +++ b/openpype/hosts/fusion/api/menu_style.qss @@ -20,7 +20,7 @@ QPushButton:hover { color: #e64b3d; } -#PypeMenu { +#OpenPypeMenu { border: 1px solid #fef9ef; } diff --git a/pype/hosts/fusion/api/pipeline.py b/openpype/hosts/fusion/api/pipeline.py similarity index 95% rename from pype/hosts/fusion/api/pipeline.py rename to openpype/hosts/fusion/api/pipeline.py index 36084dc734..4fec548993 100644 --- a/pype/hosts/fusion/api/pipeline.py +++ b/openpype/hosts/fusion/api/pipeline.py @@ -6,15 +6,12 @@ import os from avalon.tools import workfiles from avalon import api as avalon from pyblish import api as pyblish -from pype.api import Logger -import pype.hosts.fusion +from openpype.api import Logger +import openpype.hosts.fusion log = Logger().get_logger(__name__) - -AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") - -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.fusion.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.fusion.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") diff --git a/pype/hosts/fusion/api/utils.py b/openpype/hosts/fusion/api/utils.py similarity index 94% rename from pype/hosts/fusion/api/utils.py rename to openpype/hosts/fusion/api/utils.py index 377c33d457..5605323b1e 100644 --- a/pype/hosts/fusion/api/utils.py +++ b/openpype/hosts/fusion/api/utils.py @@ -7,8 +7,8 @@ Fusion tools for setting environment import os import shutil -from pype.api import Logger -import pype.hosts.fusion +from openpype.api import Logger +import openpype.hosts.fusion log = Logger().get_logger(__name__) @@ -27,7 +27,7 @@ def _sync_utility_scripts(env=None): us_env = env.get("FUSION_UTILITY_SCRIPTS_SOURCE_DIR") us_dir = env.get("FUSION_UTILITY_SCRIPTS_DIR", "") us_paths = [os.path.join( - os.path.dirname(os.path.abspath(pype.hosts.fusion.__file__)), + os.path.dirname(os.path.abspath(openpype.hosts.fusion.__file__)), "utility_scripts" )] diff --git a/pype/hosts/fusion/hooks/pre_fusion_setup.py b/openpype/hosts/fusion/hooks/pre_fusion_setup.py similarity index 95% rename from pype/hosts/fusion/hooks/pre_fusion_setup.py rename to openpype/hosts/fusion/hooks/pre_fusion_setup.py index 0a6135419b..a0c16a6700 100644 --- a/pype/hosts/fusion/hooks/pre_fusion_setup.py +++ b/openpype/hosts/fusion/hooks/pre_fusion_setup.py @@ -1,7 +1,7 @@ import os import importlib -from pype.lib import PreLaunchHook -from pype.hosts.fusion.api import utils +from openpype.lib import PreLaunchHook +from openpype.hosts.fusion.api import utils class FusionPrelaunch(PreLaunchHook): diff --git a/pype/hosts/fusion/plugins/create/create_exr_saver.py b/openpype/hosts/fusion/plugins/create/create_exr_saver.py similarity index 95% rename from pype/hosts/fusion/plugins/create/create_exr_saver.py rename to openpype/hosts/fusion/plugins/create/create_exr_saver.py index 560f7deb7f..077e77c059 100644 --- a/pype/hosts/fusion/plugins/create/create_exr_saver.py +++ b/openpype/hosts/fusion/plugins/create/create_exr_saver.py @@ -1,10 +1,10 @@ import os -import pype.api +import openpype.api from avalon import fusion -class CreateOpenEXRSaver(pype.api.Creator): +class CreateOpenEXRSaver(openpype.api.Creator): name = "openexrDefault" label = "Create OpenEXR Saver" diff --git a/pype/hosts/fusion/plugins/inventory/select_containers.py b/openpype/hosts/fusion/plugins/inventory/select_containers.py similarity index 100% rename from pype/hosts/fusion/plugins/inventory/select_containers.py rename to openpype/hosts/fusion/plugins/inventory/select_containers.py diff --git a/pype/hosts/fusion/plugins/inventory/set_tool_color.py b/openpype/hosts/fusion/plugins/inventory/set_tool_color.py similarity index 100% rename from pype/hosts/fusion/plugins/inventory/set_tool_color.py rename to openpype/hosts/fusion/plugins/inventory/set_tool_color.py diff --git a/pype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py similarity index 95% rename from pype/hosts/fusion/plugins/load/actions.py rename to openpype/hosts/fusion/plugins/load/actions.py index 51a32ee6e1..e1cdc6a41b 100644 --- a/pype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -22,7 +22,7 @@ class FusionSetFrameRangeLoader(api.Loader): def load(self, context, name, namespace, data): - from pype.hosts.fusion.api import lib + from openpype.hosts.fusion.api import lib version = context['version'] version_data = version.get("data", {}) @@ -55,7 +55,7 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): def load(self, context, name, namespace, data): - from pype.hosts.fusion.api import lib + from openpype.hosts.fusion.api import lib version = context['version'] version_data = version.get("data", {}) diff --git a/pype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py similarity index 100% rename from pype/hosts/fusion/plugins/load/load_sequence.py rename to openpype/hosts/fusion/plugins/load/load_sequence.py diff --git a/pype/hosts/fusion/plugins/publish/collect_comp.py b/openpype/hosts/fusion/plugins/publish/collect_comp.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/collect_comp.py rename to openpype/hosts/fusion/plugins/publish/collect_comp.py diff --git a/pype/hosts/fusion/plugins/publish/collect_fusion_version.py b/openpype/hosts/fusion/plugins/publish/collect_fusion_version.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/collect_fusion_version.py rename to openpype/hosts/fusion/plugins/publish/collect_fusion_version.py diff --git a/pype/hosts/fusion/plugins/publish/collect_instances.py b/openpype/hosts/fusion/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/collect_instances.py rename to openpype/hosts/fusion/plugins/publish/collect_instances.py diff --git a/pype/hosts/fusion/plugins/publish/collect_render_target.py b/openpype/hosts/fusion/plugins/publish/collect_render_target.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/collect_render_target.py rename to openpype/hosts/fusion/plugins/publish/collect_render_target.py diff --git a/pype/hosts/fusion/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py similarity index 89% rename from pype/hosts/fusion/plugins/publish/increment_current_file_deadline.py rename to openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py index 9641ba7ef6..6483454d96 100644 --- a/pype/hosts/fusion/plugins/publish/increment_current_file_deadline.py +++ b/openpype/hosts/fusion/plugins/publish/increment_current_file_deadline.py @@ -16,8 +16,8 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): def process(self, context): - from pype.lib import version_up - from pype.action import get_errored_plugins_from_data + from openpype.lib import version_up + from openpype.action import get_errored_plugins_from_data errored_plugins = get_errored_plugins_from_data(context) if any(plugin.__name__ == "FusionSubmitDeadline" diff --git a/pype/hosts/fusion/plugins/publish/render_local.py b/openpype/hosts/fusion/plugins/publish/render_local.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/render_local.py rename to openpype/hosts/fusion/plugins/publish/render_local.py diff --git a/pype/hosts/fusion/plugins/publish/save_scene.py b/openpype/hosts/fusion/plugins/publish/save_scene.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/save_scene.py rename to openpype/hosts/fusion/plugins/publish/save_scene.py diff --git a/pype/hosts/fusion/plugins/publish/submit_deadline.py b/openpype/hosts/fusion/plugins/publish/submit_deadline.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/submit_deadline.py rename to openpype/hosts/fusion/plugins/publish/submit_deadline.py diff --git a/pype/hosts/fusion/plugins/publish/validate_background_depth.py b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py similarity index 97% rename from pype/hosts/fusion/plugins/publish/validate_background_depth.py rename to openpype/hosts/fusion/plugins/publish/validate_background_depth.py index de042ae315..a0734d8278 100644 --- a/pype/hosts/fusion/plugins/publish/validate_background_depth.py +++ b/openpype/hosts/fusion/plugins/publish/validate_background_depth.py @@ -1,6 +1,6 @@ import pyblish.api -from pype import action +from openpype import action class ValidateBackgroundDepth(pyblish.api.InstancePlugin): diff --git a/pype/hosts/fusion/plugins/publish/validate_comp_saved.py b/openpype/hosts/fusion/plugins/publish/validate_comp_saved.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/validate_comp_saved.py rename to openpype/hosts/fusion/plugins/publish/validate_comp_saved.py diff --git a/pype/hosts/fusion/plugins/publish/validate_create_folder_checked.py b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py similarity index 97% rename from pype/hosts/fusion/plugins/publish/validate_create_folder_checked.py rename to openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py index cce3695c31..45ed53f65c 100644 --- a/pype/hosts/fusion/plugins/publish/validate_create_folder_checked.py +++ b/openpype/hosts/fusion/plugins/publish/validate_create_folder_checked.py @@ -1,6 +1,6 @@ import pyblish.api -from pype import action +from openpype import action class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): diff --git a/pype/hosts/fusion/plugins/publish/validate_filename_has_extension.py b/openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/validate_filename_has_extension.py rename to openpype/hosts/fusion/plugins/publish/validate_filename_has_extension.py diff --git a/pype/hosts/fusion/plugins/publish/validate_saver_has_input.py b/openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/validate_saver_has_input.py rename to openpype/hosts/fusion/plugins/publish/validate_saver_has_input.py diff --git a/pype/hosts/fusion/plugins/publish/validate_saver_passthrough.py b/openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/validate_saver_passthrough.py rename to openpype/hosts/fusion/plugins/publish/validate_saver_passthrough.py diff --git a/pype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py similarity index 100% rename from pype/hosts/fusion/plugins/publish/validate_unique_subsets.py rename to openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py diff --git a/pype/hosts/fusion/scripts/__init__.py b/openpype/hosts/fusion/scripts/__init__.py similarity index 100% rename from pype/hosts/fusion/scripts/__init__.py rename to openpype/hosts/fusion/scripts/__init__.py diff --git a/pype/hosts/fusion/scripts/duplicate_with_inputs.py b/openpype/hosts/fusion/scripts/duplicate_with_inputs.py similarity index 100% rename from pype/hosts/fusion/scripts/duplicate_with_inputs.py rename to openpype/hosts/fusion/scripts/duplicate_with_inputs.py diff --git a/pype/hosts/fusion/scripts/fusion_switch_shot.py b/openpype/hosts/fusion/scripts/fusion_switch_shot.py similarity index 99% rename from pype/hosts/fusion/scripts/fusion_switch_shot.py rename to openpype/hosts/fusion/scripts/fusion_switch_shot.py index 2eea68b007..05b577c8ba 100644 --- a/pype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/openpype/hosts/fusion/scripts/fusion_switch_shot.py @@ -8,8 +8,8 @@ from avalon import api, io, pipeline import avalon.fusion # Config imports -import pype.lib as pype -import pype.hosts.fusion.api.lib as fusion_lib +import openpype.lib as pype +import openpype.hosts.fusion.api.lib as fusion_lib log = logging.getLogger("Update Slap Comp") diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/openpype/hosts/fusion/scripts/set_rendermode.py similarity index 100% rename from pype/hosts/fusion/scripts/set_rendermode.py rename to openpype/hosts/fusion/scripts/set_rendermode.py diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py b/openpype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py rename to openpype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py b/openpype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py rename to openpype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py b/openpype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py rename to openpype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py b/openpype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py rename to openpype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py diff --git a/pype/hosts/fusion/utility_scripts/Pype_menu.py b/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py similarity index 71% rename from pype/hosts/fusion/utility_scripts/Pype_menu.py rename to openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py index 0cd2b7502e..81df2bc31d 100644 --- a/pype/hosts/fusion/utility_scripts/Pype_menu.py +++ b/openpype/hosts/fusion/utility_scripts/__OpenPype_Menu__.py @@ -1,24 +1,24 @@ import os import sys -import pype +import openpype -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) def main(env): - from pype.hosts.fusion.api import menu + from openpype.hosts.fusion.api import menu import avalon.fusion # Registers pype's Global pyblish plugins - pype.install() + openpype.install() # activate resolve from pype avalon.api.install(avalon.fusion) log.info(f"Avalon registred hosts: {avalon.api.registered_host()}") - menu.launch_pype_menu() + menu.launch_openpype_menu() if __name__ == "__main__": diff --git a/pype/hosts/fusion/utility_scripts/switch_ui.py b/openpype/hosts/fusion/utility_scripts/switch_ui.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/switch_ui.py rename to openpype/hosts/fusion/utility_scripts/switch_ui.py diff --git a/pype/hosts/fusion/utility_scripts/update_loader_ranges.py b/openpype/hosts/fusion/utility_scripts/update_loader_ranges.py similarity index 100% rename from pype/hosts/fusion/utility_scripts/update_loader_ranges.py rename to openpype/hosts/fusion/utility_scripts/update_loader_ranges.py diff --git a/pype/hosts/harmony/__init__.py b/openpype/hosts/harmony/__init__.py similarity index 100% rename from pype/hosts/harmony/__init__.py rename to openpype/hosts/harmony/__init__.py diff --git a/pype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py similarity index 95% rename from pype/hosts/harmony/api/__init__.py rename to openpype/hosts/harmony/api/__init__.py index 1a0255d045..705ccef892 100644 --- a/pype/hosts/harmony/api/__init__.py +++ b/openpype/hosts/harmony/api/__init__.py @@ -4,9 +4,9 @@ import os from pathlib import Path import logging -from pype import lib -from pype.api import (get_current_project_settings) -import pype.hosts.harmony +from openpype import lib +from openpype.api import (get_current_project_settings) +import openpype.hosts.harmony import pyblish.api @@ -15,9 +15,9 @@ import avalon.api import avalon.tools.sceneinventory -log = logging.getLogger("pype.hosts.harmony") +log = logging.getLogger("openpype.hosts.harmony") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.harmony.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.harmony.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") @@ -154,7 +154,7 @@ def application_launch(): # It is now moved so it it manually called. # ensure_scene_settings() # check_inventory() - # fills PYPE_HARMONY_JS + # fills OPENPYPE_HARMONY_JS pype_harmony_path = Path(__file__).parent.parent / "js" / "PypeHarmony.js" pype_harmony_js = pype_harmony_path.read_text() diff --git a/pype/hosts/harmony/api/plugin.py b/openpype/hosts/harmony/api/plugin.py similarity index 67% rename from pype/hosts/harmony/api/plugin.py rename to openpype/hosts/harmony/api/plugin.py index 3525ad686d..7ac7fe510c 100644 --- a/pype/hosts/harmony/api/plugin.py +++ b/openpype/hosts/harmony/api/plugin.py @@ -1,5 +1,5 @@ from avalon import harmony -from pype.api import PypeCreatorMixin +from openpype.api import PypeCreatorMixin class Creator(PypeCreatorMixin, harmony.Creator): diff --git a/pype/hosts/harmony/js/.eslintrc.json b/openpype/hosts/harmony/js/.eslintrc.json similarity index 100% rename from pype/hosts/harmony/js/.eslintrc.json rename to openpype/hosts/harmony/js/.eslintrc.json diff --git a/pype/hosts/harmony/js/PypeHarmony.js b/openpype/hosts/harmony/js/PypeHarmony.js similarity index 100% rename from pype/hosts/harmony/js/PypeHarmony.js rename to openpype/hosts/harmony/js/PypeHarmony.js diff --git a/pype/hosts/harmony/js/README.md b/openpype/hosts/harmony/js/README.md similarity index 100% rename from pype/hosts/harmony/js/README.md rename to openpype/hosts/harmony/js/README.md diff --git a/pype/hosts/harmony/js/creators/CreateRender.js b/openpype/hosts/harmony/js/creators/CreateRender.js similarity index 86% rename from pype/hosts/harmony/js/creators/CreateRender.js rename to openpype/hosts/harmony/js/creators/CreateRender.js index cfb0701df4..92ec6dfd2f 100644 --- a/pype/hosts/harmony/js/creators/CreateRender.js +++ b/openpype/hosts/harmony/js/creators/CreateRender.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } diff --git a/pype/hosts/harmony/js/loaders/ImageSequenceLoader.js b/openpype/hosts/harmony/js/loaders/ImageSequenceLoader.js similarity index 98% rename from pype/hosts/harmony/js/loaders/ImageSequenceLoader.js rename to openpype/hosts/harmony/js/loaders/ImageSequenceLoader.js index cfa71e2834..d809c350ab 100644 --- a/pype/hosts/harmony/js/loaders/ImageSequenceLoader.js +++ b/openpype/hosts/harmony/js/loaders/ImageSequenceLoader.js @@ -5,8 +5,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } if (typeof $ === 'undefined'){ diff --git a/pype/hosts/harmony/js/loaders/TemplateLoader.js b/openpype/hosts/harmony/js/loaders/TemplateLoader.js similarity index 97% rename from pype/hosts/harmony/js/loaders/TemplateLoader.js rename to openpype/hosts/harmony/js/loaders/TemplateLoader.js index 160979f943..1df04c8282 100644 --- a/pype/hosts/harmony/js/loaders/TemplateLoader.js +++ b/openpype/hosts/harmony/js/loaders/TemplateLoader.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } if (typeof $ === 'undefined'){ diff --git a/pype/hosts/harmony/js/package.json b/openpype/hosts/harmony/js/package.json similarity index 100% rename from pype/hosts/harmony/js/package.json rename to openpype/hosts/harmony/js/package.json diff --git a/pype/hosts/harmony/js/publish/CollectCurrentFile.js b/openpype/hosts/harmony/js/publish/CollectCurrentFile.js similarity index 84% rename from pype/hosts/harmony/js/publish/CollectCurrentFile.js rename to openpype/hosts/harmony/js/publish/CollectCurrentFile.js index d39f23712d..2eeb7fb764 100644 --- a/pype/hosts/harmony/js/publish/CollectCurrentFile.js +++ b/openpype/hosts/harmony/js/publish/CollectCurrentFile.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } diff --git a/pype/hosts/harmony/js/publish/CollectFarmRender.js b/openpype/hosts/harmony/js/publish/CollectFarmRender.js similarity index 90% rename from pype/hosts/harmony/js/publish/CollectFarmRender.js rename to openpype/hosts/harmony/js/publish/CollectFarmRender.js index 7c0cda5165..759dc5ce5d 100644 --- a/pype/hosts/harmony/js/publish/CollectFarmRender.js +++ b/openpype/hosts/harmony/js/publish/CollectFarmRender.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } diff --git a/pype/hosts/harmony/js/publish/CollectPalettes.js b/openpype/hosts/harmony/js/publish/CollectPalettes.js similarity index 86% rename from pype/hosts/harmony/js/publish/CollectPalettes.js rename to openpype/hosts/harmony/js/publish/CollectPalettes.js index 8fda55ff75..afb0ad854a 100644 --- a/pype/hosts/harmony/js/publish/CollectPalettes.js +++ b/openpype/hosts/harmony/js/publish/CollectPalettes.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } diff --git a/pype/hosts/harmony/js/publish/ExtractPalette.js b/openpype/hosts/harmony/js/publish/ExtractPalette.js similarity index 88% rename from pype/hosts/harmony/js/publish/ExtractPalette.js rename to openpype/hosts/harmony/js/publish/ExtractPalette.js index 794c6fdbb1..c4765354c4 100644 --- a/pype/hosts/harmony/js/publish/ExtractPalette.js +++ b/openpype/hosts/harmony/js/publish/ExtractPalette.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } /** diff --git a/pype/hosts/harmony/js/publish/ExtractTemplate.js b/openpype/hosts/harmony/js/publish/ExtractTemplate.js similarity index 91% rename from pype/hosts/harmony/js/publish/ExtractTemplate.js rename to openpype/hosts/harmony/js/publish/ExtractTemplate.js index d36a8947f8..4676e1ff68 100644 --- a/pype/hosts/harmony/js/publish/ExtractTemplate.js +++ b/openpype/hosts/harmony/js/publish/ExtractTemplate.js @@ -6,8 +6,8 @@ // check if PypeHarmony is defined and if not, load it. if (typeof PypeHarmony === 'undefined') { - var PYPE_HARMONY_JS = System.getenv('PYPE_HARMONY_JS') + '/PypeHarmony.js'; - include(PYPE_HARMONY_JS.replace(/\\/g, "/")); + var OPENPYPE_HARMONY_JS = System.getenv('OPENPYPE_HARMONY_JS') + '/PypeHarmony.js'; + include(OPENPYPE_HARMONY_JS.replace(/\\/g, "/")); } diff --git a/pype/hosts/harmony/plugins/__init__.py b/openpype/hosts/harmony/plugins/__init__.py similarity index 100% rename from pype/hosts/harmony/plugins/__init__.py rename to openpype/hosts/harmony/plugins/__init__.py diff --git a/pype/hosts/harmony/plugins/create/create_farm_render.py b/openpype/hosts/harmony/plugins/create/create_farm_render.py similarity index 95% rename from pype/hosts/harmony/plugins/create/create_farm_render.py rename to openpype/hosts/harmony/plugins/create/create_farm_render.py index a1b198b672..26dab92034 100644 --- a/pype/hosts/harmony/plugins/create/create_farm_render.py +++ b/openpype/hosts/harmony/plugins/create/create_farm_render.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Create Composite node for render on farm.""" from avalon import harmony -from pype.hosts.harmony.api import plugin +from openpype.hosts.harmony.api import plugin class CreateFarmRender(plugin.Creator): diff --git a/pype/hosts/harmony/plugins/create/create_render.py b/openpype/hosts/harmony/plugins/create/create_render.py similarity index 93% rename from pype/hosts/harmony/plugins/create/create_render.py rename to openpype/hosts/harmony/plugins/create/create_render.py index b9a0987b37..b7fd6e6ef9 100644 --- a/pype/hosts/harmony/plugins/create/create_render.py +++ b/openpype/hosts/harmony/plugins/create/create_render.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Create render node.""" from avalon import harmony -from pype.hosts.harmony.api import plugin +from openpype.hosts.harmony.api import plugin class CreateRender(plugin.Creator): diff --git a/pype/hosts/harmony/plugins/create/create_template.py b/openpype/hosts/harmony/plugins/create/create_template.py similarity index 86% rename from pype/hosts/harmony/plugins/create/create_template.py rename to openpype/hosts/harmony/plugins/create/create_template.py index 628606c9f4..534179b190 100644 --- a/pype/hosts/harmony/plugins/create/create_template.py +++ b/openpype/hosts/harmony/plugins/create/create_template.py @@ -1,4 +1,4 @@ -from pype.hosts.harmony.api import plugin +from openpype.hosts.harmony.api import plugin class CreateTemplate(plugin.Creator): diff --git a/pype/hosts/harmony/plugins/load/load_audio.py b/openpype/hosts/harmony/plugins/load/load_audio.py similarity index 100% rename from pype/hosts/harmony/plugins/load/load_audio.py rename to openpype/hosts/harmony/plugins/load/load_audio.py diff --git a/pype/hosts/harmony/plugins/load/load_background.py b/openpype/hosts/harmony/plugins/load/load_background.py similarity index 99% rename from pype/hosts/harmony/plugins/load/load_background.py rename to openpype/hosts/harmony/plugins/load/load_background.py index 5ef4535576..946090f6e6 100644 --- a/pype/hosts/harmony/plugins/load/load_background.py +++ b/openpype/hosts/harmony/plugins/load/load_background.py @@ -2,7 +2,7 @@ import os import json from avalon import api, harmony -import pype.lib +import openpype.lib copy_files = """function copyFile(srcFilename, dstFilename) @@ -343,7 +343,7 @@ class BackgroundLoader(api.Loader): } %s """ % (sig, sig) - if pype.lib.is_latest(representation): + if openpype.lib.is_latest(representation): harmony.send({"function": func, "args": [node, "green"]}) else: harmony.send({"function": func, "args": [node, "red"]}) diff --git a/pype/hosts/harmony/plugins/load/load_imagesequence.py b/openpype/hosts/harmony/plugins/load/load_imagesequence.py similarity index 98% rename from pype/hosts/harmony/plugins/load/load_imagesequence.py rename to openpype/hosts/harmony/plugins/load/load_imagesequence.py index db7af90b14..80f63a8049 100644 --- a/pype/hosts/harmony/plugins/load/load_imagesequence.py +++ b/openpype/hosts/harmony/plugins/load/load_imagesequence.py @@ -7,7 +7,7 @@ from pathlib import Path import clique from avalon import api, harmony -import pype.lib +import openpype.lib class ImageSequenceLoader(api.Loader): @@ -105,7 +105,7 @@ class ImageSequenceLoader(api.Loader): ) # Colour node. - if pype.lib.is_latest(representation): + if openpype.lib.is_latest(representation): harmony.send( { "function": "PypeHarmony.setColor", diff --git a/pype/hosts/harmony/plugins/load/load_palette.py b/openpype/hosts/harmony/plugins/load/load_palette.py similarity index 100% rename from pype/hosts/harmony/plugins/load/load_palette.py rename to openpype/hosts/harmony/plugins/load/load_palette.py diff --git a/pype/hosts/harmony/plugins/load/load_template.py b/openpype/hosts/harmony/plugins/load/load_template.py similarity index 98% rename from pype/hosts/harmony/plugins/load/load_template.py rename to openpype/hosts/harmony/plugins/load/load_template.py index 59135c7931..34161daf81 100644 --- a/pype/hosts/harmony/plugins/load/load_template.py +++ b/openpype/hosts/harmony/plugins/load/load_template.py @@ -7,7 +7,7 @@ import shutil import uuid from avalon import api, harmony -import pype.lib +import openpype.lib class TemplateLoader(api.Loader): @@ -79,7 +79,7 @@ class TemplateLoader(api.Loader): self_name = self.__class__.__name__ update_and_replace = False - if pype.lib.is_latest(representation): + if openpype.lib.is_latest(representation): self._set_green(node) else: self._set_red(node) diff --git a/pype/hosts/harmony/plugins/load/load_template_workfile.py b/openpype/hosts/harmony/plugins/load/load_template_workfile.py similarity index 100% rename from pype/hosts/harmony/plugins/load/load_template_workfile.py rename to openpype/hosts/harmony/plugins/load/load_template_workfile.py diff --git a/pype/hosts/harmony/plugins/publish/collect_audio.py b/openpype/hosts/harmony/plugins/publish/collect_audio.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_audio.py rename to openpype/hosts/harmony/plugins/publish/collect_audio.py diff --git a/pype/hosts/harmony/plugins/publish/collect_current_file.py b/openpype/hosts/harmony/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_current_file.py rename to openpype/hosts/harmony/plugins/publish/collect_current_file.py diff --git a/pype/hosts/harmony/plugins/publish/collect_farm_render.py b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py similarity index 90% rename from pype/hosts/harmony/plugins/publish/collect_farm_render.py rename to openpype/hosts/harmony/plugins/publish/collect_farm_render.py index 98706ad951..fc80e7c029 100644 --- a/pype/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/openpype/hosts/harmony/plugins/publish/collect_farm_render.py @@ -5,9 +5,9 @@ from pathlib import Path import attr from avalon import harmony, api -import pype.lib.abstract_collect_render -from pype.lib.abstract_collect_render import RenderInstance -import pype.lib +import openpype.lib.abstract_collect_render +from openpype.lib.abstract_collect_render import RenderInstance +import openpype.lib @attr.s @@ -18,7 +18,7 @@ class HarmonyRenderInstance(RenderInstance): leadingZeros = attr.ib(default=3) -class CollectFarmRender(pype.lib.abstract_collect_render. +class CollectFarmRender(openpype.lib.abstract_collect_render. AbstractCollectRender): """Gather all publishable renders.""" @@ -124,10 +124,16 @@ class CollectFarmRender(pype.lib.abstract_collect_render. # TODO: handle pixel aspect and frame step # TODO: set Deadline stuff (pools, priority, etc. by presets) # because of using 'renderFarm' as a family, replace 'Farm' with - # capitalized task name - subset_name = node.split("/")[1].replace( + # capitalized task name - issue of avalon-core Creator app + subset_name = node.split("/")[1] + task_name = context.data["anatomyData"]["task"].capitalize() + replace_str = "" + if task_name.lower() not in subset_name.lower(): + replace_str = task_name + subset_name = subset_name.replace( 'Farm', - context.data["anatomyData"]["task"].capitalize()) + replace_str) + render_instance = HarmonyRenderInstance( version=version, time=api.time(), diff --git a/pype/hosts/harmony/plugins/publish/collect_instances.py b/openpype/hosts/harmony/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_instances.py rename to openpype/hosts/harmony/plugins/publish/collect_instances.py diff --git a/pype/hosts/harmony/plugins/publish/collect_palettes.py b/openpype/hosts/harmony/plugins/publish/collect_palettes.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_palettes.py rename to openpype/hosts/harmony/plugins/publish/collect_palettes.py diff --git a/pype/hosts/harmony/plugins/publish/collect_scene.py b/openpype/hosts/harmony/plugins/publish/collect_scene.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_scene.py rename to openpype/hosts/harmony/plugins/publish/collect_scene.py diff --git a/pype/hosts/harmony/plugins/publish/collect_workfile.py b/openpype/hosts/harmony/plugins/publish/collect_workfile.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/collect_workfile.py rename to openpype/hosts/harmony/plugins/publish/collect_workfile.py diff --git a/pype/hosts/harmony/plugins/publish/extract_palette.py b/openpype/hosts/harmony/plugins/publish/extract_palette.py similarity index 99% rename from pype/hosts/harmony/plugins/publish/extract_palette.py rename to openpype/hosts/harmony/plugins/publish/extract_palette.py index 39a822153c..d334883e9c 100644 --- a/pype/hosts/harmony/plugins/publish/extract_palette.py +++ b/openpype/hosts/harmony/plugins/publish/extract_palette.py @@ -6,10 +6,10 @@ import csv from PIL import Image, ImageDraw, ImageFont from avalon import harmony -import pype.api +import openpype.api -class ExtractPalette(pype.api.Extractor): +class ExtractPalette(openpype.api.Extractor): """Extract palette.""" label = "Extract Palette" diff --git a/pype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py similarity index 98% rename from pype/hosts/harmony/plugins/publish/extract_render.py rename to openpype/hosts/harmony/plugins/publish/extract_render.py index 551d7afee1..8374a9427a 100644 --- a/pype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -4,7 +4,7 @@ import subprocess import pyblish.api from avalon import harmony -import pype.lib +import openpype.lib import clique @@ -89,7 +89,7 @@ class ExtractRender(pyblish.api.InstancePlugin): # Generate thumbnail. thumbnail_path = os.path.join(path, "thumbnail.png") - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") args = [ "{}".format(ffmpeg_path), "-y", "-i", os.path.join(path, list(collections[0])[0]), diff --git a/pype/hosts/harmony/plugins/publish/extract_save_scene.py b/openpype/hosts/harmony/plugins/publish/extract_save_scene.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/extract_save_scene.py rename to openpype/hosts/harmony/plugins/publish/extract_save_scene.py diff --git a/pype/hosts/harmony/plugins/publish/extract_template.py b/openpype/hosts/harmony/plugins/publish/extract_template.py similarity index 96% rename from pype/hosts/harmony/plugins/publish/extract_template.py rename to openpype/hosts/harmony/plugins/publish/extract_template.py index 842bc77202..687fa19c22 100644 --- a/pype/hosts/harmony/plugins/publish/extract_template.py +++ b/openpype/hosts/harmony/plugins/publish/extract_template.py @@ -3,12 +3,12 @@ import os import shutil -import pype.api +import openpype.api from avalon import harmony -import pype.hosts.harmony +import openpype.hosts.harmony -class ExtractTemplate(pype.api.Extractor): +class ExtractTemplate(openpype.api.Extractor): """Extract the connected nodes to the composite instance.""" label = "Extract Template" @@ -50,7 +50,7 @@ class ExtractTemplate(pype.api.Extractor): dependencies.remove(instance.data["setMembers"][0]) # Export template. - pype.hosts.harmony.api.export_template( + openpype.hosts.harmony.api.export_template( unique_backdrops, dependencies, filepath ) diff --git a/pype/hosts/harmony/plugins/publish/extract_workfile.py b/openpype/hosts/harmony/plugins/publish/extract_workfile.py similarity index 94% rename from pype/hosts/harmony/plugins/publish/extract_workfile.py rename to openpype/hosts/harmony/plugins/publish/extract_workfile.py index 842d0aa8d3..7f25ec8150 100644 --- a/pype/hosts/harmony/plugins/publish/extract_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/extract_workfile.py @@ -4,10 +4,10 @@ import os import shutil from zipfile import ZipFile -import pype.api +import openpype.api -class ExtractWorkfile(pype.api.Extractor): +class ExtractWorkfile(openpype.api.Extractor): """Extract and zip complete workfile folder into zip.""" label = "Extract Workfile" diff --git a/pype/hosts/harmony/plugins/publish/increment_workfile.py b/openpype/hosts/harmony/plugins/publish/increment_workfile.py similarity index 91% rename from pype/hosts/harmony/plugins/publish/increment_workfile.py rename to openpype/hosts/harmony/plugins/publish/increment_workfile.py index 858e5fab0e..fcbe299d15 100644 --- a/pype/hosts/harmony/plugins/publish/increment_workfile.py +++ b/openpype/hosts/harmony/plugins/publish/increment_workfile.py @@ -1,8 +1,8 @@ import os import pyblish.api -from pype.action import get_errored_plugins_from_data -from pype.lib import version_up +from openpype.action import get_errored_plugins_from_data +from openpype.lib import version_up from avalon import harmony diff --git a/pype/hosts/harmony/plugins/publish/validate_audio.py b/openpype/hosts/harmony/plugins/publish/validate_audio.py similarity index 100% rename from pype/hosts/harmony/plugins/publish/validate_audio.py rename to openpype/hosts/harmony/plugins/publish/validate_audio.py diff --git a/pype/hosts/harmony/plugins/publish/validate_instances.py b/openpype/hosts/harmony/plugins/publish/validate_instances.py similarity index 95% rename from pype/hosts/harmony/plugins/publish/validate_instances.py rename to openpype/hosts/harmony/plugins/publish/validate_instances.py index 238f8d1038..78073a1978 100644 --- a/pype/hosts/harmony/plugins/publish/validate_instances.py +++ b/openpype/hosts/harmony/plugins/publish/validate_instances.py @@ -1,7 +1,7 @@ import os import pyblish.api -import pype.api +import openpype.api from avalon import harmony @@ -36,7 +36,7 @@ class ValidateInstance(pyblish.api.InstancePlugin): label = "Validate Instance" hosts = ["harmony"] actions = [ValidateInstanceRepair] - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder def process(self, instance): instance_asset = instance.data["asset"] diff --git a/pype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py similarity index 95% rename from pype/hosts/harmony/plugins/publish/validate_scene_settings.py rename to openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index 9b8a6183df..b3e7f49268 100644 --- a/pype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -6,7 +6,7 @@ import json import pyblish.api from avalon import harmony -import pype.hosts.harmony +import openpype.hosts.harmony class ValidateSceneSettingsRepair(pyblish.api.Action): @@ -18,12 +18,12 @@ class ValidateSceneSettingsRepair(pyblish.api.Action): def process(self, context, plugin): """Repair action entry point.""" - expected = pype.hosts.harmony.api.get_asset_settings() + expected = openpype.hosts.harmony.api.get_asset_settings() asset_settings = _update_frames(dict.copy(expected)) asset_settings["frameStart"] = 1 asset_settings["frameEnd"] = asset_settings["frameEnd"] + \ asset_settings["handleEnd"] - pype.hosts.harmony.api.set_scene_settings(asset_settings) + openpype.hosts.harmony.api.set_scene_settings(asset_settings) if not os.path.exists(context.data["scenePath"]): self.log.info("correcting scene name") scene_dir = os.path.dirname(context.data["currentFile"]) @@ -48,7 +48,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): def process(self, instance): """Plugin entry point.""" - expected_settings = pype.hosts.harmony.api.get_asset_settings() + expected_settings = openpype.hosts.harmony.api.get_asset_settings() self.log.info(expected_settings) expected_settings = _update_frames(dict.copy(expected_settings)) diff --git a/pype/hosts/hiero/__init__.py b/openpype/hosts/hiero/__init__.py similarity index 100% rename from pype/hosts/hiero/__init__.py rename to openpype/hosts/hiero/__init__.py diff --git a/pype/hosts/hiero/api/__init__.py b/openpype/hosts/hiero/api/__init__.py similarity index 100% rename from pype/hosts/hiero/api/__init__.py rename to openpype/hosts/hiero/api/__init__.py diff --git a/pype/hosts/hiero/api/events.py b/openpype/hosts/hiero/api/events.py similarity index 99% rename from pype/hosts/hiero/api/events.py rename to openpype/hosts/hiero/api/events.py index 918912ce2e..c02e3e2ac4 100644 --- a/pype/hosts/hiero/api/events.py +++ b/openpype/hosts/hiero/api/events.py @@ -1,7 +1,7 @@ import os import hiero.core.events import avalon.api as avalon -from pype.api import Logger +from openpype.api import Logger from .lib import sync_avalon_data_to_workfile, launch_workfiles_app from .tags import add_tags_to_workfile from .menu import update_menu_task_label diff --git a/pype/hosts/hiero/api/lib.py b/openpype/hosts/hiero/api/lib.py similarity index 99% rename from pype/hosts/hiero/api/lib.py rename to openpype/hosts/hiero/api/lib.py index 14760a3137..b74e70cae3 100644 --- a/pype/hosts/hiero/api/lib.py +++ b/openpype/hosts/hiero/api/lib.py @@ -9,7 +9,7 @@ import hiero import avalon.api as avalon import avalon.io from avalon.vendor.Qt import QtWidgets -from pype.api import (Logger, Anatomy, config) +from openpype.api import (Logger, Anatomy, config) from . import tags import shutil from compiler.ast import flatten @@ -241,7 +241,7 @@ def set_track_item_pype_tag(track_item, data=None): tag_data = { "editable": "0", "note": "Pype data holder", - "icon": "pype_icon.png", + "icon": "openpype_icon.png", "metadata": {k: v for k, v in data.items()} } # get available pype tag if any diff --git a/pype/hosts/hiero/api/menu.py b/openpype/hosts/hiero/api/menu.py similarity index 99% rename from pype/hosts/hiero/api/menu.py rename to openpype/hosts/hiero/api/menu.py index f32bea07e1..9ccf5e39d1 100644 --- a/pype/hosts/hiero/api/menu.py +++ b/openpype/hosts/hiero/api/menu.py @@ -1,7 +1,7 @@ import os import sys import hiero.core -from pype.api import Logger +from openpype.api import Logger from avalon.api import Session from hiero.ui import findMenuAction diff --git a/pype/hosts/hiero/api/pipeline.py b/openpype/hosts/hiero/api/pipeline.py similarity index 98% rename from pype/hosts/hiero/api/pipeline.py rename to openpype/hosts/hiero/api/pipeline.py index 26777fa252..ab7e2bdabf 100644 --- a/pype/hosts/hiero/api/pipeline.py +++ b/openpype/hosts/hiero/api/pipeline.py @@ -12,7 +12,7 @@ from avalon.pipeline import AVALON_CONTAINER_ID from avalon import api as avalon from avalon import schema from pyblish import api as pyblish -from pype.api import Logger +from openpype.api import Logger from . import lib, menu, events log = Logger().get_logger(__name__) @@ -110,7 +110,7 @@ def containerise(track_item, """ data_imprint = OrderedDict({ - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": AVALON_CONTAINER_ID, "name": str(name), "namespace": str(namespace), @@ -289,7 +289,7 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) - from pype.hosts.hiero.api import ( + from openpype.hosts.hiero.api import ( get_track_item_pype_tag, set_publish_attribute ) diff --git a/pype/hosts/hiero/api/style.css b/openpype/hosts/hiero/api/style.css similarity index 100% rename from pype/hosts/hiero/api/style.css rename to openpype/hosts/hiero/api/style.css diff --git a/pype/hosts/hiero/api/tags.py b/openpype/hosts/hiero/api/tags.py similarity index 99% rename from pype/hosts/hiero/api/tags.py rename to openpype/hosts/hiero/api/tags.py index 63c60a3039..06fa655a2e 100644 --- a/pype/hosts/hiero/api/tags.py +++ b/openpype/hosts/hiero/api/tags.py @@ -2,7 +2,7 @@ import re import os import hiero -from pype.api import Logger +from openpype.api import Logger from avalon import io log = Logger().get_logger(__name__) diff --git a/pype/hosts/hiero/api/workio.py b/openpype/hosts/hiero/api/workio.py similarity index 98% rename from pype/hosts/hiero/api/workio.py rename to openpype/hosts/hiero/api/workio.py index c3505ef1bc..15ffbf84d8 100644 --- a/pype/hosts/hiero/api/workio.py +++ b/openpype/hosts/hiero/api/workio.py @@ -1,7 +1,7 @@ import os import hiero from avalon import api -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/hosts/hiero/plugins/_publish/collect_calculate_retime.py b/openpype/hosts/hiero/plugins/_publish/collect_calculate_retime.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_calculate_retime.py rename to openpype/hosts/hiero/plugins/_publish/collect_calculate_retime.py diff --git a/pype/hosts/hiero/plugins/_publish/collect_framerate.py b/openpype/hosts/hiero/plugins/_publish/collect_framerate.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_framerate.py rename to openpype/hosts/hiero/plugins/_publish/collect_framerate.py diff --git a/pype/hosts/hiero/plugins/_publish/collect_metadata.py b/openpype/hosts/hiero/plugins/_publish/collect_metadata.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_metadata.py rename to openpype/hosts/hiero/plugins/_publish/collect_metadata.py diff --git a/pype/hosts/hiero/plugins/_publish/collect_tag_comments.py b/openpype/hosts/hiero/plugins/_publish/collect_tag_comments.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_tag_comments.py rename to openpype/hosts/hiero/plugins/_publish/collect_tag_comments.py diff --git a/pype/hosts/hiero/plugins/_publish/collect_tag_retime.py b/openpype/hosts/hiero/plugins/_publish/collect_tag_retime.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_tag_retime.py rename to openpype/hosts/hiero/plugins/_publish/collect_tag_retime.py diff --git a/pype/hosts/hiero/plugins/_publish/collect_timecodes.py b/openpype/hosts/hiero/plugins/_publish/collect_timecodes.py similarity index 100% rename from pype/hosts/hiero/plugins/_publish/collect_timecodes.py rename to openpype/hosts/hiero/plugins/_publish/collect_timecodes.py diff --git a/pype/hosts/hiero/plugins/create/create_shot_clip.py b/openpype/hosts/hiero/plugins/create/create_shot_clip.py similarity index 98% rename from pype/hosts/hiero/plugins/create/create_shot_clip.py rename to openpype/hosts/hiero/plugins/create/create_shot_clip.py index 268f84b127..07b7a62b2a 100644 --- a/pype/hosts/hiero/plugins/create/create_shot_clip.py +++ b/openpype/hosts/hiero/plugins/create/create_shot_clip.py @@ -1,5 +1,5 @@ -import pype.hosts.hiero.api as phiero -# from pype.hosts.hiero.api import plugin, lib +import openpype.hosts.hiero.api as phiero +# from openpype.hosts.hiero.api import plugin, lib # reload(lib) # reload(plugin) # reload(phiero) @@ -120,9 +120,9 @@ class CreateShotClip(phiero.Creator): "vSyncTrack": { "value": gui_tracks, # noqa "type": "QComboBox", - "label": "Hero track", + "label": "Master track", "target": "ui", - "toolTip": "Select driving track name which should be hero for all others", # noqa + "toolTip": "Select driving track name which should be mastering all others", # noqa "order": 1} } }, diff --git a/pype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py similarity index 98% rename from pype/hosts/hiero/plugins/load/load_clip.py rename to openpype/hosts/hiero/plugins/load/load_clip.py index d9f6bbf3c8..4eadf28956 100644 --- a/pype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -1,6 +1,6 @@ from avalon import io, api -import pype.hosts.hiero.api as phiero -# from pype.hosts.hiero.api import plugin, lib +import openpype.hosts.hiero.api as phiero +# from openpype.hosts.hiero.api import plugin, lib # reload(lib) # reload(plugin) # reload(phiero) diff --git a/pype/hosts/hiero/plugins/publish/collect_assetbuilds.py b/openpype/hosts/hiero/plugins/publish/collect_assetbuilds.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_assetbuilds.py rename to openpype/hosts/hiero/plugins/publish/collect_assetbuilds.py diff --git a/pype/hosts/hiero/plugins/publish/collect_clip_resolution.py b/openpype/hosts/hiero/plugins/publish/collect_clip_resolution.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_clip_resolution.py rename to openpype/hosts/hiero/plugins/publish/collect_clip_resolution.py diff --git a/pype/hosts/hiero/plugins/publish/collect_frame_ranges.py b/openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_frame_ranges.py rename to openpype/hosts/hiero/plugins/publish/collect_frame_ranges.py diff --git a/pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py b/openpype/hosts/hiero/plugins/publish/collect_hierarchy_context.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py rename to openpype/hosts/hiero/plugins/publish/collect_hierarchy_context.py diff --git a/pype/hosts/hiero/plugins/publish/collect_host_version.py b/openpype/hosts/hiero/plugins/publish/collect_host_version.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_host_version.py rename to openpype/hosts/hiero/plugins/publish/collect_host_version.py diff --git a/pype/hosts/hiero/plugins/publish/collect_plates.py b/openpype/hosts/hiero/plugins/publish/collect_plates.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_plates.py rename to openpype/hosts/hiero/plugins/publish/collect_plates.py diff --git a/openpype/hosts/hiero/plugins/publish/collect_review.py b/openpype/hosts/hiero/plugins/publish/collect_review.py new file mode 100644 index 0000000000..a0ab00b355 --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish/collect_review.py @@ -0,0 +1,261 @@ +from pyblish import api +import os +import clique +from openpype.hosts.hiero.api import ( + is_overlapping, get_sequence_pattern_and_padding) + + +class CollectReview(api.InstancePlugin): + """Collect review representation. + """ + + # Run just before CollectSubsets + order = api.CollectorOrder + 0.1022 + label = "Collect Review" + hosts = ["hiero"] + families = ["review"] + + def get_review_item(self, instance): + """ + Get review clip track item from review track name + + Args: + instance (obj): publishing instance + + Returns: + hiero.core.TrackItem: corresponding track item + + Raises: + Exception: description + + """ + review_track = instance.data.get("review") + video_tracks = instance.context.data["videoTracks"] + for track in video_tracks: + if review_track not in track.name(): + continue + for item in track.items(): + self.log.debug(item) + if is_overlapping(item, self.main_clip): + self.log.debug("Winner is: {}".format(item)) + break + + # validate the clip is fully converted with review clip + assert is_overlapping( + item, self.main_clip, strict=True), ( + "Review clip not cowering fully " + "the clip `{}`").format(self.main_clip.name()) + + return item + + def process(self, instance): + tags = ["review", "ftrackreview"] + + # get reviewable item from `review` instance.data attribute + self.main_clip = instance.data.get("item") + self.rw_clip = self.get_review_item(instance) + + # let user know there is missing review clip and convert instance + # back as not reviewable + assert self.rw_clip, "Missing reviewable clip for '{}'".format( + self.main_clip.name() + ) + + # add to representations + if not instance.data.get("representations"): + instance.data["representations"] = list() + + # get review media main info + rw_source = self.rw_clip.source().mediaSource() + rw_source_duration = int(rw_source.duration()) + self.rw_source_path = rw_source.firstpath() + rw_source_file_info = rw_source.fileinfos().pop() + + # define if review media is sequence + is_sequence = bool(not rw_source.singleFile()) + self.log.debug("is_sequence: {}".format(is_sequence)) + + # get handles + handle_start = instance.data["handleStart"] + handle_end = instance.data["handleEnd"] + + # review timeline and source frame ranges + rw_clip_in = int(self.rw_clip.timelineIn()) + rw_clip_out = int(self.rw_clip.timelineOut()) + self.rw_clip_source_in = int(self.rw_clip.sourceIn()) + self.rw_clip_source_out = int(self.rw_clip.sourceOut()) + rw_source_first = int(rw_source_file_info.startFrame()) + + # calculate delivery source_in and source_out + # main_clip_timeline_in - review_item_timeline_in + 1 + main_clip_in = self.main_clip.timelineIn() + main_clip_out = self.main_clip.timelineOut() + + source_in_diff = main_clip_in - rw_clip_in + source_out_diff = main_clip_out - rw_clip_out + + if source_in_diff: + self.rw_clip_source_in += source_in_diff + if source_out_diff: + self.rw_clip_source_out += source_out_diff + + # review clip durations + rw_clip_duration = ( + self.rw_clip_source_out - self.rw_clip_source_in) + 1 + rw_clip_duration_h = rw_clip_duration + ( + handle_start + handle_end) + + # add created data to review item data + instance.data["reviewItemData"] = { + "mediaDuration": rw_source_duration + } + + file_dir = os.path.dirname(self.rw_source_path) + file = os.path.basename(self.rw_source_path) + ext = os.path.splitext(file)[-1] + + # detect if sequence + if not is_sequence: + # is video file + files = file + else: + files = list() + spliter, padding = get_sequence_pattern_and_padding(file) + self.log.debug("_ spliter, padding: {}, {}".format( + spliter, padding)) + base_name = file.split(spliter)[0] + + # define collection and calculate frame range + collection = clique.Collection(base_name, ext, padding, set(range( + int(rw_source_first + int( + self.rw_clip_source_in - handle_start)), + int(rw_source_first + int( + self.rw_clip_source_out + handle_end) + 1)))) + self.log.debug("_ collection: {}".format(collection)) + + real_files = os.listdir(file_dir) + self.log.debug("_ real_files: {}".format(real_files)) + + # collect frames to repre files list + for item in collection: + if item not in real_files: + self.log.debug("_ item: {}".format(item)) + continue + files.append(item) + + # add prep tag + tags.extend(["prep", "delete"]) + + # change label + instance.data["label"] = "{0} - ({1})".format( + instance.data["label"], ext + ) + + self.log.debug("Instance review: {}".format(instance.data["name"])) + + # adding representation for review mov + representation = { + "files": files, + "stagingDir": file_dir, + "frameStart": rw_source_first + self.rw_clip_source_in, + "frameEnd": rw_source_first + self.rw_clip_source_out, + "frameStartFtrack": int( + self.rw_clip_source_in - handle_start), + "frameEndFtrack": int(self.rw_clip_source_out + handle_end), + "step": 1, + "fps": instance.data["fps"], + "name": "review", + "tags": tags, + "ext": ext[1:] + } + + if rw_source_duration > rw_clip_duration_h: + self.log.debug("Media duration higher: {}".format( + (rw_source_duration - rw_clip_duration_h))) + representation.update({ + "frameStart": rw_source_first + int( + self.rw_clip_source_in - handle_start), + "frameEnd": rw_source_first + int( + self.rw_clip_source_out + handle_end), + "tags": ["_cut-bigger", "prep", "delete"] + }) + elif rw_source_duration < rw_clip_duration_h: + self.log.debug("Media duration higher: {}".format( + (rw_source_duration - rw_clip_duration_h))) + representation.update({ + "frameStart": rw_source_first + int( + self.rw_clip_source_in - handle_start), + "frameEnd": rw_source_first + int( + self.rw_clip_source_out + handle_end), + "tags": ["prep", "delete"] + }) + + instance.data["representations"].append(representation) + + self.create_thumbnail(instance) + + self.log.debug( + "Added representations: {}".format( + instance.data["representations"])) + + def create_thumbnail(self, instance): + source_file = os.path.basename(self.rw_source_path) + spliter, padding = get_sequence_pattern_and_padding(source_file) + + if spliter: + head, ext = source_file.split(spliter) + else: + head, ext = os.path.splitext(source_file) + + # staging dir creation + staging_dir = os.path.dirname( + self.rw_source_path) + + # get thumbnail frame from the middle + thumb_frame = int(self.rw_clip_source_in + ( + (self.rw_clip_source_out - self.rw_clip_source_in) / 2)) + + thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png") + thumb_path = os.path.join(staging_dir, thumb_file) + + thumbnail = self.rw_clip.thumbnail(thumb_frame).save( + thumb_path, + format='png' + ) + self.log.debug( + "__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + + self.log.debug("__ thumbnail: {}".format(thumbnail)) + thumb_representation = { + 'files': thumb_file, + 'stagingDir': staging_dir, + 'name': "thumbnail", + 'thumbnail': True, + 'ext': "png" + } + instance.data["representations"].append( + thumb_representation) + + def version_data(self, instance): + transfer_data = [ + "handleStart", "handleEnd", "sourceIn", "sourceOut", + "frameStart", "frameEnd", "sourceInH", "sourceOutH", + "clipIn", "clipOut", "clipInH", "clipOutH", "asset", + "track" + ] + + version_data = dict() + # pass data to version + version_data.update({k: instance.data[k] for k in transfer_data}) + + if 'version' in instance.data: + version_data["version"] = instance.data["version"] + + # add to data of representation + version_data.update({ + "colorspace": self.rw_clip.sourceMediaColourTransform(), + "families": instance.data["families"], + "subset": instance.data["subset"], + "fps": instance.data["fps"] + }) + instance.data["versionData"] = version_data diff --git a/pype/hosts/hiero/plugins/publish/collect_tag_tasks.py b/openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/collect_tag_tasks.py rename to openpype/hosts/hiero/plugins/publish/collect_tag_tasks.py diff --git a/pype/hosts/hiero/plugins/publish/extract_audio.py b/openpype/hosts/hiero/plugins/publish/extract_audio.py similarity index 96% rename from pype/hosts/hiero/plugins/publish/extract_audio.py rename to openpype/hosts/hiero/plugins/publish/extract_audio.py index 690e9c5b1e..6d9abb58e2 100644 --- a/pype/hosts/hiero/plugins/publish/extract_audio.py +++ b/openpype/hosts/hiero/plugins/publish/extract_audio.py @@ -1,10 +1,10 @@ import os from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles import pyblish -import pype +import openpype -class ExtractAudioFile(pype.api.Extractor): +class ExtractAudioFile(openpype.api.Extractor): """Extracts audio subset file from all active timeline audio tracks""" order = pyblish.api.ExtractorOrder diff --git a/pype/hosts/hiero/plugins/publish/extract_clip_effects.py b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py similarity index 96% rename from pype/hosts/hiero/plugins/publish/extract_clip_effects.py rename to openpype/hosts/hiero/plugins/publish/extract_clip_effects.py index 308b33ff3a..d2ac7f4786 100644 --- a/pype/hosts/hiero/plugins/publish/extract_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py @@ -1,11 +1,11 @@ -# from pype import plugins +# from openpype import plugins import os import json import pyblish.api -import pype +import openpype -class ExtractClipEffects(pype.api.Extractor): +class ExtractClipEffects(openpype.api.Extractor): """Extract clip effects instances.""" order = pyblish.api.ExtractorOrder diff --git a/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py b/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py new file mode 100644 index 0000000000..5456ddc3c4 --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish/extract_review_preparation.py @@ -0,0 +1,334 @@ +import os +import sys +import six +import errno +from pyblish import api +import openpype +import clique +from avalon.vendor import filelink + + +class ExtractReviewPreparation(openpype.api.Extractor): + """Cut up clips from long video file""" + + order = api.ExtractorOrder + label = "Extract Review Preparation" + hosts = ["hiero"] + families = ["review"] + + # presets + tags_addition = [] + + def process(self, instance): + inst_data = instance.data + asset = inst_data["asset"] + review_item_data = instance.data.get("reviewItemData") + + # get representation and loop them + representations = inst_data["representations"] + + # get resolution default + resolution_width = inst_data["resolutionWidth"] + resolution_height = inst_data["resolutionHeight"] + + # frame range data + media_duration = review_item_data["mediaDuration"] + + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") + ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe") + + # filter out mov and img sequences + representations_new = representations[:] + for repre in representations: + input_args = list() + output_args = list() + + tags = repre.get("tags", []) + + # check if supported tags are in representation for activation + filter_tag = False + for tag in ["_cut-bigger", "prep"]: + if tag in tags: + filter_tag = True + break + if not filter_tag: + continue + + self.log.debug("__ repre: {}".format(repre)) + + files = repre.get("files") + staging_dir = repre.get("stagingDir") + fps = repre.get("fps") + ext = repre.get("ext") + + # make paths + full_output_dir = os.path.join( + staging_dir, "cuts") + + if isinstance(files, list): + new_files = list() + + # frame range delivery included handles + frame_start = ( + inst_data["frameStart"] - inst_data["handleStart"]) + frame_end = ( + inst_data["frameEnd"] + inst_data["handleEnd"]) + self.log.debug("_ frame_start: {}".format(frame_start)) + self.log.debug("_ frame_end: {}".format(frame_end)) + + # make collection from input files list + collections, remainder = clique.assemble(files) + collection = collections.pop() + self.log.debug("_ collection: {}".format(collection)) + + # name components + head = collection.format("{head}") + padding = collection.format("{padding}") + tail = collection.format("{tail}") + self.log.debug("_ head: {}".format(head)) + self.log.debug("_ padding: {}".format(padding)) + self.log.debug("_ tail: {}".format(tail)) + + # make destination file with instance data + # frame start and end range + index = 0 + for image in collection: + dst_file_num = frame_start + index + dst_file_name = head + str(padding % dst_file_num) + tail + src = os.path.join(staging_dir, image) + dst = os.path.join(full_output_dir, dst_file_name) + self.log.info("Creating temp hardlinks: {}".format(dst)) + self.hardlink_file(src, dst) + new_files.append(dst_file_name) + index += 1 + + self.log.debug("_ new_files: {}".format(new_files)) + + else: + # ffmpeg when single file + new_files = "{}_{}".format(asset, files) + + # frame range + frame_start = repre.get("frameStart") + frame_end = repre.get("frameEnd") + + full_input_path = os.path.join( + staging_dir, files) + + os.path.isdir(full_output_dir) or os.makedirs(full_output_dir) + + full_output_path = os.path.join( + full_output_dir, new_files) + + self.log.debug( + "__ full_input_path: {}".format(full_input_path)) + self.log.debug( + "__ full_output_path: {}".format(full_output_path)) + + # check if audio stream is in input video file + ffprob_cmd = ( + "\"{ffprobe_path}\" -i \"{full_input_path}\" -show_streams" + " -select_streams a -loglevel error" + ).format(**locals()) + + self.log.debug("ffprob_cmd: {}".format(ffprob_cmd)) + audio_check_output = openpype.api.subprocess(ffprob_cmd) + self.log.debug( + "audio_check_output: {}".format(audio_check_output)) + + # Fix one frame difference + """ TODO: this is just work-around for issue: + https://github.com/pypeclub/pype/issues/659 + """ + frame_duration_extend = 1 + if audio_check_output and ("audio" in inst_data["families"]): + frame_duration_extend = 0 + + # translate frame to sec + start_sec = float(frame_start) / fps + duration_sec = float( + (frame_end - frame_start) + frame_duration_extend) / fps + + empty_add = None + + # check if not missing frames at start + if (start_sec < 0) or (media_duration < frame_end): + # for later swithing off `-c:v copy` output arg + empty_add = True + + # init empty variables + video_empty_start = video_layer_start = "" + audio_empty_start = audio_layer_start = "" + video_empty_end = video_layer_end = "" + audio_empty_end = audio_layer_end = "" + audio_input = audio_output = "" + v_inp_idx = 0 + concat_n = 1 + + # try to get video native resolution data + try: + resolution_output = openpype.api.subprocess(( + "\"{ffprobe_path}\" -i \"{full_input_path}\"" + " -v error " + "-select_streams v:0 -show_entries " + "stream=width,height -of csv=s=x:p=0" + ).format(**locals())) + + x, y = resolution_output.split("x") + resolution_width = int(x) + resolution_height = int(y) + except Exception as _ex: + self.log.warning( + "Video native resolution is untracable: {}".format( + _ex)) + + if audio_check_output: + # adding input for empty audio + input_args.append("-f lavfi -i anullsrc") + + # define audio empty concat variables + audio_input = "[1:a]" + audio_output = ":a=1" + v_inp_idx = 1 + + # adding input for video black frame + input_args.append(( + "-f lavfi -i \"color=c=black:" + "s={resolution_width}x{resolution_height}:r={fps}\"" + ).format(**locals())) + + if (start_sec < 0): + # recalculate input video timing + empty_start_dur = abs(start_sec) + start_sec = 0 + duration_sec = float(frame_end - ( + frame_start + (empty_start_dur * fps)) + 1) / fps + + # define starting empty video concat variables + video_empty_start = ( + "[{v_inp_idx}]trim=duration={empty_start_dur}[gv0];" # noqa + ).format(**locals()) + video_layer_start = "[gv0]" + + if audio_check_output: + # define starting empty audio concat variables + audio_empty_start = ( + "[0]atrim=duration={empty_start_dur}[ga0];" + ).format(**locals()) + audio_layer_start = "[ga0]" + + # alter concat number of clips + concat_n += 1 + + # check if not missing frames at the end + if (media_duration < frame_end): + # recalculate timing + empty_end_dur = float( + frame_end - media_duration + 1) / fps + duration_sec = float( + media_duration - frame_start) / fps + + # define ending empty video concat variables + video_empty_end = ( + "[{v_inp_idx}]trim=duration={empty_end_dur}[gv1];" + ).format(**locals()) + video_layer_end = "[gv1]" + + if audio_check_output: + # define ending empty audio concat variables + audio_empty_end = ( + "[0]atrim=duration={empty_end_dur}[ga1];" + ).format(**locals()) + audio_layer_end = "[ga0]" + + # alter concat number of clips + concat_n += 1 + + # concatting black frame togather + output_args.append(( + "-filter_complex \"" + "{audio_empty_start}" + "{video_empty_start}" + "{audio_empty_end}" + "{video_empty_end}" + "{video_layer_start}{audio_layer_start}[1:v]{audio_input}" # noqa + "{video_layer_end}{audio_layer_end}" + "concat=n={concat_n}:v=1{audio_output}\"" + ).format(**locals())) + + # append ffmpeg input video clip + input_args.append("-ss {}".format(start_sec)) + input_args.append("-t {}".format(duration_sec)) + input_args.append("-i \"{}\"".format(full_input_path)) + + # add copy audio video codec if only shortening clip + if ("_cut-bigger" in tags) and (not empty_add): + output_args.append("-c:v copy") + + # make sure it is having no frame to frame comprassion + output_args.append("-intra") + + # output filename + output_args.append("-y \"{}\"".format(full_output_path)) + + mov_args = [ + "\"{}\"".format(ffmpeg_path), + " ".join(input_args), + " ".join(output_args) + ] + subprcs_cmd = " ".join(mov_args) + + # run subprocess + self.log.debug("Executing: {}".format(subprcs_cmd)) + output = openpype.api.subprocess(subprcs_cmd) + self.log.debug("Output: {}".format(output)) + + repre_new = { + "files": new_files, + "stagingDir": full_output_dir, + "frameStart": frame_start, + "frameEnd": frame_end, + "frameStartFtrack": frame_start, + "frameEndFtrack": frame_end, + "step": 1, + "fps": fps, + "name": "cut_up_preview", + "tags": [ + "review", "ftrackreview", "delete"] + self.tags_addition, + "ext": ext, + "anatomy_template": "publish" + } + + representations_new.append(repre_new) + + for repre in representations_new: + if ("delete" in repre.get("tags", [])) and ( + "cut_up_preview" not in repre["name"]): + representations_new.remove(repre) + + self.log.debug( + "Representations: {}".format(representations_new)) + instance.data["representations"] = representations_new + + def hardlink_file(self, src, dst): + dirname = os.path.dirname(dst) + + # make sure the destination folder exist + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) + + # create hardlined file + try: + filelink.create(src, dst, filelink.HARDLINK) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + six.reraise(*sys.exc_info()) diff --git a/pype/hosts/hiero/plugins/publish/precollect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/precollect_clip_effects.py rename to openpype/hosts/hiero/plugins/publish/precollect_clip_effects.py diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py new file mode 100644 index 0000000000..bdf007de06 --- /dev/null +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -0,0 +1,221 @@ +from compiler.ast import flatten +from pyblish import api +from openpype.hosts.hiero import api as phiero +import hiero +# from openpype.hosts.hiero.api import lib +# reload(lib) +# reload(phiero) + + +class PreCollectInstances(api.ContextPlugin): + """Collect all Track items selection.""" + + order = api.CollectorOrder - 0.509 + label = "Pre-collect Instances" + hosts = ["hiero"] + + def process(self, context): + track_items = phiero.get_track_items( + selected=True, check_tagged=True, check_enabled=True) + # only return enabled track items + if not track_items: + track_items = phiero.get_track_items( + check_enabled=True, check_tagged=True) + # get sequence and video tracks + sequence = context.data["activeSequence"] + tracks = sequence.videoTracks() + + # add collection to context + tracks_effect_items = self.collect_sub_track_items(tracks) + + context.data["tracksEffectItems"] = tracks_effect_items + + self.log.info( + "Processing enabled track items: {}".format(len(track_items))) + + for _ti in track_items: + data = dict() + clip = _ti.source() + + # get clips subtracks and anotations + annotations = self.clip_annotations(clip) + subtracks = self.clip_subtrack(_ti) + self.log.debug("Annotations: {}".format(annotations)) + self.log.debug(">> Subtracks: {}".format(subtracks)) + + # get pype tag data + tag_parsed_data = phiero.get_track_item_pype_data(_ti) + # self.log.debug(pformat(tag_parsed_data)) + + if not tag_parsed_data: + continue + + if tag_parsed_data.get("id") != "pyblish.avalon.instance": + continue + # add tag data to instance data + data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + asset = tag_parsed_data["asset"] + subset = tag_parsed_data["subset"] + review = tag_parsed_data.get("review") + audio = tag_parsed_data.get("audio") + + # remove audio attribute from data + data.pop("audio") + + # insert family into families + family = tag_parsed_data["family"] + families = [str(f) for f in tag_parsed_data["families"]] + families.insert(0, str(family)) + + track = _ti.parent() + media_source = _ti.source().mediaSource() + source_path = media_source.firstpath() + file_head = media_source.filenameHead() + file_info = media_source.fileinfos().pop() + source_first_frame = int(file_info.startFrame()) + + # apply only for feview and master track instance + if review: + families += ["review", "ftrack"] + + data.update({ + "name": "{} {} {}".format(asset, subset, families), + "asset": asset, + "item": _ti, + "families": families, + + # tags + "tags": _ti.tags(), + + # track item attributes + "track": track.name(), + "trackItem": track, + + # version data + "versionData": { + "colorspace": _ti.sourceMediaColourTransform() + }, + + # source attribute + "source": source_path, + "sourceMedia": media_source, + "sourcePath": source_path, + "sourceFileHead": file_head, + "sourceFirst": source_first_frame, + + # clip's effect + "clipEffectItems": subtracks + }) + + instance = context.create_instance(**data) + + self.log.info("Creating instance: {}".format(instance)) + + if audio: + a_data = dict() + + # add tag data to instance data + a_data.update({ + k: v for k, v in tag_parsed_data.items() + if k not in ("id", "applieswhole", "label") + }) + + # create main attributes + subset = "audioMain" + family = "audio" + families = ["clip", "ftrack"] + families.insert(0, str(family)) + + name = "{} {} {}".format(asset, subset, families) + + a_data.update({ + "name": name, + "subset": subset, + "asset": asset, + "family": family, + "families": families, + "item": _ti, + + # tags + "tags": _ti.tags(), + }) + + a_instance = context.create_instance(**a_data) + self.log.info("Creating audio instance: {}".format(a_instance)) + + @staticmethod + def clip_annotations(clip): + """ + Returns list of Clip's hiero.core.Annotation + """ + annotations = [] + subTrackItems = flatten(clip.subTrackItems()) + annotations += [item for item in subTrackItems if isinstance( + item, hiero.core.Annotation)] + return annotations + + @staticmethod + def clip_subtrack(clip): + """ + Returns list of Clip's hiero.core.SubTrackItem + """ + subtracks = [] + subTrackItems = flatten(clip.parent().subTrackItems()) + for item in subTrackItems: + # avoid all anotation + if isinstance(item, hiero.core.Annotation): + continue + # # avoid all not anaibled + if not item.isEnabled(): + continue + subtracks.append(item) + return subtracks + + @staticmethod + def collect_sub_track_items(tracks): + """ + Returns dictionary with track index as key and list of subtracks + """ + # collect all subtrack items + sub_track_items = dict() + for track in tracks: + items = track.items() + + # skip if no clips on track > need track with effect only + if items: + continue + + # skip all disabled tracks + if not track.isEnabled(): + continue + + track_index = track.trackIndex() + _sub_track_items = flatten(track.subTrackItems()) + + # continue only if any subtrack items are collected + if len(_sub_track_items) < 1: + continue + + enabled_sti = list() + # loop all found subtrack items and check if they are enabled + for _sti in _sub_track_items: + # checking if not enabled + if not _sti.isEnabled(): + continue + if isinstance(_sti, hiero.core.Annotation): + continue + # collect the subtrack item + enabled_sti.append(_sti) + + # continue only if any subtrack items are collected + if len(enabled_sti) < 1: + continue + + # add collection of subtrackitems to dict + sub_track_items[track_index] = enabled_sti + + return sub_track_items diff --git a/pype/hosts/hiero/plugins/publish/precollect_workfile.py b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py similarity index 98% rename from pype/hosts/hiero/plugins/publish/precollect_workfile.py rename to openpype/hosts/hiero/plugins/publish/precollect_workfile.py index 1b22371943..ef7d07421b 100644 --- a/pype/hosts/hiero/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_workfile.py @@ -1,6 +1,6 @@ import os import pyblish.api -from pype.hosts.hiero import api as phiero +from openpype.hosts.hiero import api as phiero from avalon import api as avalon diff --git a/pype/hosts/hiero/plugins/publish/validate_audio.py b/openpype/hosts/hiero/plugins/publish/validate_audio.py similarity index 92% rename from pype/hosts/hiero/plugins/publish/validate_audio.py rename to openpype/hosts/hiero/plugins/publish/validate_audio.py index 5decc86304..0b2a94dc68 100644 --- a/pype/hosts/hiero/plugins/publish/validate_audio.py +++ b/openpype/hosts/hiero/plugins/publish/validate_audio.py @@ -1,5 +1,5 @@ import pyblish -from pype.hosts.hiero.api import is_overlapping +from openpype.hosts.hiero.api import is_overlapping class ValidateAudioFile(pyblish.api.InstancePlugin): diff --git a/pype/hosts/hiero/plugins/publish/validate_hierarchy.py b/openpype/hosts/hiero/plugins/publish/validate_hierarchy.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/validate_hierarchy.py rename to openpype/hosts/hiero/plugins/publish/validate_hierarchy.py diff --git a/pype/hosts/hiero/plugins/publish/validate_names.py b/openpype/hosts/hiero/plugins/publish/validate_names.py similarity index 100% rename from pype/hosts/hiero/plugins/publish/validate_names.py rename to openpype/hosts/hiero/plugins/publish/validate_names.py diff --git a/pype/hosts/hiero/plugins/publish/version_up_workfile.py b/openpype/hosts/hiero/plugins/publish/version_up_workfile.py similarity index 94% rename from pype/hosts/hiero/plugins/publish/version_up_workfile.py rename to openpype/hosts/hiero/plugins/publish/version_up_workfile.py index 893d3789eb..ae03513d78 100644 --- a/pype/hosts/hiero/plugins/publish/version_up_workfile.py +++ b/openpype/hosts/hiero/plugins/publish/version_up_workfile.py @@ -1,5 +1,5 @@ from pyblish import api -import pype.api as pype +import openpype.api as pype class VersionUpWorkfile(api.ContextPlugin): diff --git a/pype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox b/openpype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox similarity index 100% rename from pype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox rename to openpype/hosts/hiero/startup/HieroPlayer/PlayerPresets.hrox diff --git a/pype/hosts/hiero/startup/Icons/1_add_handles_end.png b/openpype/hosts/hiero/startup/Icons/1_add_handles_end.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/1_add_handles_end.png rename to openpype/hosts/hiero/startup/Icons/1_add_handles_end.png diff --git a/pype/hosts/hiero/startup/Icons/2_add_handles.png b/openpype/hosts/hiero/startup/Icons/2_add_handles.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/2_add_handles.png rename to openpype/hosts/hiero/startup/Icons/2_add_handles.png diff --git a/pype/hosts/hiero/startup/Icons/3D.png b/openpype/hosts/hiero/startup/Icons/3D.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/3D.png rename to openpype/hosts/hiero/startup/Icons/3D.png diff --git a/pype/hosts/hiero/startup/Icons/3_add_handles_start.png b/openpype/hosts/hiero/startup/Icons/3_add_handles_start.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/3_add_handles_start.png rename to openpype/hosts/hiero/startup/Icons/3_add_handles_start.png diff --git a/pype/hosts/hiero/startup/Icons/4_2D.png b/openpype/hosts/hiero/startup/Icons/4_2D.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/4_2D.png rename to openpype/hosts/hiero/startup/Icons/4_2D.png diff --git a/pype/hosts/hiero/startup/Icons/edit.png b/openpype/hosts/hiero/startup/Icons/edit.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/edit.png rename to openpype/hosts/hiero/startup/Icons/edit.png diff --git a/pype/hosts/hiero/startup/Icons/fusion.png b/openpype/hosts/hiero/startup/Icons/fusion.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/fusion.png rename to openpype/hosts/hiero/startup/Icons/fusion.png diff --git a/pype/hosts/hiero/startup/Icons/hierarchy.png b/openpype/hosts/hiero/startup/Icons/hierarchy.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/hierarchy.png rename to openpype/hosts/hiero/startup/Icons/hierarchy.png diff --git a/pype/hosts/hiero/startup/Icons/houdini.png b/openpype/hosts/hiero/startup/Icons/houdini.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/houdini.png rename to openpype/hosts/hiero/startup/Icons/houdini.png diff --git a/pype/hosts/hiero/startup/Icons/layers.psd b/openpype/hosts/hiero/startup/Icons/layers.psd similarity index 100% rename from pype/hosts/hiero/startup/Icons/layers.psd rename to openpype/hosts/hiero/startup/Icons/layers.psd diff --git a/pype/hosts/hiero/startup/Icons/lense.png b/openpype/hosts/hiero/startup/Icons/lense.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/lense.png rename to openpype/hosts/hiero/startup/Icons/lense.png diff --git a/pype/hosts/hiero/startup/Icons/lense1.png b/openpype/hosts/hiero/startup/Icons/lense1.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/lense1.png rename to openpype/hosts/hiero/startup/Icons/lense1.png diff --git a/pype/hosts/hiero/startup/Icons/maya.png b/openpype/hosts/hiero/startup/Icons/maya.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/maya.png rename to openpype/hosts/hiero/startup/Icons/maya.png diff --git a/pype/hosts/hiero/startup/Icons/nuke.png b/openpype/hosts/hiero/startup/Icons/nuke.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/nuke.png rename to openpype/hosts/hiero/startup/Icons/nuke.png diff --git a/pype/hosts/hiero/startup/Icons/pype_icon.png b/openpype/hosts/hiero/startup/Icons/pype_icon.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/pype_icon.png rename to openpype/hosts/hiero/startup/Icons/pype_icon.png diff --git a/pype/hosts/hiero/startup/Icons/resolution.png b/openpype/hosts/hiero/startup/Icons/resolution.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/resolution.png rename to openpype/hosts/hiero/startup/Icons/resolution.png diff --git a/pype/hosts/hiero/startup/Icons/resolution.psd b/openpype/hosts/hiero/startup/Icons/resolution.psd similarity index 100% rename from pype/hosts/hiero/startup/Icons/resolution.psd rename to openpype/hosts/hiero/startup/Icons/resolution.psd diff --git a/pype/hosts/hiero/startup/Icons/retiming.png b/openpype/hosts/hiero/startup/Icons/retiming.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/retiming.png rename to openpype/hosts/hiero/startup/Icons/retiming.png diff --git a/pype/hosts/hiero/startup/Icons/retiming.psd b/openpype/hosts/hiero/startup/Icons/retiming.psd similarity index 100% rename from pype/hosts/hiero/startup/Icons/retiming.psd rename to openpype/hosts/hiero/startup/Icons/retiming.psd diff --git a/pype/hosts/hiero/startup/Icons/review.png b/openpype/hosts/hiero/startup/Icons/review.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/review.png rename to openpype/hosts/hiero/startup/Icons/review.png diff --git a/pype/hosts/hiero/startup/Icons/review.psd b/openpype/hosts/hiero/startup/Icons/review.psd similarity index 100% rename from pype/hosts/hiero/startup/Icons/review.psd rename to openpype/hosts/hiero/startup/Icons/review.psd diff --git a/pype/hosts/hiero/startup/Icons/volume.png b/openpype/hosts/hiero/startup/Icons/volume.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/volume.png rename to openpype/hosts/hiero/startup/Icons/volume.png diff --git a/pype/hosts/hiero/startup/Icons/z_layer_bg.png b/openpype/hosts/hiero/startup/Icons/z_layer_bg.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/z_layer_bg.png rename to openpype/hosts/hiero/startup/Icons/z_layer_bg.png diff --git a/pype/hosts/hiero/startup/Icons/z_layer_fg.png b/openpype/hosts/hiero/startup/Icons/z_layer_fg.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/z_layer_fg.png rename to openpype/hosts/hiero/startup/Icons/z_layer_fg.png diff --git a/pype/hosts/hiero/startup/Icons/z_layer_main.png b/openpype/hosts/hiero/startup/Icons/z_layer_main.png similarity index 100% rename from pype/hosts/hiero/startup/Icons/z_layer_main.png rename to openpype/hosts/hiero/startup/Icons/z_layer_main.png diff --git a/pype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py b/openpype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py rename to openpype/hosts/hiero/startup/Python/Startup/SpreadsheetExport.py diff --git a/pype/hosts/hiero/startup/Python/Startup/Startup.py b/openpype/hosts/hiero/startup/Python/Startup/Startup.py similarity index 79% rename from pype/hosts/hiero/startup/Python/Startup/Startup.py rename to openpype/hosts/hiero/startup/Python/Startup/Startup.py index 94b2d53f9f..8de2dc2d11 100644 --- a/pype/hosts/hiero/startup/Python/Startup/Startup.py +++ b/openpype/hosts/hiero/startup/Python/Startup/Startup.py @@ -2,7 +2,7 @@ import traceback # activate hiero from pype import avalon.api -import pype.hosts.hiero.api as phiero +import openpype.hosts.hiero.api as phiero avalon.api.install(phiero) try: @@ -15,5 +15,5 @@ except ImportError as e: else: # Setup integration - import pype.hosts.hiero.api as phiero + import openpype.hosts.hiero.api as phiero phiero.lib.setup() diff --git a/pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py rename to openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportTask.py diff --git a/pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py rename to openpype/hosts/hiero/startup/Python/Startup/otioexporter/OTIOExportUI.py diff --git a/pype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py b/openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py rename to openpype/hosts/hiero/startup/Python/Startup/otioexporter/__init__.py diff --git a/pype/hosts/hiero/startup/Python/Startup/project_helpers.py b/openpype/hosts/hiero/startup/Python/Startup/project_helpers.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/project_helpers.py rename to openpype/hosts/hiero/startup/Python/Startup/project_helpers.py diff --git a/pype/hosts/hiero/startup/Python/Startup/selection_tracker.py b/openpype/hosts/hiero/startup/Python/Startup/selection_tracker.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/selection_tracker.py rename to openpype/hosts/hiero/startup/Python/Startup/selection_tracker.py diff --git a/pype/hosts/hiero/startup/Python/Startup/setFrameRate.py b/openpype/hosts/hiero/startup/Python/Startup/setFrameRate.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/setFrameRate.py rename to openpype/hosts/hiero/startup/Python/Startup/setFrameRate.py diff --git a/pype/hosts/hiero/startup/Python/Startup/version_everywhere.py b/openpype/hosts/hiero/startup/Python/Startup/version_everywhere.py similarity index 100% rename from pype/hosts/hiero/startup/Python/Startup/version_everywhere.py rename to openpype/hosts/hiero/startup/Python/Startup/version_everywhere.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py b/openpype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py rename to openpype/hosts/hiero/startup/Python/StartupUI/PimpMySpreadsheet.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/Purge.py b/openpype/hosts/hiero/startup/Python/StartupUI/Purge.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/Purge.py rename to openpype/hosts/hiero/startup/Python/StartupUI/Purge.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/openpype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py rename to openpype/hosts/hiero/startup/Python/StartupUI/nukeStyleKeyboardShortcuts.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py rename to openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/OTIOImport.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py b/openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py rename to openpype/hosts/hiero/startup/Python/StartupUI/otioimporter/__init__.py diff --git a/pype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py b/openpype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py similarity index 100% rename from pype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py rename to openpype/hosts/hiero/startup/Python/StartupUI/setPosterFrame.py diff --git a/pype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/openpype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to openpype/hosts/hiero/startup/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/openpype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to openpype/hosts/hiero/startup/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/openpype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 100% rename from pype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to openpype/hosts/hiero/startup/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/hosts/houdini/__init__.py b/openpype/hosts/houdini/__init__.py similarity index 100% rename from pype/hosts/houdini/__init__.py rename to openpype/hosts/houdini/__init__.py diff --git a/pype/hosts/houdini/api/__init__.py b/openpype/hosts/houdini/api/__init__.py similarity index 91% rename from pype/hosts/houdini/api/__init__.py rename to openpype/hosts/houdini/api/__init__.py index c6012d1133..21f4ae41c3 100644 --- a/pype/hosts/houdini/api/__init__.py +++ b/openpype/hosts/houdini/api/__init__.py @@ -8,14 +8,14 @@ from pyblish import api as pyblish from avalon import api as avalon from avalon.houdini import pipeline as houdini -import pype.hosts.houdini -from pype.hosts.houdini.api import lib +import openpype.hosts.houdini +from openpype.hosts.houdini.api import lib -from pype.lib import any_outdated +from openpype.lib import any_outdated -log = logging.getLogger("pype.hosts.houdini") +log = logging.getLogger("openpype.hosts.houdini") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.houdini.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.houdini.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") diff --git a/pype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py similarity index 99% rename from pype/hosts/houdini/api/lib.py rename to openpype/hosts/houdini/api/lib.py index 5087fba934..dd586ca02d 100644 --- a/pype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -4,7 +4,7 @@ from contextlib import contextmanager import hou -from pype import lib +from openpype import lib from avalon import api, io from avalon.houdini import lib as houdini diff --git a/pype/hosts/houdini/api/plugin.py b/openpype/hosts/houdini/api/plugin.py similarity index 67% rename from pype/hosts/houdini/api/plugin.py rename to openpype/hosts/houdini/api/plugin.py index 864cc59f09..9820ed49c3 100644 --- a/pype/hosts/houdini/api/plugin.py +++ b/openpype/hosts/houdini/api/plugin.py @@ -1,5 +1,5 @@ from avalon import houdini -from pype.api import PypeCreatorMixin +from openpype.api import PypeCreatorMixin class Creator(PypeCreatorMixin, houdini.Creator): diff --git a/pype/hosts/houdini/plugins/create/create_alembic_camera.py b/openpype/hosts/houdini/plugins/create/create_alembic_camera.py similarity index 96% rename from pype/hosts/houdini/plugins/create/create_alembic_camera.py rename to openpype/hosts/houdini/plugins/create/create_alembic_camera.py index 2a849ddaa0..adcfb48539 100644 --- a/pype/hosts/houdini/plugins/create/create_alembic_camera.py +++ b/openpype/hosts/houdini/plugins/create/create_alembic_camera.py @@ -1,4 +1,4 @@ -from pype.hosts.houdini.api import plugin +from openpype.hosts.houdini.api import plugin class CreateAlembicCamera(plugin.Creator): diff --git a/pype/hosts/houdini/plugins/create/create_pointcache.py b/openpype/hosts/houdini/plugins/create/create_pointcache.py similarity index 96% rename from pype/hosts/houdini/plugins/create/create_pointcache.py rename to openpype/hosts/houdini/plugins/create/create_pointcache.py index a36ee1c965..6be854ac28 100644 --- a/pype/hosts/houdini/plugins/create/create_pointcache.py +++ b/openpype/hosts/houdini/plugins/create/create_pointcache.py @@ -1,4 +1,4 @@ -from pype.hosts.houdini.api import plugin +from openpype.hosts.houdini.api import plugin class CreatePointCache(plugin.Creator): diff --git a/pype/hosts/houdini/plugins/create/create_vbd_cache.py b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py similarity index 94% rename from pype/hosts/houdini/plugins/create/create_vbd_cache.py rename to openpype/hosts/houdini/plugins/create/create_vbd_cache.py index 01401f0ce4..f8f3bbf9c3 100644 --- a/pype/hosts/houdini/plugins/create/create_vbd_cache.py +++ b/openpype/hosts/houdini/plugins/create/create_vbd_cache.py @@ -1,4 +1,4 @@ -from pype.hosts.houdini.api import plugin +from openpype.hosts.houdini.api import plugin class CreateVDBCache(plugin.Creator): diff --git a/pype/hosts/houdini/plugins/load/load_alembic.py b/openpype/hosts/houdini/plugins/load/load_alembic.py similarity index 100% rename from pype/hosts/houdini/plugins/load/load_alembic.py rename to openpype/hosts/houdini/plugins/load/load_alembic.py diff --git a/pype/hosts/houdini/plugins/load/load_camera.py b/openpype/hosts/houdini/plugins/load/load_camera.py similarity index 100% rename from pype/hosts/houdini/plugins/load/load_camera.py rename to openpype/hosts/houdini/plugins/load/load_camera.py diff --git a/pype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/houdini/plugins/publish/collect_current_file.py rename to openpype/hosts/houdini/plugins/publish/collect_current_file.py diff --git a/pype/hosts/houdini/plugins/publish/collect_frames.py b/openpype/hosts/houdini/plugins/publish/collect_frames.py similarity index 97% rename from pype/hosts/houdini/plugins/publish/collect_frames.py rename to openpype/hosts/houdini/plugins/publish/collect_frames.py index 47037f94d4..1d664aeaeb 100644 --- a/pype/hosts/houdini/plugins/publish/collect_frames.py +++ b/openpype/hosts/houdini/plugins/publish/collect_frames.py @@ -2,7 +2,7 @@ import os import re import pyblish.api -from pype.hosts.houdini.api import lib +from openpype.hosts.houdini.api import lib class CollectFrames(pyblish.api.InstancePlugin): diff --git a/pype/hosts/houdini/plugins/publish/collect_instances.py b/openpype/hosts/houdini/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/houdini/plugins/publish/collect_instances.py rename to openpype/hosts/houdini/plugins/publish/collect_instances.py diff --git a/pype/hosts/houdini/plugins/publish/collect_output_node.py b/openpype/hosts/houdini/plugins/publish/collect_output_node.py similarity index 100% rename from pype/hosts/houdini/plugins/publish/collect_output_node.py rename to openpype/hosts/houdini/plugins/publish/collect_output_node.py diff --git a/pype/hosts/houdini/plugins/publish/collect_workscene_fps.py b/openpype/hosts/houdini/plugins/publish/collect_workscene_fps.py similarity index 100% rename from pype/hosts/houdini/plugins/publish/collect_workscene_fps.py rename to openpype/hosts/houdini/plugins/publish/collect_workscene_fps.py diff --git a/pype/hosts/houdini/plugins/publish/extract_alembic.py b/openpype/hosts/houdini/plugins/publish/extract_alembic.py similarity index 95% rename from pype/hosts/houdini/plugins/publish/extract_alembic.py rename to openpype/hosts/houdini/plugins/publish/extract_alembic.py index a71b85c529..b251ebdc90 100644 --- a/pype/hosts/houdini/plugins/publish/extract_alembic.py +++ b/openpype/hosts/houdini/plugins/publish/extract_alembic.py @@ -1,10 +1,10 @@ import os import pyblish.api -import pype.api +import openpype.api -class ExtractAlembic(pype.api.Extractor): +class ExtractAlembic(openpype.api.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Alembic" diff --git a/pype/hosts/houdini/plugins/publish/extract_vdb_cache.py b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py similarity index 95% rename from pype/hosts/houdini/plugins/publish/extract_vdb_cache.py rename to openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py index c56a4dd73a..f480fe6236 100644 --- a/pype/hosts/houdini/plugins/publish/extract_vdb_cache.py +++ b/openpype/hosts/houdini/plugins/publish/extract_vdb_cache.py @@ -1,10 +1,10 @@ import os import pyblish.api -import pype.api +import openpype.api -class ExtractVDBCache(pype.api.Extractor): +class ExtractVDBCache(openpype.api.Extractor): order = pyblish.api.ExtractorOrder + 0.1 label = "Extract VDB Cache" diff --git a/pype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py b/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py similarity index 94% rename from pype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py rename to openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py index e5bc118306..7b23d73ac7 100644 --- a/pype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/valiate_vdb_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateVDBInputNode(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + 0.1 + order = openpype.api.ValidateContentsOrder + 0.1 families = ["vdbcache"] hosts = ["houdini"] label = "Validate Input Node (VDB)" diff --git a/pype/hosts/houdini/plugins/publish/validate_alembic_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py similarity index 93% rename from pype/hosts/houdini/plugins/publish/validate_alembic_input_node.py rename to openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py index c68e3cdf5c..e8596b739d 100644 --- a/pype/hosts/houdini/plugins/publish/validate_alembic_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateAlembicInputNode(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + 0.1 + order = openpype.api.ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Input Node (Abc)" diff --git a/pype/hosts/houdini/plugins/publish/validate_animation_settings.py b/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py similarity index 97% rename from pype/hosts/houdini/plugins/publish/validate_animation_settings.py rename to openpype/hosts/houdini/plugins/publish/validate_animation_settings.py index 633c13dfb6..a42c3696da 100644 --- a/pype/hosts/houdini/plugins/publish/validate_animation_settings.py +++ b/openpype/hosts/houdini/plugins/publish/validate_animation_settings.py @@ -1,6 +1,6 @@ import pyblish.api -from pype.hosts.houdini.api import lib +from openpype.hosts.houdini.api import lib class ValidateAnimationSettings(pyblish.api.InstancePlugin): diff --git a/pype/hosts/houdini/plugins/publish/validate_bypass.py b/openpype/hosts/houdini/plugins/publish/validate_bypass.py similarity index 92% rename from pype/hosts/houdini/plugins/publish/validate_bypass.py rename to openpype/hosts/houdini/plugins/publish/validate_bypass.py index 70505f0731..9118ae0e8c 100644 --- a/pype/hosts/houdini/plugins/publish/validate_bypass.py +++ b/openpype/hosts/houdini/plugins/publish/validate_bypass.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateBypassed(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidateBypassed(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder - 0.1 + order = openpype.api.ValidateContentsOrder - 0.1 families = ["*"] hosts = ["houdini"] label = "Validate ROP Bypass" diff --git a/pype/hosts/houdini/plugins/publish/validate_camera_rop.py b/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py similarity index 95% rename from pype/hosts/houdini/plugins/publish/validate_camera_rop.py rename to openpype/hosts/houdini/plugins/publish/validate_camera_rop.py index 48335cfb37..ca75579267 100644 --- a/pype/hosts/houdini/plugins/publish/validate_camera_rop.py +++ b/openpype/hosts/houdini/plugins/publish/validate_camera_rop.py @@ -1,11 +1,11 @@ import pyblish.api -import pype.api +import openpype.api class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['camera'] hosts = ['houdini'] label = 'Camera ROP' diff --git a/pype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py b/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py similarity index 92% rename from pype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py rename to openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py index c2b72b4e43..a735f4b64b 100644 --- a/pype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py +++ b/openpype/hosts/houdini/plugins/publish/validate_mkpaths_toggled.py @@ -1,11 +1,11 @@ import pyblish.api -import pype.api +import openpype.api class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['pointcache', 'camera', 'vdbcache'] diff --git a/pype/hosts/houdini/plugins/publish/validate_outnode_exists.py b/openpype/hosts/houdini/plugins/publish/validate_outnode_exists.py similarity index 95% rename from pype/hosts/houdini/plugins/publish/validate_outnode_exists.py rename to openpype/hosts/houdini/plugins/publish/validate_outnode_exists.py index 0ba04b87d0..bfa2d38f1a 100644 --- a/pype/hosts/houdini/plugins/publish/validate_outnode_exists.py +++ b/openpype/hosts/houdini/plugins/publish/validate_outnode_exists.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidatOutputNodeExists(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["*"] hosts = ['houdini'] label = "Output Node Exists" diff --git a/pype/hosts/houdini/plugins/publish/validate_output_node.py b/openpype/hosts/houdini/plugins/publish/validate_output_node.py similarity index 100% rename from pype/hosts/houdini/plugins/publish/validate_output_node.py rename to openpype/hosts/houdini/plugins/publish/validate_output_node.py diff --git a/pype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py similarity index 97% rename from pype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py rename to openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py index 70e7873d3b..608e236198 100644 --- a/pype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py +++ b/openpype/hosts/houdini/plugins/publish/validate_primitive_hierarchy_paths.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + 0.1 + order = openpype.api.ValidateContentsOrder + 0.1 families = ["pointcache"] hosts = ["houdini"] label = "Validate Prims Hierarchy Path" diff --git a/pype/hosts/houdini/plugins/publish/validate_vdb_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py similarity index 94% rename from pype/hosts/houdini/plugins/publish/validate_vdb_input_node.py rename to openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py index e5bc118306..7b23d73ac7 100644 --- a/pype/hosts/houdini/plugins/publish/validate_vdb_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_vdb_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateVDBInputNode(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + 0.1 + order = openpype.api.ValidateContentsOrder + 0.1 families = ["vdbcache"] hosts = ["houdini"] label = "Validate Input Node (VDB)" diff --git a/pype/hosts/houdini/startup/MainMenuCommon.XML b/openpype/hosts/houdini/startup/MainMenuCommon.XML similarity index 73% rename from pype/hosts/houdini/startup/MainMenuCommon.XML rename to openpype/hosts/houdini/startup/MainMenuCommon.XML index ba639a71a1..77ee182e7c 100644 --- a/pype/hosts/houdini/startup/MainMenuCommon.XML +++ b/openpype/hosts/houdini/startup/MainMenuCommon.XML @@ -2,21 +2,7 @@ - - - - - - - - - - + @@ -55,10 +41,10 @@ publish.show(parent) - + diff --git a/pype/hosts/houdini/startup/scripts/123.py b/openpype/hosts/houdini/startup/scripts/123.py similarity index 71% rename from pype/hosts/houdini/startup/scripts/123.py rename to openpype/hosts/houdini/startup/scripts/123.py index 144b9fdb89..6d90b8352e 100644 --- a/pype/hosts/houdini/startup/scripts/123.py +++ b/openpype/hosts/houdini/startup/scripts/123.py @@ -3,7 +3,7 @@ import hou def main(): - print("Installing Avalon ...") + print("Installing OpenPype ...") api.install(houdini) diff --git a/pype/hosts/maya/__init__.py b/openpype/hosts/maya/__init__.py similarity index 100% rename from pype/hosts/maya/__init__.py rename to openpype/hosts/maya/__init__.py diff --git a/pype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py similarity index 91% rename from pype/hosts/maya/api/__init__.py rename to openpype/hosts/maya/api/__init__.py index 9d4418e58d..4697d212de 100644 --- a/pype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -10,14 +10,14 @@ from avalon.maya import suspended_refresh from avalon.maya.pipeline import IS_HEADLESS from avalon.tools import workfiles from pyblish import api as pyblish -from pype.lib import any_outdated -import pype.hosts.maya -from pype.hosts.maya.lib import copy_workspace_mel +from openpype.lib import any_outdated +import openpype.hosts.maya +from openpype.hosts.maya.lib import copy_workspace_mel from . import menu, lib -log = logging.getLogger("pype.hosts.maya") +log = logging.getLogger("openpype.hosts.maya") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.maya.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.maya.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") @@ -133,16 +133,16 @@ def on_open(_): """On scene open let's assume the containers have changed.""" from avalon.vendor.Qt import QtWidgets - from pype.widgets import popup + from openpype.widgets import popup cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.remove_render_layer_observer()") cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_observer()") cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_change_observer()") # # Update current task for the current scene # update_task_from_path(cmds.file(query=True, sceneName=True)) @@ -183,13 +183,13 @@ def on_new(_): avalon.logger.info("Running callback on new..") with suspended_refresh(): cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.remove_render_layer_observer()") cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_observer()") cmds.evalDeferred( - "from pype.hosts.maya.api import lib;" + "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_change_observer()") lib.set_context_settings() diff --git a/pype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py similarity index 97% rename from pype/hosts/maya/api/action.py rename to openpype/hosts/maya/api/action.py index f623298451..a98b906d8c 100644 --- a/pype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -4,7 +4,7 @@ from __future__ import absolute_import import pyblish.api -from pype.api import get_errored_instances_from_context +from openpype.api import get_errored_instances_from_context class GenerateUUIDsOnInvalidAction(pyblish.api.Action): @@ -72,7 +72,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): nodes (list): all nodes to regenerate ids on """ - from pype.hosts.maya.api import lib + from openpype.hosts.maya.api import lib import avalon.io as io asset = instance.data['asset'] diff --git a/pype/hosts/maya/api/attributes.py b/openpype/hosts/maya/api/attributes.py similarity index 99% rename from pype/hosts/maya/api/attributes.py rename to openpype/hosts/maya/api/attributes.py index a98548301a..84d1e1391f 100644 --- a/pype/hosts/maya/api/attributes.py +++ b/openpype/hosts/maya/api/attributes.py @@ -3,7 +3,7 @@ https://github.com/Colorbleed/colorbleed-config/blob/acre/colorbleed/maya/lib_rendersetup.py Credits: Roy Nieterau (BigRoy) / Colorbleed -Modified for use in Pype +Modified for use in OpenPype """ diff --git a/pype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py similarity index 98% rename from pype/hosts/maya/api/customize.py rename to openpype/hosts/maya/api/customize.py index 3f3449b3c9..22945471b7 100644 --- a/pype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -89,11 +89,11 @@ def override_toolbox_ui(): log.warning("Could not import Workfiles tool") try: - from pype.tools import mayalookassigner + from openpype.tools import mayalookassigner except Exception: log.warning("Could not import Maya Look assigner tool") - from pype.api import resources + from openpype.api import resources icons = resources.get_resource("icons") diff --git a/pype/hosts/maya/api/expected_files.py b/openpype/hosts/maya/api/expected_files.py similarity index 99% rename from pype/hosts/maya/api/expected_files.py rename to openpype/hosts/maya/api/expected_files.py index 9a8c8d6e0f..186b199796 100644 --- a/pype/hosts/maya/api/expected_files.py +++ b/openpype/hosts/maya/api/expected_files.py @@ -44,7 +44,7 @@ from abc import ABCMeta, abstractmethod import six -import pype.hosts.maya.api.lib as lib +import openpype.hosts.maya.api.lib as lib from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup diff --git a/pype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py similarity index 99% rename from pype/hosts/maya/api/lib.py rename to openpype/hosts/maya/api/lib.py index 44f1577f7f..ae2d329a97 100644 --- a/pype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -21,7 +21,7 @@ from avalon.vendor.six import string_types import avalon.maya.lib import avalon.maya.interactive -from pype import lib +from openpype import lib log = logging.getLogger(__name__) @@ -2677,7 +2677,7 @@ def update_content_on_context_change(): def show_message(title, msg): from avalon.vendor.Qt import QtWidgets - from pype.widgets import message_window + from openpype.widgets import message_window # Find maya main window top_level_widgets = {w.objectName(): w for w in diff --git a/pype/hosts/maya/api/menu.json b/openpype/hosts/maya/api/menu.json similarity index 79% rename from pype/hosts/maya/api/menu.json rename to openpype/hosts/maya/api/menu.json index 03eb05e5bd..bf4d812d33 100644 --- a/pype/hosts/maya/api/menu.json +++ b/openpype/hosts/maya/api/menu.json @@ -1,28 +1,28 @@ [ { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\save_scene_incremental.py", + "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py", "sourcetype": "file", "title": "# Version Up", "tooltip": "Incremental save with a specific format" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\open_current_folder.py", + "command": "$OPENPYPE_SCRIPTS\\others\\open_current_folder.py", "sourcetype": "file", "title": "Open working folder..", "tooltip": "Show current scene in Explorer" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\avalon\\launch_manager.py", + "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py", "sourcetype": "file", "title": "# Project Manager", "tooltip": "Add assets to the project" }, { "type": "action", - "command": "from pype.tools.assetcreator import app as assetcreator; assetcreator.show(context='maya')", + "command": "from openpype.tools.assetcreator import app as assetcreator; assetcreator.show(context='maya')", "sourcetype": "python", "title": "Asset Creator", "tooltip": "Open the Asset Creator" @@ -44,7 +44,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", "sourcetype": "file", "tags": ["modeling", "separateMeshPerShader"], "title": "# Separate Mesh Per Shader", @@ -52,7 +52,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", "sourcetype": "file", "tags": ["modeling", "poly", "detach", "separate"], "title": "# Polygon Detach and Separate", @@ -60,14 +60,14 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", "sourcetype": "file", "tags": ["modeling", "select", "nth", "edge", "ui"], "title": "# Select Every Nth Edge" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\djPFXUVs.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py", "sourcetype": "file", "tags": ["modeling", "djPFX", "UVs"], "title": "# dj PFX UVs", @@ -81,7 +81,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\advancedSkeleton.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\advancedSkeleton.py", "sourcetype": "file", "tags": [ "rigging", @@ -106,7 +106,7 @@ { "type": "action", "title": "# Import Proxies", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", "sourcetype": "file", "tags": ["shading", "vray", "import", "proxies"], "tooltip": "" @@ -117,7 +117,7 @@ { "type": "action", "title": "# Select All GES", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "select All GES"] @@ -125,7 +125,7 @@ { "type": "action", "title": "# Select All GES Under Selection", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "select", "all", "GES"] @@ -136,7 +136,7 @@ { "type": "action", "title": "# Selection To VRay Mesh", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "selection", "vraymesh"] @@ -144,7 +144,7 @@ { "type": "action", "title": "# Add VRay Round Edges Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "round edges", "attribute"] @@ -152,7 +152,7 @@ { "type": "action", "title": "# Add Gamma", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "add gamma"] @@ -162,7 +162,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", "sourcetype": "file", "title": "# Select Unconnected Shader Materials", "tags": [ @@ -177,7 +177,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", "sourcetype": "file", "title": "# Merge Similar VRay Mesh Materials", "tags": [ @@ -192,7 +192,7 @@ { "type": "action", "title": "# Create Two Sided Material", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", "sourcetype": "file", "tooltip": "Creates two sided material for selected material and renames it", "tags": ["shading", "vray", "two sided", "material"] @@ -200,7 +200,7 @@ { "type": "action", "title": "# Create Two Sided Material For Selected", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", "sourcetype": "file", "tooltip": "Select material to create a two sided version from it", "tags": [ @@ -215,7 +215,7 @@ { "type": "action", "title": "# Add OpenSubdiv Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -229,7 +229,7 @@ { "type": "action", "title": "# Remove OpenSubdiv Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -246,7 +246,7 @@ { "type": "action", "title": "# Add Subdivision Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -258,7 +258,7 @@ { "type": "action", "title": "# Remove Subdivision Attribute.py", - "command": "$PYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -275,7 +275,7 @@ { "type": "action", "title": "# Add Vray Object Ids", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "add", "object id"] @@ -283,7 +283,7 @@ { "type": "action", "title": "# Add Vray Material Ids", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "addVrayMaterialIds.py"] @@ -294,7 +294,7 @@ { "type": "action", "title": "# Set Physical DOF Depth", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "physical", "DOF ", "Depth"] @@ -302,7 +302,7 @@ { "type": "action", "title": "# Magic Vray Proxy UI", - "command": "$PYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "magicVrayProxyUI"] @@ -311,7 +311,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", + "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", "sourcetype": "file", "tags": [ "shading", @@ -335,7 +335,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\LightLinkUi.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py", "sourcetype": "file", "tags": ["shading", "light", "link", "ui"], "title": "# Light Link UI", @@ -343,7 +343,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vdviewer_ui.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py", "sourcetype": "file", "tags": [ "shading", @@ -358,7 +358,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", "sourcetype": "file", "tags": ["shading", "CLRImage", "textures", "preview"], "title": "# Set Texture Preview To CLRImage", @@ -366,7 +366,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", "sourcetype": "file", "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], "title": "# Fix Default Shader Set Behavior", @@ -374,7 +374,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", "sourcetype": "file", "tags": [ "shading", @@ -389,7 +389,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\selectLambert1Members.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py", "sourcetype": "file", "tags": ["shading", "selectLambert1Members"], "title": "# Select Lambert1 Members", @@ -397,7 +397,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", "sourcetype": "file", "tags": ["shading", "selectShapesWithoutShader"], "title": "# Select Shapes Without Shader", @@ -405,7 +405,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", "sourcetype": "file", "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"], "title": "# Fix RenderLayer Out Adjustment Errors", @@ -413,7 +413,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", "sourcetype": "file", "tags": [ "shading", @@ -429,7 +429,7 @@ { "type": "action", "title": "# Image 2 Tiled EXR", - "command": "$PYPE_SCRIPTS\\shading\\open_img2exr.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "exr"] @@ -442,7 +442,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", + "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", "sourcetype": "file", "tags": ["settings", "deadline", "globals", "render"], "title": "# DL Submission Settings UI", @@ -461,7 +461,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyValues.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py", "sourcetype": "file", "tags": ["animation", "copy", "attributes"], "title": "# Copy Values", @@ -469,7 +469,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", "sourcetype": "file", "tags": [ "animation", @@ -483,7 +483,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", "sourcetype": "file", "tags": [ "animation", @@ -497,7 +497,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", "sourcetype": "file", "tags": [ "animation", @@ -511,7 +511,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", "sourcetype": "file", "tags": [ "animation", @@ -525,7 +525,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", "sourcetype": "file", "tags": [ "animation", @@ -539,7 +539,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", "sourcetype": "file", "tags": [ "animation", @@ -561,7 +561,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", "sourcetype": "file", "tags": ["animation", "hierarchy", "toggle", "freeze"], "title": "# Toggle Freeze Hierarchy", @@ -569,7 +569,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", "sourcetype": "file", "tags": ["animation", "nucleus", "toggle", "parallel"], "title": "# Toggle Parallel Nucleus", @@ -579,21 +579,21 @@ }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "tags": ["animation", "bake", "selection", "worldspace.py"], "title": "# Bake Selected To Worldspace", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\timeStepper.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py", "tags": ["animation", "time", "stepper"], "title": "# Time Stepper", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\capture_ui.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py", "tags": [ "animation", "capture", @@ -607,63 +607,63 @@ }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\simplePlayblastUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py", "tags": ["animation", "simple", "playblast", "ui"], "title": "# Simple Playblast UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\tweenMachineUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py", "tags": ["animation", "tween", "machine"], "title": "# Tween Machine UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", "tags": ["animation", "select", "curves"], "title": "# Select All Animation Curves", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\pathAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py", "tags": ["animation", "path", "along"], "title": "# Path Animation", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", "tags": ["animation", "offsetSelectedObjectsUI.py"], "title": "# Offset Selected Objects UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\key_amplifier_ui.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py", "tags": ["animation", "key", "amplifier"], "title": "# Key Amplifier UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", "tags": ["animation", "anim_scene_optimizer.py"], "title": "# Anim_Scene_Optimizer", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\zvParentMaster.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py", "tags": ["animation", "zvParentMaster.py"], "title": "# ZV Parent Master", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\animLibrary.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\animLibrary.py", "tags": ["animation", "studiolibrary.py"], "title": "Anim Library", "type": "action" @@ -676,7 +676,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\alignDistributeUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py", "sourcetype": "file", "tags": ["layout", "align", "Distribute", "UI"], "title": "# Align Distribute UI", @@ -684,7 +684,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\alignSimpleUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py", "sourcetype": "file", "tags": ["layout", "align", "UI", "Simple"], "title": "# Align Simple UI", @@ -692,7 +692,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\center_locator.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py", "sourcetype": "file", "tags": ["layout", "center", "locator"], "title": "# Center Locator", @@ -700,7 +700,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\average_locator.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py", "sourcetype": "file", "tags": ["layout", "average", "locator"], "title": "# Average Locator", @@ -708,7 +708,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", "sourcetype": "file", "tags": ["layout", "select", "proximity", "ui"], "title": "# Select Within Proximity UI", @@ -716,7 +716,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\dupCurveUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py", "sourcetype": "file", "tags": ["layout", "Duplicate", "Curve", "UI"], "title": "# Duplicate Curve UI", @@ -724,7 +724,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\randomDeselectUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py", "sourcetype": "file", "tags": ["layout", "random", "Deselect", "UI"], "title": "# Random Deselect UI", @@ -732,7 +732,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\multiReferencerUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py", "sourcetype": "file", "tags": ["layout", "multi", "reference"], "title": "# Multi Referencer UI", @@ -740,7 +740,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", "sourcetype": "file", "tags": ["layout", "duplicate", "offset", "UI"], "title": "# Duplicate Offset UI", @@ -748,7 +748,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\spPaint3d.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py", "sourcetype": "file", "tags": ["layout", "spPaint3d", "paint", "tool"], "title": "# SP Paint 3d", @@ -756,7 +756,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\randomizeUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py", "sourcetype": "file", "tags": ["layout", "randomize", "UI"], "title": "# Randomize UI", @@ -764,7 +764,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", "sourcetype": "file", "tags": ["layout", "distribute", "ObjectUI", "within"], "title": "# Distribute Within Object UI", @@ -778,7 +778,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjects.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py", "sourcetype": "file", "tags": ["particles", "instancerToObjects"], "title": "# Instancer To Objects", @@ -786,7 +786,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", "sourcetype": "file", "tags": ["particles", "instancerToObjectsInstances"], "title": "# Instancer To Objects Instances", @@ -794,7 +794,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", "sourcetype": "file", "tags": [ "particles", @@ -805,7 +805,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", "sourcetype": "file", "tags": ["particles", "instancerToObjectsWithAnimation"], "title": "# Instancer To Objects With Animation", @@ -819,7 +819,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", "sourcetype": "file", "tags": ["cleanup", "repair", "containers"], "title": "# Find and Repair Containers", @@ -830,7 +830,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeNamespaces.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py", "sourcetype": "file", "tags": ["cleanup", "remove", "namespaces"], "title": "# Remove Namespaces", @@ -838,7 +838,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", "sourcetype": "file", "tags": ["cleanup", "remove_user_defined_attributes"], "title": "# Remove User Defined Attributes", @@ -846,7 +846,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", "sourcetype": "file", "tags": ["cleanup", "removeUnknownNodes"], "title": "# Remove Unknown Nodes", @@ -854,7 +854,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", "sourcetype": "file", "tags": ["cleanup", "removeUnloadedReferences"], "title": "# Remove Unloaded References", @@ -862,7 +862,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", "sourcetype": "file", "tags": ["cleanup", "removeReferencesFailedEdits"], "title": "# Remove References Failed Edits", @@ -870,7 +870,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", "sourcetype": "file", "tags": ["cleanup", "removeUnusedLooks"], "title": "# Remove Unused Looks", @@ -881,7 +881,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", "sourcetype": "file", "tags": ["cleanup", "uniqifyNodeNames"], "title": "# Uniqify Node Names", @@ -889,7 +889,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", "sourcetype": "file", "tags": ["cleanup", "auto", "rename", "filenodes"], "title": "# Auto Rename File Nodes", @@ -897,7 +897,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\update_asset_id.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py", "sourcetype": "file", "tags": ["cleanup", "update", "database", "asset", "id"], "title": "# Update Asset ID", @@ -905,7 +905,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\ccRenameReplace.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\ccRenameReplace.py", "sourcetype": "file", "tags": ["cleanup", "rename", "ui"], "title": "Renamer", @@ -913,7 +913,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", "sourcetype": "file", "tags": ["cleanup", "renameShapesToTransform"], "title": "# Rename Shapes To Transform", diff --git a/pype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py similarity index 94% rename from pype/hosts/maya/api/menu.py rename to openpype/hosts/maya/api/menu.py index 9381043511..42e5c66e4a 100644 --- a/pype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -4,11 +4,12 @@ import logging from avalon.vendor.Qt import QtWidgets, QtGui from avalon.maya import pipeline -from pype.api import BuildWorkfile +from openpype.api import BuildWorkfile import maya.cmds as cmds self = sys.modules[__name__] -self._menu = os.environ.get('PYPE_STUDIO_NAME') or "Pype" +self._menu = os.environ.get("AVALON_LABEL") + log = logging.getLogger(__name__) @@ -43,7 +44,7 @@ def deferred(): ) def modify_workfiles(): - from pype.tools import workfiles + from openpype.tools import workfiles def launch_workfiles_app(*_args, **_kwargs): workfiles.show( @@ -126,7 +127,7 @@ def uninstall(): def install(): if cmds.about(batch=True): - log.info("Skipping pype.menu initialization in batch mode..") + log.info("Skipping openpype.menu initialization in batch mode..") return uninstall() diff --git a/pype/hosts/maya/api/menu_backup.json b/openpype/hosts/maya/api/menu_backup.json similarity index 77% rename from pype/hosts/maya/api/menu_backup.json rename to openpype/hosts/maya/api/menu_backup.json index 735799345d..731a33a630 100644 --- a/pype/hosts/maya/api/menu_backup.json +++ b/openpype/hosts/maya/api/menu_backup.json @@ -1,21 +1,21 @@ [ { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\save_scene_incremental.py", + "command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py", "sourcetype": "file", "title": "Version Up", "tooltip": "Incremental save with a specific format" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\show_current_scene_in_explorer.py", + "command": "$OPENPYPE_SCRIPTS\\others\\show_current_scene_in_explorer.py", "sourcetype": "file", "title": "Explore current scene..", "tooltip": "Show current scene in Explorer" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\avalon\\launch_manager.py", + "command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py", "sourcetype": "file", "title": "Project Manager", "tooltip": "Add assets to the project" @@ -29,7 +29,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\duplicate_normalized.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\duplicate_normalized.py", "sourcetype": "file", "tags": ["modeling", "duplicate", "normalized"], "title": "Duplicate Normalized", @@ -37,7 +37,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\transferUVs.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\transferUVs.py", "sourcetype": "file", "tags": ["modeling", "transfer", "uv"], "title": "Transfer UVs", @@ -45,7 +45,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\mirrorSymmetry.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\mirrorSymmetry.py", "sourcetype": "file", "tags": ["modeling", "mirror", "symmetry"], "title": "Mirror Symmetry", @@ -53,7 +53,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\selectOutlineUI.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\selectOutlineUI.py", "sourcetype": "file", "tags": ["modeling", "select", "outline", "ui"], "title": "Select Outline UI", @@ -61,7 +61,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", "sourcetype": "file", "tags": ["modeling", "polygon", "uvset", "delete"], "title": "Polygon Delete Other UV Sets", @@ -69,7 +69,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polyCombineQuick.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polyCombineQuick.py", "sourcetype": "file", "tags": ["modeling", "combine", "polygon", "quick"], "title": "Polygon Combine Quick", @@ -77,7 +77,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py", "sourcetype": "file", "tags": ["modeling", "separateMeshPerShader"], "title": "Separate Mesh Per Shader", @@ -85,7 +85,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py", "sourcetype": "file", "tags": ["modeling", "poly", "detach", "separate"], "title": "Polygon Detach and Separate", @@ -93,7 +93,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polyRelaxVerts.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polyRelaxVerts.py", "sourcetype": "file", "tags": ["modeling", "relax", "verts"], "title": "Polygon Relax Vertices", @@ -101,14 +101,14 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", "sourcetype": "file", "tags": ["modeling", "select", "nth", "edge", "ui"], "title": "Select Every Nth Edge" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\modeling\\djPFXUVs.py", + "command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py", "sourcetype": "file", "tags": ["modeling", "djPFX", "UVs"], "title": "dj PFX UVs", @@ -122,105 +122,105 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\addCurveBetween.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\addCurveBetween.py", "sourcetype": "file", "tags": ["rigging", "addCurveBetween", "file"], "title": "Add Curve Between" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\averageSkinWeights.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\averageSkinWeights.py", "sourcetype": "file", "tags": ["rigging", "average", "skin weights", "file"], "title": "Average Skin Weights" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", "sourcetype": "file", "tags": ["rigging", "cbSmoothSkinWeightUI", "file"], "title": "CB Smooth Skin Weight UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\channelBoxManagerUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\channelBoxManagerUI.py", "sourcetype": "file", "tags": ["rigging", "channelBoxManagerUI", "file"], "title": "Channel Box Manager UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\characterAutorigger.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\characterAutorigger.py", "sourcetype": "file", "tags": ["rigging", "characterAutorigger", "file"], "title": "Character Auto Rigger" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\connectUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\connectUI.py", "sourcetype": "file", "tags": ["rigging", "connectUI", "file"], "title": "Connect UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\copySkinWeightsLocal.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\copySkinWeightsLocal.py", "sourcetype": "file", "tags": ["rigging", "copySkinWeightsLocal", "file"], "title": "Copy Skin Weights Local" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\createCenterLocator.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\createCenterLocator.py", "sourcetype": "file", "tags": ["rigging", "createCenterLocator", "file"], "title": "Create Center Locator" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\freezeTransformToGroup.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\freezeTransformToGroup.py", "sourcetype": "file", "tags": ["rigging", "freezeTransformToGroup", "file"], "title": "Freeze Transform To Group" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\groupSelected.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\groupSelected.py", "sourcetype": "file", "tags": ["rigging", "groupSelected", "file"], "title": "Group Selected" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", "sourcetype": "file", "tags": ["rigging", "ikHandlePoleVectorLocator", "file"], "title": "IK Handle Pole Vector Locator" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\jointOrientUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\jointOrientUI.py", "sourcetype": "file", "tags": ["rigging", "jointOrientUI", "file"], "title": "Joint Orient UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\jointsOnCurve.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\jointsOnCurve.py", "sourcetype": "file", "tags": ["rigging", "jointsOnCurve", "file"], "title": "Joints On Curve" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", "sourcetype": "file", "tags": ["rigging", "resetBindSelectedSkinJoints", "file"], "title": "Reset Bind Selected Skin Joints" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", "sourcetype": "file", "tags": [ "rigging", @@ -231,7 +231,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", "sourcetype": "file", "tags": [ "rigging", @@ -242,14 +242,14 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\setJointLabels.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointLabels.py", "sourcetype": "file", "tags": ["rigging", "setJointLabels", "file"], "title": "Set Joint Labels" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", "sourcetype": "file", "tags": [ "rigging", @@ -260,84 +260,84 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", "sourcetype": "file", "tags": ["rigging", "setSelectedJointsOrientationZero", "file"], "title": "Set Selected Joints Orientation Zero" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\mirrorCurveShape.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\mirrorCurveShape.py", "sourcetype": "file", "tags": ["rigging", "mirrorCurveShape", "file"], "title": "Mirror Curve Shape" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\setRotationOrderUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\setRotationOrderUI.py", "sourcetype": "file", "tags": ["rigging", "setRotationOrderUI", "file"], "title": "Set Rotation Order UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\paintItNowUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\paintItNowUI.py", "sourcetype": "file", "tags": ["rigging", "paintItNowUI", "file"], "title": "Paint It Now UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\parentScaleConstraint.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\parentScaleConstraint.py", "sourcetype": "file", "tags": ["rigging", "parentScaleConstraint", "file"], "title": "Parent Scale Constraint" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\quickSetWeightsUI.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\quickSetWeightsUI.py", "sourcetype": "file", "tags": ["rigging", "quickSetWeightsUI", "file"], "title": "Quick Set Weights UI" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\rapidRig.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\rapidRig.py", "sourcetype": "file", "tags": ["rigging", "rapidRig", "file"], "title": "Rapid Rig" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", "sourcetype": "file", "tags": ["rigging", "regenerate_blendshape_targets", "file"], "title": "Regenerate Blendshape Targets" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\removeRotationAxis.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\removeRotationAxis.py", "sourcetype": "file", "tags": ["rigging", "removeRotationAxis", "file"], "title": "Remove Rotation Axis" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", "sourcetype": "file", "tags": ["rigging", "resetBindSelectedMeshes", "file"], "title": "Reset Bind Selected Meshes" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\simpleControllerOnSelection.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelection.py", "sourcetype": "file", "tags": ["rigging", "simpleControllerOnSelection", "file"], "title": "Simple Controller On Selection" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", "sourcetype": "file", "tags": [ "rigging", @@ -348,35 +348,35 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\superRelativeCluster.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\superRelativeCluster.py", "sourcetype": "file", "tags": ["rigging", "superRelativeCluster", "file"], "title": "Super Relative Cluster" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", "sourcetype": "file", "tags": ["rigging", "tfSmoothSkinWeight", "file"], "title": "TF Smooth Skin Weight" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\toggleIntermediates.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleIntermediates.py", "sourcetype": "file", "tags": ["rigging", "toggleIntermediates", "file"], "title": "Toggle Intermediates" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", "sourcetype": "file", "tags": ["rigging", "toggleSegmentScaleCompensate", "file"], "title": "Toggle Segment Scale Compensate" }, { "type": "action", - "command": "$PYPE_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", + "command": "$OPENPYPE_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", "sourcetype": "file", "tags": ["rigging", "toggleSkinclusterDeformNormals", "file"], "title": "Toggle Skincluster Deform Normals" @@ -394,7 +394,7 @@ { "type": "action", "title": "Import Proxies", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py", "sourcetype": "file", "tags": ["shading", "vray", "import", "proxies"], "tooltip": "" @@ -405,7 +405,7 @@ { "type": "action", "title": "Select All GES", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "select All GES"] @@ -413,7 +413,7 @@ { "type": "action", "title": "Select All GES Under Selection", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "select", "all", "GES"] @@ -424,7 +424,7 @@ { "type": "action", "title": "Selection To VRay Mesh", - "command": "$PYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "selection", "vraymesh"] @@ -432,7 +432,7 @@ { "type": "action", "title": "Add VRay Round Edges Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "round edges", "attribute"] @@ -440,7 +440,7 @@ { "type": "action", "title": "Add Gamma", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "add gamma"] @@ -450,7 +450,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", "sourcetype": "file", "title": "Select Unconnected Shader Materials", "tags": [ @@ -465,7 +465,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", "sourcetype": "file", "title": "Merge Similar VRay Mesh Materials", "tags": [ @@ -480,7 +480,7 @@ { "type": "action", "title": "Create Two Sided Material", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", "sourcetype": "file", "tooltip": "Creates two sided material for selected material and renames it", "tags": ["shading", "vray", "two sided", "material"] @@ -488,7 +488,7 @@ { "type": "action", "title": "Create Two Sided Material For Selected", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", "sourcetype": "file", "tooltip": "Select material to create a two sided version from it", "tags": [ @@ -503,7 +503,7 @@ { "type": "action", "title": "Add OpenSubdiv Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -517,7 +517,7 @@ { "type": "action", "title": "Remove OpenSubdiv Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -534,7 +534,7 @@ { "type": "action", "title": "Add Subdivision Attribute", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -546,7 +546,7 @@ { "type": "action", "title": "Remove Subdivision Attribute.py", - "command": "$PYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", "tags": [ @@ -563,7 +563,7 @@ { "type": "action", "title": "Add Vray Object Ids", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "add", "object id"] @@ -571,7 +571,7 @@ { "type": "action", "title": "Add Vray Material Ids", - "command": "$PYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "addVrayMaterialIds.py"] @@ -582,7 +582,7 @@ { "type": "action", "title": "Set Physical DOF Depth", - "command": "$PYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "physical", "DOF ", "Depth"] @@ -590,7 +590,7 @@ { "type": "action", "title": "Magic Vray Proxy UI", - "command": "$PYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "magicVrayProxyUI"] @@ -599,7 +599,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", + "command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", "sourcetype": "file", "tags": [ "shading", @@ -623,7 +623,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\LightLinkUi.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py", "sourcetype": "file", "tags": ["shading", "light", "link", "ui"], "title": "Light Link UI", @@ -631,7 +631,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\vdviewer_ui.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py", "sourcetype": "file", "tags": [ "shading", @@ -646,7 +646,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", "sourcetype": "file", "tags": ["shading", "CLRImage", "textures", "preview"], "title": "Set Texture Preview To CLRImage", @@ -654,7 +654,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", "sourcetype": "file", "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], "title": "Fix Default Shader Set Behavior", @@ -662,7 +662,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", "sourcetype": "file", "tags": [ "shading", @@ -677,7 +677,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\selectLambert1Members.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py", "sourcetype": "file", "tags": ["shading", "selectLambert1Members"], "title": "Select Lambert1 Members", @@ -685,7 +685,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py", "sourcetype": "file", "tags": ["shading", "selectShapesWithoutShader"], "title": "Select Shapes Without Shader", @@ -693,7 +693,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", "sourcetype": "file", "tags": ["shading", "fixRenderLayerOutAdjustmentErrors"], "title": "Fix RenderLayer Out Adjustment Errors", @@ -701,7 +701,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", "sourcetype": "file", "tags": [ "shading", @@ -717,7 +717,7 @@ { "type": "action", "title": "Image 2 Tiled EXR", - "command": "$PYPE_SCRIPTS\\shading\\open_img2exr.py", + "command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py", "sourcetype": "file", "tooltip": "", "tags": ["shading", "vray", "exr"] @@ -730,7 +730,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", + "command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", "sourcetype": "file", "tags": ["settings", "deadline", "globals", "render"], "title": "DL Submission Settings UI", @@ -749,7 +749,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyValues.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py", "sourcetype": "file", "tags": ["animation", "copy", "attributes"], "title": "Copy Values", @@ -757,7 +757,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py", "sourcetype": "file", "tags": [ "animation", @@ -771,7 +771,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py", "sourcetype": "file", "tags": [ "animation", @@ -785,7 +785,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", "sourcetype": "file", "tags": [ "animation", @@ -799,7 +799,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", "sourcetype": "file", "tags": [ "animation", @@ -813,7 +813,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py", "sourcetype": "file", "tags": [ "animation", @@ -827,7 +827,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", "sourcetype": "file", "tags": [ "animation", @@ -849,7 +849,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", "sourcetype": "file", "tags": ["animation", "hierarchy", "toggle", "freeze"], "title": "Toggle Freeze Hierarchy", @@ -857,7 +857,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", "sourcetype": "file", "tags": ["animation", "nucleus", "toggle", "parallel"], "title": "Toggle Parallel Nucleus", @@ -867,21 +867,21 @@ }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "tags": ["animation", "bake", "selection", "worldspace.py"], "title": "Bake Selected To Worldspace", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\timeStepper.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py", "tags": ["animation", "time", "stepper"], "title": "Time Stepper", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\capture_ui.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py", "tags": [ "animation", "capture", @@ -895,63 +895,63 @@ }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\simplePlayblastUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py", "tags": ["animation", "simple", "playblast", "ui"], "title": "Simple Playblast UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\tweenMachineUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py", "tags": ["animation", "tween", "machine"], "title": "Tween Machine UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py", "tags": ["animation", "select", "curves"], "title": "Select All Animation Curves", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\pathAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py", "tags": ["animation", "path", "along"], "title": "Path Animation", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", "tags": ["animation", "offsetSelectedObjectsUI.py"], "title": "Offset Selected Objects UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\key_amplifier_ui.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py", "tags": ["animation", "key", "amplifier"], "title": "Key Amplifier UI", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py", "tags": ["animation", "anim_scene_optimizer.py"], "title": "Anim_Scene_Optimizer", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\zvParentMaster.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py", "tags": ["animation", "zvParentMaster.py"], "title": "ZV Parent Master", "type": "action" }, { "sourcetype": "file", - "command": "$PYPE_SCRIPTS\\animation\\poseLibrary.py", + "command": "$OPENPYPE_SCRIPTS\\animation\\poseLibrary.py", "tags": ["animation", "poseLibrary.py"], "title": "Pose Library", "type": "action" @@ -964,7 +964,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\alignDistributeUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py", "sourcetype": "file", "tags": ["layout", "align", "Distribute", "UI"], "title": "Align Distribute UI", @@ -972,7 +972,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\alignSimpleUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py", "sourcetype": "file", "tags": ["layout", "align", "UI", "Simple"], "title": "Align Simple UI", @@ -980,7 +980,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\center_locator.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py", "sourcetype": "file", "tags": ["layout", "center", "locator"], "title": "Center Locator", @@ -988,7 +988,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\average_locator.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py", "sourcetype": "file", "tags": ["layout", "average", "locator"], "title": "Average Locator", @@ -996,7 +996,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py", "sourcetype": "file", "tags": ["layout", "select", "proximity", "ui"], "title": "Select Within Proximity UI", @@ -1004,7 +1004,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\dupCurveUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py", "sourcetype": "file", "tags": ["layout", "Duplicate", "Curve", "UI"], "title": "Duplicate Curve UI", @@ -1012,7 +1012,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\randomDeselectUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py", "sourcetype": "file", "tags": ["layout", "random", "Deselect", "UI"], "title": "Random Deselect UI", @@ -1020,7 +1020,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\multiReferencerUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py", "sourcetype": "file", "tags": ["layout", "multi", "reference"], "title": "Multi Referencer UI", @@ -1028,7 +1028,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py", "sourcetype": "file", "tags": ["layout", "duplicate", "offset", "UI"], "title": "Duplicate Offset UI", @@ -1036,7 +1036,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\spPaint3d.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py", "sourcetype": "file", "tags": ["layout", "spPaint3d", "paint", "tool"], "title": "SP Paint 3d", @@ -1044,7 +1044,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\randomizeUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py", "sourcetype": "file", "tags": ["layout", "randomize", "UI"], "title": "Randomize UI", @@ -1052,7 +1052,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", + "command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py", "sourcetype": "file", "tags": ["layout", "distribute", "ObjectUI", "within"], "title": "Distribute Within Object UI", @@ -1066,7 +1066,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjects.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py", "sourcetype": "file", "tags": ["particles", "instancerToObjects"], "title": "Instancer To Objects", @@ -1074,7 +1074,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py", "sourcetype": "file", "tags": ["particles", "instancerToObjectsInstances"], "title": "Instancer To Objects Instances", @@ -1082,7 +1082,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", "sourcetype": "file", "tags": [ "particles", @@ -1097,7 +1097,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\particleComponentsToLocators.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\particleComponentsToLocators.py", "sourcetype": "file", "tags": ["particles", "components", "locators"], "title": "Particle Components To Locators", @@ -1105,7 +1105,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", "sourcetype": "file", "tags": ["particles", "objects", "particles", "instancer"], "title": "Objects To Particles And Instancer", @@ -1113,7 +1113,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\spawnParticlesOnMesh.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\spawnParticlesOnMesh.py", "sourcetype": "file", "tags": ["particles", "spawn", "on", "mesh"], "title": "Spawn Particles On Mesh", @@ -1121,7 +1121,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", "sourcetype": "file", "tags": [ "particles", @@ -1132,7 +1132,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\objectsToParticles.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\objectsToParticles.py", "sourcetype": "file", "tags": ["particles", "objectsToParticles"], "title": "Objects To Particles", @@ -1140,7 +1140,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", "sourcetype": "file", "tags": ["particles", "add_particle_cacheFile_attrs"], "title": "Add Particle CacheFile Attributes", @@ -1148,7 +1148,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\mergeParticleSystems.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\mergeParticleSystems.py", "sourcetype": "file", "tags": ["particles", "mergeParticleSystems"], "title": "Merge Particle Systems", @@ -1156,7 +1156,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\particlesToLocators.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\particlesToLocators.py", "sourcetype": "file", "tags": ["particles", "particlesToLocators"], "title": "Particles To Locators", @@ -1164,7 +1164,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", "sourcetype": "file", "tags": ["particles", "instancerToObjectsWithAnimation"], "title": "Instancer To Objects With Animation", @@ -1175,7 +1175,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", "sourcetype": "file", "tags": [ "particles", @@ -1191,7 +1191,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\clearInitialState.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\clearInitialState.py", "sourcetype": "file", "tags": ["particles", "clearInitialState"], "title": "Clear Initial State", @@ -1199,7 +1199,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\particles\\killSelectedParticles.py", + "command": "$OPENPYPE_SCRIPTS\\particles\\killSelectedParticles.py", "sourcetype": "file", "tags": ["particles", "killSelectedParticles"], "title": "Kill Selected Particles", @@ -1213,7 +1213,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\yeti\\yeti_rig_manager.py", + "command": "$OPENPYPE_SCRIPTS\\yeti\\yeti_rig_manager.py", "sourcetype": "file", "tags": ["yeti", "rig", "fur", "manager"], "title": "Open Yeti Rig Manager", @@ -1227,7 +1227,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py", "sourcetype": "file", "tags": ["cleanup", "repair", "containers"], "title": "Find and Repair Containers", @@ -1235,7 +1235,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\selectByType.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectByType.py", "sourcetype": "file", "tags": ["cleanup", "selectByType"], "title": "Select By Type", @@ -1243,7 +1243,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\selectIntermediateObjects.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectIntermediateObjects.py", "sourcetype": "file", "tags": ["cleanup", "selectIntermediateObjects"], "title": "Select Intermediate Objects", @@ -1251,7 +1251,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\selectNonUniqueNames.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\selectNonUniqueNames.py", "sourcetype": "file", "tags": ["cleanup", "select", "non unique", "names"], "title": "Select Non Unique Names", @@ -1262,7 +1262,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeNamespaces.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py", "sourcetype": "file", "tags": ["cleanup", "remove", "namespaces"], "title": "Remove Namespaces", @@ -1270,7 +1270,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", "sourcetype": "file", "tags": ["cleanup", "remove_user_defined_attributes"], "title": "Remove User Defined Attributes", @@ -1278,7 +1278,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py", "sourcetype": "file", "tags": ["cleanup", "removeUnknownNodes"], "title": "Remove Unknown Nodes", @@ -1286,7 +1286,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py", "sourcetype": "file", "tags": ["cleanup", "removeUnloadedReferences"], "title": "Remove Unloaded References", @@ -1294,7 +1294,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", "sourcetype": "file", "tags": ["cleanup", "removeReferencesFailedEdits"], "title": "Remove References Failed Edits", @@ -1302,7 +1302,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py", "sourcetype": "file", "tags": ["cleanup", "removeUnusedLooks"], "title": "Remove Unused Looks", @@ -1310,7 +1310,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", "sourcetype": "file", "tags": ["cleanup", "deleteGhostIntermediateObjects"], "title": "Delete Ghost Intermediate Objects", @@ -1321,7 +1321,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\resetViewportCache.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\resetViewportCache.py", "sourcetype": "file", "tags": ["cleanup", "reset", "viewport", "cache"], "title": "Reset Viewport Cache", @@ -1329,7 +1329,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py", "sourcetype": "file", "tags": ["cleanup", "uniqifyNodeNames"], "title": "Uniqify Node Names", @@ -1337,7 +1337,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py", "sourcetype": "file", "tags": ["cleanup", "auto", "rename", "filenodes"], "title": "Auto Rename File Nodes", @@ -1345,7 +1345,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\update_asset_id.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py", "sourcetype": "file", "tags": ["cleanup", "update", "database", "asset", "id"], "title": "Update Asset ID", @@ -1353,7 +1353,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\colorbleedRename.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\colorbleedRename.py", "sourcetype": "file", "tags": ["cleanup", "rename", "ui"], "title": "Colorbleed Renamer", @@ -1361,7 +1361,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py", "sourcetype": "file", "tags": ["cleanup", "renameShapesToTransform"], "title": "Rename Shapes To Transform", @@ -1369,7 +1369,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\reorderUI.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\reorderUI.py", "sourcetype": "file", "tags": ["cleanup", "reorderUI"], "title": "Reorder UI", @@ -1377,7 +1377,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\cleanup\\pastedCleaner.py", + "command": "$OPENPYPE_SCRIPTS\\cleanup\\pastedCleaner.py", "sourcetype": "file", "tags": ["cleanup", "pastedCleaner"], "title": "Pasted Cleaner", @@ -1396,7 +1396,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", + "command": "$OPENPYPE_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", "sourcetype": "file", "tags": ["others", "yeti", "cache", "selected"], "title": "Cache Selected Yeti Nodes", @@ -1411,7 +1411,7 @@ "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", + "command": "$OPENPYPE_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", "sourcetype": "file", "tags": ["others", "selectSoftSelection"], "title": "Select Soft Selection", @@ -1421,14 +1421,14 @@ }, { "type": "menu", - "command": "$PYPE_SCRIPTS\\others\\display", + "command": "$OPENPYPE_SCRIPTS\\others\\display", "sourcetype": "file", "tags": ["others", "display"], "title": "Display", "items": [ { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", + "command": "$OPENPYPE_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", "sourcetype": "file", "tags": ["others", "wireframe", "selected", "objects"], "title": "Wireframe Selected Objects", @@ -1438,7 +1438,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\archiveSceneUI.py", + "command": "$OPENPYPE_SCRIPTS\\others\\archiveSceneUI.py", "sourcetype": "file", "tags": ["others", "archiveSceneUI"], "title": "Archive Scene UI", @@ -1446,7 +1446,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\getSimilarMeshes.py", + "command": "$OPENPYPE_SCRIPTS\\others\\getSimilarMeshes.py", "sourcetype": "file", "tags": ["others", "getSimilarMeshes"], "title": "Get Similar Meshes", @@ -1454,7 +1454,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\createBoundingBoxEachSelected.py", + "command": "$OPENPYPE_SCRIPTS\\others\\createBoundingBoxEachSelected.py", "sourcetype": "file", "tags": ["others", "createBoundingBoxEachSelected"], "title": "Create BoundingBox Each Selected", @@ -1462,7 +1462,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\curveFromPositionEveryFrame.py", + "command": "$OPENPYPE_SCRIPTS\\others\\curveFromPositionEveryFrame.py", "sourcetype": "file", "tags": ["others", "curveFromPositionEveryFrame"], "title": "Curve From Position", @@ -1470,7 +1470,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\instanceLeafSmartTransform.py", + "command": "$OPENPYPE_SCRIPTS\\others\\instanceLeafSmartTransform.py", "sourcetype": "file", "tags": ["others", "instance", "leaf", "smart", "transform"], "title": "Instance Leaf Smart Transform", @@ -1478,7 +1478,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\instanceSmartTransform.py", + "command": "$OPENPYPE_SCRIPTS\\others\\instanceSmartTransform.py", "sourcetype": "file", "tags": ["others", "instance", "smart", "transform"], "title": "Instance Smart Transform", @@ -1486,7 +1486,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", + "command": "$OPENPYPE_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", "sourcetype": "file", "tags": ["others", "randomizeUVShellsSelectedObjects"], "title": "Randomize UV Shells", @@ -1494,7 +1494,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\centerPivotGroup.py", + "command": "$OPENPYPE_SCRIPTS\\others\\centerPivotGroup.py", "sourcetype": "file", "tags": ["others", "centerPivotGroup"], "title": "Center Pivot Group", @@ -1505,7 +1505,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\locatorsOnSelectedFaces.py", + "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnSelectedFaces.py", "sourcetype": "file", "tags": ["others", "locatorsOnSelectedFaces"], "title": "Locators On Selected Faces", @@ -1513,7 +1513,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", + "command": "$OPENPYPE_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", "sourcetype": "file", "tags": ["others", "locatorsOnEdgeSelectionPrompt"], "title": "Locators On Edge Selection Prompt", @@ -1524,7 +1524,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\copyDeformers.py", + "command": "$OPENPYPE_SCRIPTS\\others\\copyDeformers.py", "sourcetype": "file", "tags": ["others", "copyDeformers"], "title": "Copy Deformers", @@ -1532,7 +1532,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\selectInReferenceEditor.py", + "command": "$OPENPYPE_SCRIPTS\\others\\selectInReferenceEditor.py", "sourcetype": "file", "tags": ["others", "selectInReferenceEditor"], "title": "Select In Reference Editor", @@ -1540,7 +1540,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\selectConstrainingObject.py", + "command": "$OPENPYPE_SCRIPTS\\others\\selectConstrainingObject.py", "sourcetype": "file", "tags": ["others", "selectConstrainingObject"], "title": "Select Constraining Object", @@ -1548,7 +1548,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\deformerSetRelationsUI.py", + "command": "$OPENPYPE_SCRIPTS\\others\\deformerSetRelationsUI.py", "sourcetype": "file", "tags": ["others", "deformerSetRelationsUI"], "title": "Deformer Set Relations UI", @@ -1556,7 +1556,7 @@ }, { "type": "action", - "command": "$PYPE_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", + "command": "$OPENPYPE_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", "sourcetype": "file", "tags": ["others", "recreate", "base", "nodes", "lattice"], "title": "Recreate Base Nodes For Lattice Nodes", diff --git a/pype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py similarity index 98% rename from pype/hosts/maya/api/plugin.py rename to openpype/hosts/maya/api/plugin.py index 81c89017ff..257908c768 100644 --- a/pype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -1,7 +1,7 @@ from avalon import api from avalon.vendor import qargparse import avalon.maya -from pype.api import PypeCreatorMixin +from openpype.api import PypeCreatorMixin def get_reference_node_parents(ref): @@ -256,7 +256,7 @@ class ReferenceLoader(api.Loader): Deprecated; this functionality is replaced by `api.remove()` Arguments: - container (avalon-core:container-1.0): Which container + container (openpype:container-1.0): Which container to remove from scene. """ diff --git a/pype/hosts/maya/api/render_setup_tools.py b/openpype/hosts/maya/api/render_setup_tools.py similarity index 99% rename from pype/hosts/maya/api/render_setup_tools.py rename to openpype/hosts/maya/api/render_setup_tools.py index 9ba48310d6..2ad59810d0 100644 --- a/pype/hosts/maya/api/render_setup_tools.py +++ b/openpype/hosts/maya/api/render_setup_tools.py @@ -5,7 +5,7 @@ Export Maya nodes from Render Setup layer as if flattened in that layer instead of exporting the defaultRenderLayer as Maya forces by default Credits: Roy Nieterau (BigRoy) / Colorbleed -Modified for use in Pype +Modified for use in OpenPype """ diff --git a/pype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py similarity index 98% rename from pype/hosts/maya/api/setdress.py rename to openpype/hosts/maya/api/setdress.py index 2c4dd93b18..be26572039 100644 --- a/pype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -9,7 +9,7 @@ from maya import cmds from avalon import api, io from avalon.maya.lib import unique_namespace -from pype.hosts.maya.api.lib import matrix_equals +from openpype.hosts.maya.api.lib import matrix_equals log = logging.getLogger("PackageLoader") @@ -161,7 +161,7 @@ def _add(instance, representation_id, loaders, namespace, root="|"): """ - from pype.hosts.maya.lib import get_container_transforms + from openpype.hosts.maya.lib import get_container_transforms # Process within the namespace with namespaced(namespace, new=False) as namespace: @@ -358,7 +358,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): """ - from pype.hosts.maya.lib import DEFAULT_MATRIX, get_container_transforms + from openpype.hosts.maya.lib import DEFAULT_MATRIX, get_container_transforms set_namespace = set_container['namespace'] diff --git a/pype/hosts/maya/hooks/pre_copy_mel.py b/openpype/hosts/maya/hooks/pre_copy_mel.py similarity index 81% rename from pype/hosts/maya/hooks/pre_copy_mel.py rename to openpype/hosts/maya/hooks/pre_copy_mel.py index a56f3f71b2..b11e18241e 100644 --- a/pype/hosts/maya/hooks/pre_copy_mel.py +++ b/openpype/hosts/maya/hooks/pre_copy_mel.py @@ -1,5 +1,5 @@ -from pype.lib import PreLaunchHook -from pype.hosts.maya.lib import copy_workspace_mel +from openpype.lib import PreLaunchHook +from openpype.hosts.maya.lib import copy_workspace_mel class PreCopyMel(PreLaunchHook): diff --git a/pype/hosts/maya/lib.py b/openpype/hosts/maya/lib.py similarity index 100% rename from pype/hosts/maya/lib.py rename to openpype/hosts/maya/lib.py diff --git a/pype/hosts/maya/plugins/__init__.py b/openpype/hosts/maya/plugins/__init__.py similarity index 100% rename from pype/hosts/maya/plugins/__init__.py rename to openpype/hosts/maya/plugins/__init__.py diff --git a/pype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py similarity index 96% rename from pype/hosts/maya/plugins/create/create_animation.py rename to openpype/hosts/maya/plugins/create/create_animation.py index 312357c173..5155aec0ab 100644 --- a/pype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_ass.py b/openpype/hosts/maya/plugins/create/create_ass.py similarity index 96% rename from pype/hosts/maya/plugins/create/create_ass.py rename to openpype/hosts/maya/plugins/create/create_ass.py index 2d19d20af8..7f1cb55821 100644 --- a/pype/hosts/maya/plugins/create/create_ass.py +++ b/openpype/hosts/maya/plugins/create/create_ass.py @@ -1,6 +1,6 @@ from collections import OrderedDict -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_assembly.py b/openpype/hosts/maya/plugins/create/create_assembly.py similarity index 82% rename from pype/hosts/maya/plugins/create/create_assembly.py rename to openpype/hosts/maya/plugins/create/create_assembly.py index 254d3b6670..fa9d692792 100644 --- a/pype/hosts/maya/plugins/create/create_assembly.py +++ b/openpype/hosts/maya/plugins/create/create_assembly.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateAssembly(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_camera.py b/openpype/hosts/maya/plugins/create/create_camera.py similarity index 95% rename from pype/hosts/maya/plugins/create/create_camera.py rename to openpype/hosts/maya/plugins/create/create_camera.py index 4f0865c3c4..09f41914e4 100644 --- a/pype/hosts/maya/plugins/create/create_camera.py +++ b/openpype/hosts/maya/plugins/create/create_camera.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_layout.py b/openpype/hosts/maya/plugins/create/create_layout.py similarity index 81% rename from pype/hosts/maya/plugins/create/create_layout.py rename to openpype/hosts/maya/plugins/create/create_layout.py index bae1bc89b1..8d825d23ba 100644 --- a/pype/hosts/maya/plugins/create/create_layout.py +++ b/openpype/hosts/maya/plugins/create/create_layout.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateLayout(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_look.py b/openpype/hosts/maya/plugins/create/create_look.py similarity index 93% rename from pype/hosts/maya/plugins/create/create_look.py rename to openpype/hosts/maya/plugins/create/create_look.py index d81397c1c8..96266aa799 100644 --- a/pype/hosts/maya/plugins/create/create_look.py +++ b/openpype/hosts/maya/plugins/create/create_look.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_mayaascii.py b/openpype/hosts/maya/plugins/create/create_mayaascii.py similarity index 82% rename from pype/hosts/maya/plugins/create/create_mayaascii.py rename to openpype/hosts/maya/plugins/create/create_mayaascii.py index 0248ebe45e..f51e126c00 100644 --- a/pype/hosts/maya/plugins/create/create_mayaascii.py +++ b/openpype/hosts/maya/plugins/create/create_mayaascii.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateMayaAscii(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_model.py b/openpype/hosts/maya/plugins/create/create_model.py similarity index 93% rename from pype/hosts/maya/plugins/create/create_model.py rename to openpype/hosts/maya/plugins/create/create_model.py index f7b5847ae1..f1d9d22c1c 100644 --- a/pype/hosts/maya/plugins/create/create_model.py +++ b/openpype/hosts/maya/plugins/create/create_model.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateModel(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py similarity index 96% rename from pype/hosts/maya/plugins/create/create_pointcache.py rename to openpype/hosts/maya/plugins/create/create_pointcache.py index c179bec9f4..9afea731fd 100644 --- a/pype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py similarity index 97% rename from pype/hosts/maya/plugins/create/create_render.py rename to openpype/hosts/maya/plugins/create/create_render.py index 1d06cf7080..907f9cf781 100644 --- a/pype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -8,11 +8,11 @@ import requests from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) -from pype.api import get_system_settings +from openpype.api import get_system_settings class CreateRender(plugin.Creator): @@ -274,7 +274,7 @@ class CreateRender(plugin.Creator): # authentication token expired so we need to login to Muster # again to get it. We use Pype API call to show login window. api_url = "{}/muster/show_login".format( - os.environ["PYPE_WEBSERVER_URL"]) + os.environ["OPENPYPE_WEBSERVER_URL"]) self.log.debug(api_url) login_response = self._requests_get(api_url, timeout=1) if login_response.status_code != 200: @@ -296,7 +296,7 @@ class CreateRender(plugin.Creator): """ if "verify" not in kwargs: kwargs["verify"] = ( - False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True + False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True ) # noqa return requests.post(*args, **kwargs) @@ -315,6 +315,6 @@ class CreateRender(plugin.Creator): """ if "verify" not in kwargs: kwargs["verify"] = ( - False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True + False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True ) # noqa return requests.get(*args, **kwargs) diff --git a/pype/hosts/maya/plugins/create/create_rendersetup.py b/openpype/hosts/maya/plugins/create/create_rendersetup.py similarity index 97% rename from pype/hosts/maya/plugins/create/create_rendersetup.py rename to openpype/hosts/maya/plugins/create/create_rendersetup.py index 37eac4aae8..494f90d87b 100644 --- a/pype/hosts/maya/plugins/create/create_rendersetup.py +++ b/openpype/hosts/maya/plugins/create/create_rendersetup.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py similarity index 95% rename from pype/hosts/maya/plugins/create/create_review.py rename to openpype/hosts/maya/plugins/create/create_review.py index 3f91c8af15..05b05be7a5 100644 --- a/pype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py similarity index 94% rename from pype/hosts/maya/plugins/create/create_rig.py rename to openpype/hosts/maya/plugins/create/create_rig.py index 186e8007ee..ae1670961f 100644 --- a/pype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -1,6 +1,6 @@ from maya import cmds -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_setdress.py b/openpype/hosts/maya/plugins/create/create_setdress.py similarity index 83% rename from pype/hosts/maya/plugins/create/create_setdress.py rename to openpype/hosts/maya/plugins/create/create_setdress.py index e235d01b20..8274a6dd83 100644 --- a/pype/hosts/maya/plugins/create/create_setdress.py +++ b/openpype/hosts/maya/plugins/create/create_setdress.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateSetDress(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py similarity index 86% rename from pype/hosts/maya/plugins/create/create_unreal_staticmesh.py rename to openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py index 06cec0f673..db1684bbc8 100644 --- a/pype/hosts/maya/plugins/create/create_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/create/create_unreal_staticmesh.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateUnrealStaticMesh(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_vrayproxy.py b/openpype/hosts/maya/plugins/create/create_vrayproxy.py similarity index 91% rename from pype/hosts/maya/plugins/create/create_vrayproxy.py rename to openpype/hosts/maya/plugins/create/create_vrayproxy.py index 0af670a0d3..5c0365b495 100644 --- a/pype/hosts/maya/plugins/create/create_vrayproxy.py +++ b/openpype/hosts/maya/plugins/create/create_vrayproxy.py @@ -1,4 +1,4 @@ -from pype.hosts.maya.api import plugin +from openpype.hosts.maya.api import plugin class CreateVrayProxy(plugin.Creator): diff --git a/pype/hosts/maya/plugins/create/create_vrayscene.py b/openpype/hosts/maya/plugins/create/create_vrayscene.py similarity index 96% rename from pype/hosts/maya/plugins/create/create_vrayscene.py rename to openpype/hosts/maya/plugins/create/create_vrayscene.py index f37a6c4e20..f9d3c7b8f0 100644 --- a/pype/hosts/maya/plugins/create/create_vrayscene.py +++ b/openpype/hosts/maya/plugins/create/create_vrayscene.py @@ -8,11 +8,11 @@ import requests from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) -from pype.api import get_system_settings +from openpype.api import get_system_settings class CreateVRayScene(plugin.Creator): @@ -191,7 +191,7 @@ class CreateVRayScene(plugin.Creator): # authentication token expired so we need to login to Muster # again to get it. We use Pype API call to show login window. api_url = "{}/muster/show_login".format( - os.environ["PYPE_WEBSERVER_URL"]) + os.environ["OPENPYPE_WEBSERVER_URL"]) self.log.debug(api_url) login_response = self._requests_get(api_url, timeout=1) if login_response.status_code != 200: @@ -213,7 +213,7 @@ class CreateVRayScene(plugin.Creator): """ if "verify" not in kwargs: kwargs["verify"] = ( - False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True + False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True ) # noqa return requests.post(*args, **kwargs) @@ -232,6 +232,6 @@ class CreateVRayScene(plugin.Creator): """ if "verify" not in kwargs: kwargs["verify"] = ( - False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True + False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True ) # noqa return requests.get(*args, **kwargs) diff --git a/pype/hosts/maya/plugins/create/create_yeti_cache.py b/openpype/hosts/maya/plugins/create/create_yeti_cache.py similarity index 94% rename from pype/hosts/maya/plugins/create/create_yeti_cache.py rename to openpype/hosts/maya/plugins/create/create_yeti_cache.py index cf4f32b207..a37d8082db 100644 --- a/pype/hosts/maya/plugins/create/create_yeti_cache.py +++ b/openpype/hosts/maya/plugins/create/create_yeti_cache.py @@ -1,6 +1,6 @@ from collections import OrderedDict -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/create/create_yeti_rig.py b/openpype/hosts/maya/plugins/create/create_yeti_rig.py similarity index 93% rename from pype/hosts/maya/plugins/create/create_yeti_rig.py rename to openpype/hosts/maya/plugins/create/create_yeti_rig.py index eda51824c1..2326958963 100644 --- a/pype/hosts/maya/plugins/create/create_yeti_rig.py +++ b/openpype/hosts/maya/plugins/create/create_yeti_rig.py @@ -1,6 +1,6 @@ from maya import cmds -from pype.hosts.maya.api import ( +from openpype.hosts.maya.api import ( lib, plugin ) diff --git a/pype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py similarity index 93% rename from pype/hosts/maya/plugins/load/_load_animation.py rename to openpype/hosts/maya/plugins/load/_load_animation.py index bef97d79bb..b1784f1590 100644 --- a/pype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -1,7 +1,7 @@ -import pype.hosts.maya.api.plugin +import openpype.hosts.maya.api.plugin -class AbcLoader(pype.hosts.maya.api.plugin.ReferenceLoader): +class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["animation", diff --git a/pype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py similarity index 100% rename from pype/hosts/maya/plugins/load/actions.py rename to openpype/hosts/maya/plugins/load/actions.py diff --git a/pype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_ass.py rename to openpype/hosts/maya/plugins/load/load_ass.py index 8aaa99369c..b4bbd93f99 100644 --- a/pype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -1,11 +1,11 @@ from avalon import api -import pype.hosts.maya.api.plugin +import openpype.hosts.maya.api.plugin import os -from pype.api import get_project_settings +from openpype.api import get_project_settings import clique -class AssProxyLoader(pype.hosts.maya.api.plugin.ReferenceLoader): +class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load the Proxy""" families = ["ass"] diff --git a/pype/hosts/maya/plugins/load/load_assembly.py b/openpype/hosts/maya/plugins/load/load_assembly.py similarity index 94% rename from pype/hosts/maya/plugins/load/load_assembly.py rename to openpype/hosts/maya/plugins/load/load_assembly.py index a5f2394444..2f0ca3922e 100644 --- a/pype/hosts/maya/plugins/load/load_assembly.py +++ b/openpype/hosts/maya/plugins/load/load_assembly.py @@ -23,7 +23,7 @@ class AssemblyLoader(api.Loader): suffix="_", ) - from pype.hosts.maya.api import setdress + from openpype.hosts.maya.api import setdress containers = setdress.load_package(filepath=self.fname, name=name, @@ -45,7 +45,7 @@ class AssemblyLoader(api.Loader): def update(self, container, representation): - from pype import setdress + from openpype import setdress return setdress.update_package(container, representation) @@ -53,7 +53,7 @@ class AssemblyLoader(api.Loader): """Remove all sub containers""" from avalon import api - from pype import setdress + from openpype import setdress import maya.cmds as cmds # Remove all members diff --git a/pype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py similarity index 100% rename from pype/hosts/maya/plugins/load/load_audio.py rename to openpype/hosts/maya/plugins/load/load_audio.py diff --git a/pype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_gpucache.py rename to openpype/hosts/maya/plugins/load/load_gpucache.py index e6fde1eaa8..5b1b29e184 100644 --- a/pype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -1,6 +1,6 @@ import os from avalon import api -from pype.api import get_project_settings +from openpype.api import get_project_settings class GpuCacheLoader(api.Loader): """Load model Alembic as gpuCache""" diff --git a/pype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py similarity index 100% rename from pype/hosts/maya/plugins/load/load_image_plane.py rename to openpype/hosts/maya/plugins/load/load_image_plane.py diff --git a/pype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py similarity index 93% rename from pype/hosts/maya/plugins/load/load_look.py rename to openpype/hosts/maya/plugins/load/load_look.py index ab8f971d62..4392d1f78d 100644 --- a/pype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -1,13 +1,13 @@ -import pype.hosts.maya.api.plugin +import openpype.hosts.maya.api.plugin from avalon import api, io import json -import pype.hosts.maya.api.lib +import openpype.hosts.maya.api.lib from collections import defaultdict -from pype.widgets.message_window import ScrollMessageBox +from openpype.widgets.message_window import ScrollMessageBox from Qt import QtWidgets -class LookLoader(pype.hosts.maya.api.plugin.ReferenceLoader): +class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Specific loader for lookdev""" families = ["look"] @@ -120,9 +120,9 @@ class LookLoader(pype.hosts.maya.api.plugin.ReferenceLoader): cmds.file(cr=reference_node) # cleanReference # reapply shading groups from json representation on orig nodes - pype.hosts.maya.api.lib.apply_shaders(relationships, - shader_nodes, - orig_nodes) + openpype.hosts.maya.api.lib.apply_shaders(relationships, + shader_nodes, + orig_nodes) msg = ["During reference update some edits failed.", "All successful edits were kept intact.\n", @@ -138,8 +138,8 @@ class LookLoader(pype.hosts.maya.api.plugin.ReferenceLoader): # region compute lookup nodes_by_id = defaultdict(list) for n in nodes: - nodes_by_id[pype.hosts.maya.api.lib.get_id(n)].append(n) - pype.hosts.maya.api.lib.apply_attributes(attributes, nodes_by_id) + nodes_by_id[openpype.hosts.maya.api.lib.get_id(n)].append(n) + openpype.hosts.maya.api.lib.apply_attributes(attributes, nodes_by_id) # Update metadata cmds.setAttr("{}.representation".format(node), diff --git a/pype/hosts/maya/plugins/load/load_matchmove.py b/openpype/hosts/maya/plugins/load/load_matchmove.py similarity index 100% rename from pype/hosts/maya/plugins/load/load_matchmove.py rename to openpype/hosts/maya/plugins/load/load_matchmove.py diff --git a/pype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py similarity index 96% rename from pype/hosts/maya/plugins/load/load_reference.py rename to openpype/hosts/maya/plugins/load/load_reference.py index 7ef6417b9f..61e0290296 100644 --- a/pype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -1,12 +1,12 @@ -import pype.hosts.maya.api.plugin +import openpype.hosts.maya.api.plugin from avalon import api, maya from maya import cmds import os -from pype.api import get_project_settings -from pype.lib import get_creator_by_name +from openpype.api import get_project_settings +from openpype.lib import get_creator_by_name -class ReferenceLoader(pype.hosts.maya.api.plugin.ReferenceLoader): +class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """Load the model""" families = ["model", diff --git a/pype/hosts/maya/plugins/load/load_rendersetup.py b/openpype/hosts/maya/plugins/load/load_rendersetup.py similarity index 96% rename from pype/hosts/maya/plugins/load/load_rendersetup.py rename to openpype/hosts/maya/plugins/load/load_rendersetup.py index c540c3880e..574ae9bd3d 100644 --- a/pype/hosts/maya/plugins/load/load_rendersetup.py +++ b/openpype/hosts/maya/plugins/load/load_rendersetup.py @@ -12,7 +12,7 @@ import sys from avalon import api from avalon.maya import lib -from pype.hosts.maya.api import lib as pypelib +from openpype.hosts.maya.api import lib as pypelib from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup @@ -32,7 +32,8 @@ class RenderSetupLoader(api.Loader): def load(self, context, name, namespace, data): """Load RenderSetup settings.""" from avalon.maya.pipeline import containerise - # from pype.hosts.maya.api.lib import namespaced + + # from openpype.hosts.maya.api.lib import namespaced asset = context['asset']['name'] namespace = namespace or lib.unique_namespace( diff --git a/pype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_vdb_to_redshift.py rename to openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py index 17c78d7165..b705b55f4d 100644 --- a/pype/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -1,6 +1,6 @@ from avalon import api import os -from pype.api import get_project_settings +from openpype.api import get_project_settings class LoadVDBtoRedShift(api.Loader): """Load OpenVDB in a Redshift Volume Shape""" diff --git a/pype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_vdb_to_vray.py rename to openpype/hosts/maya/plugins/load/load_vdb_to_vray.py index 2959ef42ec..82ccdb481b 100644 --- a/pype/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -1,5 +1,5 @@ from avalon import api -from pype.api import get_project_settings +from openpype.api import get_project_settings import os diff --git a/pype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_vrayproxy.py rename to openpype/hosts/maya/plugins/load/load_vrayproxy.py index 270d0f9baa..2bff6e0a77 100644 --- a/pype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -1,6 +1,6 @@ from avalon.maya import lib from avalon import api -from pype.api import get_project_settings +from openpype.api import get_project_settings import os import maya.cmds as cmds @@ -19,7 +19,7 @@ class VRayProxyLoader(api.Loader): def load(self, context, name, namespace, data): from avalon.maya.pipeline import containerise - from pype.hosts.maya.api.lib import namespaced + from openpype.hosts.maya.api.lib import namespaced try: family = context["representation"]["context"]["family"] diff --git a/pype/hosts/maya/plugins/load/load_vrayscene.py b/openpype/hosts/maya/plugins/load/load_vrayscene.py similarity index 98% rename from pype/hosts/maya/plugins/load/load_vrayscene.py rename to openpype/hosts/maya/plugins/load/load_vrayscene.py index b258119a1a..b0f0c2a54b 100644 --- a/pype/hosts/maya/plugins/load/load_vrayscene.py +++ b/openpype/hosts/maya/plugins/load/load_vrayscene.py @@ -1,6 +1,6 @@ from avalon.maya import lib from avalon import api -from pype.api import config +from openpype.api import config import os import maya.cmds as cmds @@ -19,7 +19,7 @@ class VRaySceneLoader(api.Loader): def load(self, context, name, namespace, data): from avalon.maya.pipeline import containerise - from pype.hosts.maya.lib import namespaced + from openpype.hosts.maya.lib import namespaced try: family = context["representation"]["context"]["family"] diff --git a/pype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py similarity index 99% rename from pype/hosts/maya/plugins/load/load_yeti_cache.py rename to openpype/hosts/maya/plugins/load/load_yeti_cache.py index 16d51ad56d..43c8aa16a0 100644 --- a/pype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -8,8 +8,8 @@ from maya import cmds from avalon import api, io from avalon.maya import lib as avalon_lib, pipeline -from pype.hosts.maya.api import lib -from pype.api import get_project_settings +from openpype.hosts.maya.api import lib +from openpype.api import get_project_settings from pprint import pprint diff --git a/pype/hosts/maya/plugins/load/load_yeti_rig.py b/openpype/hosts/maya/plugins/load/load_yeti_rig.py similarity index 93% rename from pype/hosts/maya/plugins/load/load_yeti_rig.py rename to openpype/hosts/maya/plugins/load/load_yeti_rig.py index d025ed13cc..a329be4cf5 100644 --- a/pype/hosts/maya/plugins/load/load_yeti_rig.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_rig.py @@ -1,12 +1,12 @@ import os from collections import defaultdict -from pype.api import get_project_settings -import pype.hosts.maya.api.plugin -from pype.hosts.maya.api import lib +from openpype.api import get_project_settings +import openpype.hosts.maya.api.plugin +from openpype.hosts.maya.api import lib -class YetiRigLoader(pype.hosts.maya.api.plugin.ReferenceLoader): +class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """ This loader will load Yeti rig. You can select something in scene and if it has same ID as mesh published with rig, their shapes will be linked diff --git a/pype/hosts/maya/plugins/publish/__init__.py b/openpype/hosts/maya/plugins/publish/__init__.py similarity index 100% rename from pype/hosts/maya/plugins/publish/__init__.py rename to openpype/hosts/maya/plugins/publish/__init__.py diff --git a/pype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_animation.py rename to openpype/hosts/maya/plugins/publish/collect_animation.py diff --git a/pype/hosts/maya/plugins/publish/collect_ass.py b/openpype/hosts/maya/plugins/publish/collect_ass.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_ass.py rename to openpype/hosts/maya/plugins/publish/collect_ass.py diff --git a/pype/hosts/maya/plugins/publish/collect_assembly.py b/openpype/hosts/maya/plugins/publish/collect_assembly.py similarity index 98% rename from pype/hosts/maya/plugins/publish/collect_assembly.py rename to openpype/hosts/maya/plugins/publish/collect_assembly.py index be3408a0bd..22af1239b1 100644 --- a/pype/hosts/maya/plugins/publish/collect_assembly.py +++ b/openpype/hosts/maya/plugins/publish/collect_assembly.py @@ -3,7 +3,7 @@ import pyblish.api from maya import cmds, mel from avalon import maya as avalon -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib # TODO : Publish of assembly: -unique namespace for all assets, VALIDATOR! diff --git a/pype/hosts/maya/plugins/publish/collect_file_dependencies.py b/openpype/hosts/maya/plugins/publish/collect_file_dependencies.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_file_dependencies.py rename to openpype/hosts/maya/plugins/publish/collect_file_dependencies.py diff --git a/pype/hosts/maya/plugins/publish/collect_ftrack_family.py b/openpype/hosts/maya/plugins/publish/collect_ftrack_family.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_ftrack_family.py rename to openpype/hosts/maya/plugins/publish/collect_ftrack_family.py diff --git a/pype/hosts/maya/plugins/publish/collect_history.py b/openpype/hosts/maya/plugins/publish/collect_history.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_history.py rename to openpype/hosts/maya/plugins/publish/collect_history.py diff --git a/pype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_instances.py rename to openpype/hosts/maya/plugins/publish/collect_instances.py diff --git a/pype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py similarity index 99% rename from pype/hosts/maya/plugins/publish/collect_look.py rename to openpype/hosts/maya/plugins/publish/collect_look.py index 35abc5a991..acc6d8f128 100644 --- a/pype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -4,7 +4,7 @@ import glob from maya import cmds import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib SHAPE_ATTRS = ["castsShadows", "receiveShadows", diff --git a/pype/hosts/maya/plugins/publish/collect_maya_units.py b/openpype/hosts/maya/plugins/publish/collect_maya_units.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_maya_units.py rename to openpype/hosts/maya/plugins/publish/collect_maya_units.py diff --git a/pype/hosts/maya/plugins/publish/collect_maya_workspace.py b/openpype/hosts/maya/plugins/publish/collect_maya_workspace.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_maya_workspace.py rename to openpype/hosts/maya/plugins/publish/collect_maya_workspace.py diff --git a/pype/hosts/maya/plugins/publish/collect_mayaascii.py b/openpype/hosts/maya/plugins/publish/collect_mayaascii.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_mayaascii.py rename to openpype/hosts/maya/plugins/publish/collect_mayaascii.py diff --git a/pype/hosts/maya/plugins/publish/collect_model.py b/openpype/hosts/maya/plugins/publish/collect_model.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_model.py rename to openpype/hosts/maya/plugins/publish/collect_model.py diff --git a/pype/hosts/maya/plugins/publish/collect_remove_marked.py b/openpype/hosts/maya/plugins/publish/collect_remove_marked.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_remove_marked.py rename to openpype/hosts/maya/plugins/publish/collect_remove_marked.py diff --git a/pype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py similarity index 99% rename from pype/hosts/maya/plugins/publish/collect_render.py rename to openpype/hosts/maya/plugins/publish/collect_render.py index c24cf1dfef..75749a952e 100644 --- a/pype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -49,8 +49,8 @@ import maya.app.renderSetup.model.renderSetup as renderSetup import pyblish.api from avalon import maya, api -from pype.hosts.maya.api.expected_files import ExpectedFiles -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api.expected_files import ExpectedFiles +from openpype.hosts.maya.api import lib class CollectMayaRender(pyblish.api.ContextPlugin): diff --git a/pype/hosts/maya/plugins/publish/collect_render_layer_aovs.py b/openpype/hosts/maya/plugins/publish/collect_render_layer_aovs.py similarity index 98% rename from pype/hosts/maya/plugins/publish/collect_render_layer_aovs.py rename to openpype/hosts/maya/plugins/publish/collect_render_layer_aovs.py index d776b43907..9666499c42 100644 --- a/pype/hosts/maya/plugins/publish/collect_render_layer_aovs.py +++ b/openpype/hosts/maya/plugins/publish/collect_render_layer_aovs.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): diff --git a/pype/hosts/maya/plugins/publish/collect_renderable_camera.py b/openpype/hosts/maya/plugins/publish/collect_renderable_camera.py similarity index 93% rename from pype/hosts/maya/plugins/publish/collect_renderable_camera.py rename to openpype/hosts/maya/plugins/publish/collect_renderable_camera.py index 5b3468424a..0bf06aa273 100644 --- a/pype/hosts/maya/plugins/publish/collect_renderable_camera.py +++ b/openpype/hosts/maya/plugins/publish/collect_renderable_camera.py @@ -2,7 +2,7 @@ import pyblish.api from maya import cmds -from pype.hosts.maya.api.attributes import get_attr_in_layer +from openpype.hosts.maya.api.attributes import get_attr_in_layer class CollectRenderableCamera(pyblish.api.InstancePlugin): diff --git a/pype/hosts/maya/plugins/publish/collect_review.py b/openpype/hosts/maya/plugins/publish/collect_review.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_review.py rename to openpype/hosts/maya/plugins/publish/collect_review.py diff --git a/pype/hosts/maya/plugins/publish/collect_rig.py b/openpype/hosts/maya/plugins/publish/collect_rig.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_rig.py rename to openpype/hosts/maya/plugins/publish/collect_rig.py diff --git a/pype/hosts/maya/plugins/publish/collect_scene.py b/openpype/hosts/maya/plugins/publish/collect_scene.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_scene.py rename to openpype/hosts/maya/plugins/publish/collect_scene.py diff --git a/pype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py rename to openpype/hosts/maya/plugins/publish/collect_unreal_staticmesh.py diff --git a/pype/hosts/maya/plugins/publish/collect_vrayscene.py b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py similarity index 99% rename from pype/hosts/maya/plugins/publish/collect_vrayscene.py rename to openpype/hosts/maya/plugins/publish/collect_vrayscene.py index 7960bb7937..7097d7ce9c 100644 --- a/pype/hosts/maya/plugins/publish/collect_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/collect_vrayscene.py @@ -7,7 +7,7 @@ from maya import cmds import pyblish.api from avalon import api -from pype.hosts.maya import lib +from openpype.hosts.maya import lib class CollectVrayScene(pyblish.api.InstancePlugin): diff --git a/pype/hosts/maya/plugins/publish/collect_workscene_fps.py b/openpype/hosts/maya/plugins/publish/collect_workscene_fps.py similarity index 100% rename from pype/hosts/maya/plugins/publish/collect_workscene_fps.py rename to openpype/hosts/maya/plugins/publish/collect_workscene_fps.py diff --git a/pype/hosts/maya/plugins/publish/collect_yeti_cache.py b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py similarity index 97% rename from pype/hosts/maya/plugins/publish/collect_yeti_cache.py rename to openpype/hosts/maya/plugins/publish/collect_yeti_cache.py index 2cfc56e486..e6b5ca4260 100644 --- a/pype/hosts/maya/plugins/publish/collect_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_cache.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib SETTINGS = {"renderDensity", "renderWidth", diff --git a/pype/hosts/maya/plugins/publish/collect_yeti_rig.py b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py similarity index 99% rename from pype/hosts/maya/plugins/publish/collect_yeti_rig.py rename to openpype/hosts/maya/plugins/publish/collect_yeti_rig.py index 15bcdc51dd..0d240b1a32 100644 --- a/pype/hosts/maya/plugins/publish/collect_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/collect_yeti_rig.py @@ -5,7 +5,7 @@ from maya import cmds import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib SETTINGS = {"renderDensity", diff --git a/pype/hosts/maya/plugins/publish/determine_future_version.py b/openpype/hosts/maya/plugins/publish/determine_future_version.py similarity index 100% rename from pype/hosts/maya/plugins/publish/determine_future_version.py rename to openpype/hosts/maya/plugins/publish/determine_future_version.py diff --git a/pype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py similarity index 96% rename from pype/hosts/maya/plugins/publish/extract_animation.py rename to openpype/hosts/maya/plugins/publish/extract_animation.py index f56ba2f400..b86ded1fb0 100644 --- a/pype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -3,11 +3,11 @@ import os from maya import cmds import avalon.maya -import pype.api -from pype.hosts.maya.api.lib import extract_alembic +import openpype.api +from openpype.hosts.maya.api.lib import extract_alembic -class ExtractAnimation(pype.api.Extractor): +class ExtractAnimation(openpype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, diff --git a/pype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_ass.py rename to openpype/hosts/maya/plugins/publish/extract_ass.py index 4cf394aefe..7461ccdf78 100644 --- a/pype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,12 +1,12 @@ import os import avalon.maya -import pype.api +import openpype.api from maya import cmds -class ExtractAssStandin(pype.api.Extractor): +class ExtractAssStandin(openpype.api.Extractor): """Extract the content of the instance to a ass file Things to pay attention to: diff --git a/pype/hosts/maya/plugins/publish/extract_assembly.py b/openpype/hosts/maya/plugins/publish/extract_assembly.py similarity index 94% rename from pype/hosts/maya/plugins/publish/extract_assembly.py rename to openpype/hosts/maya/plugins/publish/extract_assembly.py index c72f1f1835..482930b76e 100644 --- a/pype/hosts/maya/plugins/publish/extract_assembly.py +++ b/openpype/hosts/maya/plugins/publish/extract_assembly.py @@ -2,13 +2,13 @@ import json import os -import pype.api -from pype.hosts.maya.api.lib import extract_alembic +import openpype.api +from openpype.hosts.maya.api.lib import extract_alembic from maya import cmds -class ExtractAssembly(pype.api.Extractor): +class ExtractAssembly(openpype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, diff --git a/pype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py similarity index 95% rename from pype/hosts/maya/plugins/publish/extract_assproxy.py rename to openpype/hosts/maya/plugins/publish/extract_assproxy.py index 3579d26534..f53f385b8b 100644 --- a/pype/hosts/maya/plugins/publish/extract_assproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_assproxy.py @@ -4,16 +4,16 @@ from maya import cmds import contextlib import avalon.maya -import pype.api +import openpype.api -class ExtractAssProxy(pype.api.Extractor): +class ExtractAssProxy(openpype.api.Extractor): """Extract proxy model as Maya Ascii to use as arnold standin """ - order = pype.api.Extractor.order + 0.2 + order = openpype.api.Extractor.order + 0.2 label = "Ass Proxy (Maya ASCII)" hosts = ["maya"] families = ["ass"] diff --git a/pype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py similarity index 96% rename from pype/hosts/maya/plugins/publish/extract_camera_alembic.py rename to openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index c37f2ecb76..8950ed6254 100644 --- a/pype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -3,12 +3,12 @@ import os from maya import cmds import avalon.maya -import pype.api +import openpype.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib -class ExtractCameraAlembic(pype.api.Extractor): +class ExtractCameraAlembic(openpype.api.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's diff --git a/pype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_camera_mayaScene.py rename to openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 16ebc39419..888dc636b2 100644 --- a/pype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -6,8 +6,8 @@ import itertools from maya import cmds import avalon.maya -import pype.api -from pype.hosts.maya.api import lib +import openpype.api +from openpype.hosts.maya.api import lib def massage_ma_file(path): @@ -78,7 +78,7 @@ def unlock(plug): cmds.disconnectAttr(source, destination) -class ExtractCameraMayaScene(pype.api.Extractor): +class ExtractCameraMayaScene(openpype.api.Extractor): """Extract a Camera as Maya Scene. This will create a duplicate of the camera that will be baked *with* diff --git a/pype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py similarity index 99% rename from pype/hosts/maya/plugins/publish/extract_fbx.py rename to openpype/hosts/maya/plugins/publish/extract_fbx.py index 6a75bfce0e..e5f3b0cda4 100644 --- a/pype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -6,10 +6,10 @@ import maya.mel as mel import pyblish.api import avalon.maya -import pype.api +import openpype.api -class ExtractFBX(pype.api.Extractor): +class ExtractFBX(openpype.api.Extractor): """Extract FBX from Maya. This extracts reproducible FBX exports ignoring any of the settings set diff --git a/pype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_look.py rename to openpype/hosts/maya/plugins/publish/extract_look.py index 2c4837b7a7..79488a372c 100644 --- a/pype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -13,8 +13,8 @@ import pyblish.api import avalon.maya from avalon import io, api -import pype.api -from pype.hosts.maya.api import lib +import openpype.api +from openpype.hosts.maya.api import lib # Modes for transfer COPY = 1 @@ -105,7 +105,7 @@ def no_workspace_dir(): os.rmdir(fake_workspace_dir) -class ExtractLook(pype.api.Extractor): +class ExtractLook(openpype.api.Extractor): """Extract Look (Maya Ascii + JSON) Only extracts the sets (shadingEngines and alike) alongside a .json file @@ -343,7 +343,7 @@ class ExtractLook(pype.api.Extractor): args = [] if do_maketx: args.append("maketx") - texture_hash = pype.api.source_hash(filepath, *args) + texture_hash = openpype.api.source_hash(filepath, *args) # If source has been published before with the same settings, # then don't reprocess but hardlink from the original diff --git a/pype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py similarity index 97% rename from pype/hosts/maya/plugins/publish/extract_maya_scene_raw.py rename to openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 8df9f8e715..c85bc0387d 100644 --- a/pype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -5,10 +5,10 @@ import os from maya import cmds import avalon.maya -import pype.api +import openpype.api -class ExtractMayaSceneRaw(pype.api.Extractor): +class ExtractMayaSceneRaw(openpype.api.Extractor): """Extract as Maya Scene (raw). This will preserve all references, construction history, etc. diff --git a/pype/hosts/maya/plugins/publish/extract_model.py b/openpype/hosts/maya/plugins/publish/extract_model.py similarity index 97% rename from pype/hosts/maya/plugins/publish/extract_model.py rename to openpype/hosts/maya/plugins/publish/extract_model.py index 1c268a9e0e..1773297826 100644 --- a/pype/hosts/maya/plugins/publish/extract_model.py +++ b/openpype/hosts/maya/plugins/publish/extract_model.py @@ -5,11 +5,11 @@ import os from maya import cmds import avalon.maya -import pype.api -from pype.hosts.maya.api import lib +import openpype.api +from openpype.hosts.maya.api import lib -class ExtractModel(pype.api.Extractor): +class ExtractModel(openpype.api.Extractor): """Extract as Model (Maya Scene). Only extracts contents based on the original "setMembers" data to ensure diff --git a/pype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_playblast.py rename to openpype/hosts/maya/plugins/publish/extract_playblast.py index 8402e41285..358fca6c2a 100644 --- a/pype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -4,14 +4,14 @@ import contextlib import clique import capture -from pype.hosts.maya.api import lib -import pype.api +from openpype.hosts.maya.api import lib +import openpype.api from maya import cmds import pymel.core as pm -class ExtractPlayblast(pype.api.Extractor): +class ExtractPlayblast(openpype.api.Extractor): """Extract viewport playblast. Takes review camera and creates review Quicktime video based on viewport diff --git a/pype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py similarity index 95% rename from pype/hosts/maya/plugins/publish/extract_pointcache.py rename to openpype/hosts/maya/plugins/publish/extract_pointcache.py index 58742c8567..dea52f2154 100644 --- a/pype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -3,11 +3,11 @@ import os from maya import cmds import avalon.maya -import pype.api -from pype.hosts.maya.api.lib import extract_alembic +import openpype.api +from openpype.hosts.maya.api.lib import extract_alembic -class ExtractAlembic(pype.api.Extractor): +class ExtractAlembic(openpype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, diff --git a/pype/hosts/maya/plugins/publish/extract_rendersetup.py b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py similarity index 93% rename from pype/hosts/maya/plugins/publish/extract_rendersetup.py rename to openpype/hosts/maya/plugins/publish/extract_rendersetup.py index c8d8db0bbb..6bdd5f590e 100644 --- a/pype/hosts/maya/plugins/publish/extract_rendersetup.py +++ b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py @@ -1,10 +1,10 @@ import json import os -import pype.api +import openpype.api import maya.app.renderSetup.model.renderSetup as renderSetup -class ExtractRenderSetup(pype.api.Extractor): +class ExtractRenderSetup(openpype.api.Extractor): """ Produce renderSetup template file diff --git a/pype/hosts/maya/plugins/publish/extract_rig.py b/openpype/hosts/maya/plugins/publish/extract_rig.py similarity index 97% rename from pype/hosts/maya/plugins/publish/extract_rig.py rename to openpype/hosts/maya/plugins/publish/extract_rig.py index fd7fc051aa..b28b60114e 100644 --- a/pype/hosts/maya/plugins/publish/extract_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig.py @@ -5,10 +5,10 @@ import os from maya import cmds import avalon.maya -import pype.api +import openpype.api -class ExtractRig(pype.api.Extractor): +class ExtractRig(openpype.api.Extractor): """Extract rig as Maya Scene.""" label = "Extract Rig (Maya Scene)" diff --git a/pype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_thumbnail.py rename to openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 9f6d16275f..35d720726b 100644 --- a/pype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -4,14 +4,14 @@ import glob import capture -from pype.hosts.maya.api import lib -import pype.api +from openpype.hosts.maya.api import lib +import openpype.api from maya import cmds import pymel.core as pm -class ExtractThumbnail(pype.api.Extractor): +class ExtractThumbnail(openpype.api.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's diff --git a/pype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py similarity index 96% rename from pype/hosts/maya/plugins/publish/extract_vrayproxy.py rename to openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index fe07159b65..7103601b85 100644 --- a/pype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -1,12 +1,12 @@ import os import avalon.maya -import pype.api +import openpype.api from maya import cmds -class ExtractVRayProxy(pype.api.Extractor): +class ExtractVRayProxy(openpype.api.Extractor): """Extract the content of the instance to a vrmesh file Things to pay attention to: diff --git a/pype/hosts/maya/plugins/publish/extract_vrayscene.py b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py similarity index 97% rename from pype/hosts/maya/plugins/publish/extract_vrayscene.py rename to openpype/hosts/maya/plugins/publish/extract_vrayscene.py index a217332d8e..d3a3df6b1c 100644 --- a/pype/hosts/maya/plugins/publish/extract_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py @@ -4,13 +4,13 @@ import os import re import avalon.maya -import pype.api -from pype.hosts.maya.render_setup_tools import export_in_rs_layer +import openpype.api +from openpype.hosts.maya.render_setup_tools import export_in_rs_layer from maya import cmds -class ExtractVrayscene(pype.api.Extractor): +class ExtractVrayscene(openpype.api.Extractor): """Extractor for vrscene.""" label = "VRay Scene (.vrscene)" diff --git a/pype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py similarity index 97% rename from pype/hosts/maya/plugins/publish/extract_yeti_cache.py rename to openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index 5a67a6ab7e..05fe79ecc5 100644 --- a/pype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -3,10 +3,10 @@ import json from maya import cmds -import pype.api +import openpype.api -class ExtractYetiCache(pype.api.Extractor): +class ExtractYetiCache(openpype.api.Extractor): """Producing Yeti cache files using scene time range. This will extract Yeti cache file sequence and fur settings. diff --git a/pype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py similarity index 98% rename from pype/hosts/maya/plugins/publish/extract_yeti_rig.py rename to openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 43908d8742..b9bed47fa5 100644 --- a/pype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -8,8 +8,8 @@ import contextlib from maya import cmds import avalon.maya.lib as lib -import pype.api -import pype.hosts.maya.api.lib as maya +import openpype.api +import openpype.hosts.maya.api.lib as maya @contextlib.contextmanager @@ -91,7 +91,7 @@ def yetigraph_attribute_values(assumed_destination, resources): pass -class ExtractYetiRig(pype.api.Extractor): +class ExtractYetiRig(openpype.api.Extractor): """Extract the Yeti rig to a Maya Scene and write the Yeti rig data.""" label = "Extract Yeti Rig" diff --git a/pype/hosts/maya/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py similarity index 91% rename from pype/hosts/maya/plugins/publish/increment_current_file_deadline.py rename to openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py index 8259145b68..f9cfac3eb9 100644 --- a/pype/hosts/maya/plugins/publish/increment_current_file_deadline.py +++ b/openpype/hosts/maya/plugins/publish/increment_current_file_deadline.py @@ -18,8 +18,8 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): import os from maya import cmds - from pype.lib import version_up - from pype.action import get_errored_plugins_from_data + from openpype.lib import version_up + from openpype.action import get_errored_plugins_from_data errored_plugins = get_errored_plugins_from_data(context) if any(plugin.__name__ == "MayaSubmitDeadline" diff --git a/pype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py similarity index 100% rename from pype/hosts/maya/plugins/publish/save_scene.py rename to openpype/hosts/maya/plugins/publish/save_scene.py diff --git a/pype/hosts/maya/plugins/publish/submit_maya_muster.py b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py similarity index 98% rename from pype/hosts/maya/plugins/publish/submit_maya_muster.py rename to openpype/hosts/maya/plugins/publish/submit_maya_muster.py index e31f989224..1c97f0faf7 100644 --- a/pype/hosts/maya/plugins/publish/submit_maya_muster.py +++ b/openpype/hosts/maya/plugins/publish/submit_maya_muster.py @@ -10,8 +10,8 @@ from avalon import api from avalon.vendor import requests import pyblish.api -from pype.hosts.maya.api import lib -from pype.api import get_system_settings +from openpype.hosts.maya.api import lib +from openpype.api import get_system_settings # mapping between Maya renderer names and Muster template ids @@ -40,7 +40,7 @@ def _get_template_id(renderer): def _get_script(): """Get path to the image sequence script""" try: - from pype.scripts import publish_filesequence + from openpype.scripts import publish_filesequence except Exception: raise RuntimeError("Expected module 'publish_deadline'" "to be available") @@ -310,7 +310,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] metadata_path = os.path.join(output_dir, metadata_filename) - pype_root = os.environ["PYPE_SETUP_PATH"] + pype_root = os.environ["OPENPYPE_SETUP_PATH"] # we must provide either full path to executable or use musters own # python named MPython.exe, residing directly in muster bin @@ -509,7 +509,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): environment[path] = os.environ[path] environment["PATH"] = os.environ["PATH"] - # self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) + # self.log.debug("enviro: {}".format(environment['OPENPYPE_SCRIPTS'])) clean_environment = {} for key, value in environment.items(): clean_path = "" @@ -559,5 +559,5 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): of defense SSL is providing and it is not recommended. """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa return requests.post(*args, **kwargs) diff --git a/pype/hosts/maya/plugins/publish/validate_animation_content.py b/openpype/hosts/maya/plugins/publish/validate_animation_content.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_animation_content.py rename to openpype/hosts/maya/plugins/publish/validate_animation_content.py index ec6f33c969..bcea761a01 100644 --- a/pype/hosts/maya/plugins/publish/validate_animation_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_content.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateAnimationContent(pyblish.api.InstancePlugin): @@ -11,11 +11,11 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["animation"] label = "Animation Content" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py rename to openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py index 6bea9bf856..00f0d38775 100644 --- a/pype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_animation_out_set_related_node_ids.py @@ -1,9 +1,9 @@ import maya.cmds as cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): @@ -16,13 +16,13 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['animation', "pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' actions = [ - pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction ] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_ass_relative_paths.py b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_ass_relative_paths.py rename to openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py index b77a0c1923..3625d4ab32 100644 --- a/pype/hosts/maya/plugins/publish/validate_ass_relative_paths.py +++ b/openpype/hosts/maya/plugins/publish/validate_ass_relative_paths.py @@ -4,18 +4,18 @@ import types import maya.cmds as cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateAssRelativePaths(pyblish.api.InstancePlugin): """Ensure exporting ass file has set relative texture paths""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['ass'] label = "ASS has relative texture paths" - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, instance): # we cannot ask this until user open render settings as diff --git a/pype/hosts/maya/plugins/publish/validate_assembly_name.py b/openpype/hosts/maya/plugins/publish/validate_assembly_name.py similarity index 94% rename from pype/hosts/maya/plugins/publish/validate_assembly_name.py rename to openpype/hosts/maya/plugins/publish/validate_assembly_name.py index 54479035c5..8f7a3dfaf9 100644 --- a/pype/hosts/maya/plugins/publish/validate_assembly_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_name.py @@ -1,6 +1,6 @@ import pyblish.api import maya.cmds as cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateAssemblyName(pyblish.api.InstancePlugin): @@ -12,7 +12,7 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin): label = "Validate Assembly Name" order = pyblish.api.ValidatorOrder families = ["assembly"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] active = False @classmethod diff --git a/pype/hosts/maya/plugins/publish/validate_assembly_namespaces.py b/openpype/hosts/maya/plugins/publish/validate_assembly_namespaces.py similarity index 88% rename from pype/hosts/maya/plugins/publish/validate_assembly_namespaces.py rename to openpype/hosts/maya/plugins/publish/validate_assembly_namespaces.py index 32f4b8db52..a9ea5a6d15 100644 --- a/pype/hosts/maya/plugins/publish/validate_assembly_namespaces.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_namespaces.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin): @@ -18,7 +18,7 @@ class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin): label = "Validate Assembly Namespaces" order = pyblish.api.ValidatorOrder families = ["assembly"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_assembly_transforms.py b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_assembly_transforms.py rename to openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py index 146736ba8d..00600a6f62 100644 --- a/pype/hosts/maya/plugins/publish/validate_assembly_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_assembly_transforms.py @@ -1,9 +1,9 @@ import pyblish.api -import pype.api +import openpype.api from maya import cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): @@ -28,8 +28,8 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder + 0.49 label = "Assembly Model Transforms" families = ["assembly"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] prompt_message = ("You are about to reset the matrix to the default values." " This can alter the look of your scene. " @@ -44,7 +44,7 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - from pype.hosts.maya.api import lib + from openpype.hosts.maya.api import lib # Get all transforms in the loaded containers container_roots = cmds.listRelatives(instance.data["hierarchy"], @@ -89,7 +89,7 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin): """ - from pype.hosts.maya.api import lib + from openpype.hosts.maya.api import lib from avalon.vendor.Qt import QtWidgets # Store namespace in variable, cosmetics thingy diff --git a/pype/hosts/maya/plugins/publish/validate_attributes.py b/openpype/hosts/maya/plugins/publish/validate_attributes.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_attributes.py rename to openpype/hosts/maya/plugins/publish/validate_attributes.py index 97e63a475b..e2a22f80b6 100644 --- a/pype/hosts/maya/plugins/publish/validate_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_attributes.py @@ -1,7 +1,7 @@ import pymel.core as pm import pyblish.api -import pype.api +import openpype.api class ValidateAttributes(pyblish.api.ContextPlugin): @@ -16,10 +16,10 @@ class ValidateAttributes(pyblish.api.ContextPlugin): } """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Attributes" hosts = ["maya"] - actions = [pype.api.RepairContextAction] + actions = [openpype.api.RepairContextAction] optional = True attributes = None diff --git a/pype/hosts/maya/plugins/publish/validate_camera_attributes.py b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_camera_attributes.py rename to openpype/hosts/maya/plugins/publish/validate_camera_attributes.py index 797244ba84..e019788aff 100644 --- a/pype/hosts/maya/plugins/publish/validate_camera_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_attributes.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateCameraAttributes(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['camera'] hosts = ['maya'] label = 'Camera Attributes' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] DEFAULTS = [ ("filmFitOffset", 0.0), diff --git a/pype/hosts/maya/plugins/publish/validate_camera_contents.py b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_camera_contents.py rename to openpype/hosts/maya/plugins/publish/validate_camera_contents.py index 644cfd16bf..d9e88edaac 100644 --- a/pype/hosts/maya/plugins/publish/validate_camera_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_camera_contents.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateCameraContents(pyblish.api.InstancePlugin): @@ -15,11 +15,11 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['camera'] hosts = ['maya'] label = 'Camera Contents' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_color_sets.py b/openpype/hosts/maya/plugins/publish/validate_color_sets.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_color_sets.py rename to openpype/hosts/maya/plugins/publish/validate_color_sets.py index aa5901ee0a..45224b0672 100644 --- a/pype/hosts/maya/plugins/publish/validate_color_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_color_sets.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateColorSets(pyblish.api.Validator): @@ -13,13 +13,13 @@ class ValidateColorSets(pyblish.api.Validator): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' label = 'Mesh ColorSets' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] optional = True @staticmethod diff --git a/pype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py b/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py rename to openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py index aa6ffa555c..3c3ea68fc6 100644 --- a/pype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py +++ b/openpype/hosts/maya/plugins/publish/validate_current_renderlayer_renderable.py @@ -1,7 +1,7 @@ import pyblish.api from maya import cmds -from pype.plugin import contextplugin_should_run +from openpype.plugin import contextplugin_should_run class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): diff --git a/pype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_frame_range.py rename to openpype/hosts/maya/plugins/publish/validate_frame_range.py index 1ee6e2bd25..d5009701f2 100644 --- a/pype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api from maya import cmds @@ -17,7 +17,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): """ label = "Validate Frame Range" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["animation", "pointcache", "camera", @@ -25,7 +25,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): "review", "yeticache"] optional = True - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, instance): context = instance.context diff --git a/pype/hosts/maya/plugins/publish/validate_instance_has_members.py b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py similarity index 78% rename from pype/hosts/maya/plugins/publish/validate_instance_has_members.py rename to openpype/hosts/maya/plugins/publish/validate_instance_has_members.py index b28a056198..e04a26e4fd 100644 --- a/pype/hosts/maya/plugins/publish/validate_instance_has_members.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_has_members.py @@ -1,15 +1,15 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): """Validates instance objectSet has *any* members.""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] label = 'Instance has members' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_instance_subset.py b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_instance_subset.py rename to openpype/hosts/maya/plugins/publish/validate_instance_subset.py index 19c60e4f64..a8c16425d6 100644 --- a/pype/hosts/maya/plugins/publish/validate_instance_subset.py +++ b/openpype/hosts/maya/plugins/publish/validate_instance_subset.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api import string # Allow only characters, numbers and underscore @@ -16,7 +16,7 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): """Validates subset name has only valid characters""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["*"] label = "Subset Name" diff --git a/pype/hosts/maya/plugins/publish/validate_instancer_content.py b/openpype/hosts/maya/plugins/publish/validate_instancer_content.py similarity index 98% rename from pype/hosts/maya/plugins/publish/validate_instancer_content.py rename to openpype/hosts/maya/plugins/publish/validate_instancer_content.py index 74ed73ef53..32abe91f48 100644 --- a/pype/hosts/maya/plugins/publish/validate_instancer_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_instancer_content.py @@ -1,7 +1,7 @@ import maya.cmds as cmds import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib class ValidateInstancerContent(pyblish.api.InstancePlugin): diff --git a/pype/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py b/openpype/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py similarity index 100% rename from pype/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py rename to openpype/hosts/maya/plugins/publish/validate_instancer_frame_ranges.py diff --git a/pype/hosts/maya/plugins/publish/validate_look_contents.py b/openpype/hosts/maya/plugins/publish/validate_look_contents.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_look_contents.py rename to openpype/hosts/maya/plugins/publish/validate_look_contents.py index 8893fdfae8..443a0ad719 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_contents.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateLookContents(pyblish.api.InstancePlugin): @@ -17,11 +17,11 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Data Contents' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/pype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py rename to openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py index af355e178b..262dd10b74 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_default_shaders_connections.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): @@ -16,7 +16,7 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Default Shader Connections' diff --git a/pype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py rename to openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py index 8cf5493d20..9d074f927b 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_id_reference_edits.py @@ -2,8 +2,8 @@ from collections import defaultdict from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): @@ -16,12 +16,12 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Id Reference Edits' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/hosts/maya/plugins/publish/validate_look_members_unique.py b/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_look_members_unique.py rename to openpype/hosts/maya/plugins/publish/validate_look_members_unique.py index 0b0b50715a..2367602d05 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_members_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_members_unique.py @@ -1,8 +1,8 @@ from collections import defaultdict import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): @@ -20,13 +20,13 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): """ - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder label = 'Look members unique' hosts = ['maya'] families = ['look'] - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py b/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py rename to openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py index 0d6a1ca562..8ba6cde988 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_no_default_shaders.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @@ -23,11 +23,11 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + 0.01 + order = openpype.api.ValidateContentsOrder + 0.01 families = ['look'] hosts = ['maya'] label = 'Look No Default Shaders' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] DEFAULT_SHADERS = {"lambert1", "initialShadingGroup", "initialParticleSE", "particleCloud1"} diff --git a/pype/hosts/maya/plugins/publish/validate_look_sets.py b/openpype/hosts/maya/plugins/publish/validate_look_sets.py similarity index 94% rename from pype/hosts/maya/plugins/publish/validate_look_sets.py rename to openpype/hosts/maya/plugins/publish/validate_look_sets.py index d044d8ad2e..48431d0906 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_sets.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_sets.py @@ -1,8 +1,8 @@ -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib import pyblish.api -import pype.api +import openpype.api class ValidateLookSets(pyblish.api.InstancePlugin): @@ -38,11 +38,11 @@ class ValidateLookSets(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Sets' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/pype/hosts/maya/plugins/publish/validate_look_shading_group.py b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_look_shading_group.py rename to openpype/hosts/maya/plugins/publish/validate_look_shading_group.py index 8d4e3ccf70..e8affac036 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_shading_group.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_shading_group.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateShadingEngine(pyblish.api.InstancePlugin): @@ -11,12 +11,12 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin): Shading engines should be named "{surface_shader}SG" """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["look"] hosts = ["maya"] label = "Look Shading Engine Naming" actions = [ - pype.hosts.maya.api.action.SelectInvalidAction, pype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, openpype.api.RepairAction ] # The default connections to check diff --git a/pype/hosts/maya/plugins/publish/validate_look_single_shader.py b/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_look_single_shader.py rename to openpype/hosts/maya/plugins/publish/validate_look_single_shader.py index 8c4835c5bf..2b32ccf492 100644 --- a/pype/hosts/maya/plugins/publish/validate_look_single_shader.py +++ b/openpype/hosts/maya/plugins/publish/validate_look_single_shader.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateSingleShader(pyblish.api.InstancePlugin): @@ -12,11 +12,11 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] # The default connections to check def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_maya_units.py b/openpype/hosts/maya/plugins/publish/validate_maya_units.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_maya_units.py rename to openpype/hosts/maya/plugins/publish/validate_maya_units.py index c99b8eb813..94065344ed 100644 --- a/pype/hosts/maya/plugins/publish/validate_maya_units.py +++ b/openpype/hosts/maya/plugins/publish/validate_maya_units.py @@ -1,9 +1,9 @@ import maya.cmds as cmds import pyblish.api -import pype.api -from pype import lib -import pype.hosts.maya.api.lib as mayalib +import openpype.api +from openpype import lib +import openpype.hosts.maya.api.lib as mayalib from math import ceil @@ -14,10 +14,10 @@ def float_round(num, places=0, direction=ceil): class ValidateMayaUnits(pyblish.api.ContextPlugin): """Check if the Maya units are set correct""" - order = pype.api.ValidateSceneOrder + order = openpype.api.ValidateSceneOrder label = "Maya Units" hosts = ['maya'] - actions = [pype.api.RepairContextAction] + actions = [openpype.api.RepairContextAction] def process(self, context): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py index a2792693af..6b3f508561 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -1,8 +1,8 @@ import pymel.core as pc from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action from avalon import maya @@ -13,14 +13,14 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): later published looks can discover non-default Arnold attributes. """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ["maya"] families = ["model"] category = "geometry" label = "Mesh Arnold Attributes" actions = [ - pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction ] optional = True if cmds.getAttr( diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py similarity index 94% rename from pype/hosts/maya/plugins/publish/validate_mesh_has_uv.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py index ae80b6a8f2..8f9b5d1c4e 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def len_flattened(components): @@ -45,12 +45,12 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): UVs for every face. """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' label = 'Mesh Has UVs' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True @classmethod diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py similarity index 83% rename from pype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py index 5628008a38..8fa1f3cf3b 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_lamina_faces.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): @@ -12,13 +12,13 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Lamina Faces' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py b/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py similarity index 88% rename from pype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py index 8446808459..5ccfa7377a 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_no_negative_scale.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): @@ -17,11 +17,11 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Mesh No Negative Scale' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py similarity index 85% rename from pype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py index 4a2773671d..9bd584bbbf 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_manifold.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateMeshNonManifold(pyblish.api.Validator): @@ -13,11 +13,11 @@ class ValidateMeshNonManifold(pyblish.api.Validator): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Mesh Non-Manifold Vertices/Edges' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py index 0ed02328c8..5e6f24cf79 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @@ -16,13 +16,13 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder families = ['model'] hosts = ['maya'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Edge Length Non Zero' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True __tolerance = 1e-5 diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py index d9d740cf13..b14781b608 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_normals_unlocked.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateMeshNormalsUnlocked(pyblish.api.Validator): @@ -13,14 +13,14 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Normals Unlocked' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] optional = True @staticmethod diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py similarity index 98% rename from pype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py index 3619d27298..57cf0803a4 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_overlapping_uvs.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action import math import maya.api.OpenMaya as om import pymel.core as pm @@ -230,12 +230,12 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin): It is optional to warn publisher about it. """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' label = 'Mesh Has Overlapping UVs' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] optional = True @classmethod diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py index b9542fe930..0969573a90 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_shader_connections.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def pairs(iterable): @@ -73,12 +73,12 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] label = "Mesh Shader Connections" - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] def process(self, instance): """Process all the nodes in the instance 'objectSet'""" diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py index 348bcb5fad..9d2aeb7d99 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_single_uv_set.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): @@ -15,15 +15,15 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model', 'pointcache'] category = 'uv' optional = True version = (0, 1, 0) label = "Mesh Single UV Set" - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py index 5f954ee917..52c45d3b0c 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_uv_set_map1.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): @@ -15,13 +15,13 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] optional = True label = "Mesh has map1 UV Set" - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py rename to openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 8d39c5cae4..463c3c4c50 100644 --- a/pype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def len_flattened(components): @@ -57,13 +57,13 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] category = 'geometry' label = 'Mesh Vertices Have Edges' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @classmethod def repair(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_model_content.py b/openpype/hosts/maya/plugins/publish/validate_model_content.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_model_content.py rename to openpype/hosts/maya/plugins/publish/validate_model_content.py index 9bec7187e9..3d4f122af4 100644 --- a/pype/hosts/maya/plugins/publish/validate_model_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_content.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateModelContent(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateModelContent(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Model Content" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_model_name.py b/openpype/hosts/maya/plugins/publish/validate_model_name.py similarity index 94% rename from pype/hosts/maya/plugins/publish/validate_model_name.py rename to openpype/hosts/maya/plugins/publish/validate_model_name.py index 2f1586538e..98da4d42ba 100644 --- a/pype/hosts/maya/plugins/publish/validate_model_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_name.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action import re @@ -14,11 +14,11 @@ class ValidateModelName(pyblish.api.InstancePlugin): """ optional = True - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["model"] label = "Model Name" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] # path to shader names definitions # TODO: move it to preset file material_file = None diff --git a/pype/hosts/maya/plugins/publish/validate_muster_connection.py b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_muster_connection.py rename to openpype/hosts/maya/plugins/publish/validate_muster_connection.py index 868135677e..1a7ee11230 100644 --- a/pype/hosts/maya/plugins/publish/validate_muster_connection.py +++ b/openpype/hosts/maya/plugins/publish/validate_muster_connection.py @@ -4,8 +4,8 @@ import appdirs import pyblish.api from avalon.vendor import requests -from pype.plugin import contextplugin_should_run -import pype.hosts.maya.api.action +from openpype.plugin import contextplugin_should_run +import openpype.hosts.maya.api.action class ValidateMusterConnection(pyblish.api.ContextPlugin): @@ -20,7 +20,7 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): token = None if not os.environ.get("MUSTER_REST_URL"): active = False - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, context): @@ -85,7 +85,7 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): Renew authentication token by logging into Muster """ api_url = "{}/muster/show_login".format( - os.environ["PYPE_WEBSERVER_URL"]) + os.environ["OPENPYPE_WEBSERVER_URL"]) cls.log.debug(api_url) response = cls._requests_get(api_url, timeout=1) if response.status_code != 200: @@ -103,7 +103,7 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): of defense SSL is providing and it is not recommended. """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa return requests.post(*args, **kwargs) def _requests_get(self, *args, **kwargs): @@ -117,5 +117,5 @@ class ValidateMusterConnection(pyblish.api.ContextPlugin): of defense SSL is providing and it is not recommended. """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa return requests.get(*args, **kwargs) diff --git a/pype/hosts/maya/plugins/publish/validate_no_animation.py b/openpype/hosts/maya/plugins/publish/validate_no_animation.py similarity index 84% rename from pype/hosts/maya/plugins/publish/validate_no_animation.py rename to openpype/hosts/maya/plugins/publish/validate_no_animation.py index a6daf3246e..6621e452f0 100644 --- a/pype/hosts/maya/plugins/publish/validate_no_animation.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_animation.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateNoAnimation(pyblish.api.Validator): @@ -14,12 +14,12 @@ class ValidateNoAnimation(pyblish.api.Validator): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "No Animation" hosts = ["maya"] families = ["model"] optional = True - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_no_default_camera.py b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py similarity index 84% rename from pype/hosts/maya/plugins/publish/validate_no_default_camera.py rename to openpype/hosts/maya/plugins/publish/validate_no_default_camera.py index 07ec2e4325..c3f6f3c38e 100644 --- a/pype/hosts/maya/plugins/publish/validate_no_default_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_default_camera.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): @@ -13,12 +13,12 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): settings when being loaded and sometimes being skipped. """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['camera'] version = (0, 1, 0) label = "No Default Cameras" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_no_namespace.py b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py similarity index 86% rename from pype/hosts/maya/plugins/publish/validate_no_namespace.py rename to openpype/hosts/maya/plugins/publish/validate_no_namespace.py index 97cd46e68a..5b3d6bc9c4 100644 --- a/pype/hosts/maya/plugins/publish/validate_no_namespace.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_namespace.py @@ -2,8 +2,8 @@ import pymel.core as pm import maya.cmds as cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def get_namespace(node_name): @@ -16,14 +16,14 @@ def get_namespace(node_name): class ValidateNoNamespace(pyblish.api.InstancePlugin): """Ensure the nodes don't have a namespace""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' version = (0, 1, 0) label = 'No Namespaces' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_no_null_transforms.py b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py similarity index 90% rename from pype/hosts/maya/plugins/publish/validate_no_null_transforms.py rename to openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py index 78f06bbbd8..36d61b03e8 100644 --- a/pype/hosts/maya/plugins/publish/validate_no_null_transforms.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_null_transforms.py @@ -1,8 +1,8 @@ import maya.cmds as cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def has_shape_children(node): @@ -37,14 +37,14 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' version = (0, 1, 0) label = 'No Empty/Null Transforms' - actions = [pype.api.RepairAction, - pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.api.RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py b/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py similarity index 82% rename from pype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py rename to openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py index 6b2f1337d3..d140a1f24a 100644 --- a/pype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py +++ b/openpype/hosts/maya/plugins/publish/validate_no_unknown_nodes.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): @@ -16,12 +16,12 @@ class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model', 'rig'] optional = True label = "Unknown Nodes" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_no_vraymesh.py b/openpype/hosts/maya/plugins/publish/validate_no_vraymesh.py similarity index 100% rename from pype/hosts/maya/plugins/publish/validate_no_vraymesh.py rename to openpype/hosts/maya/plugins/publish/validate_no_vraymesh.py diff --git a/pype/hosts/maya/plugins/publish/validate_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_node_ids.py similarity index 81% rename from pype/hosts/maya/plugins/publish/validate_node_ids.py rename to openpype/hosts/maya/plugins/publish/validate_node_ids.py index 4942d44d70..d17d34117f 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids.py @@ -1,8 +1,8 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib class ValidateNodeIDs(pyblish.api.InstancePlugin): @@ -14,7 +14,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): """ - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder label = 'Instance Nodes Have ID' hosts = ['maya'] families = ["model", @@ -25,8 +25,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): "yetiRig", "assembly"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py rename to openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py index 757414a074..a4d4d2bcc2 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_deformed_shapes.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): @@ -16,13 +16,13 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ['look'] hosts = ['maya'] label = 'Deformed shape ids' actions = [ - pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction ] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_node_ids_in_database.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py similarity index 85% rename from pype/hosts/maya/plugins/publish/validate_node_ids_in_database.py rename to openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py index 154d0c1d38..c5f675c8ca 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_ids_in_database.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_in_database.py @@ -2,9 +2,9 @@ import pyblish.api from avalon import io -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @@ -18,13 +18,13 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): """ - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder label = 'Node Ids in Database' hosts = ['maya'] families = ["*"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/hosts/maya/plugins/publish/validate_node_ids_related.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py similarity index 84% rename from pype/hosts/maya/plugins/publish/validate_node_ids_related.py rename to openpype/hosts/maya/plugins/publish/validate_node_ids_related.py index 390e5c157a..276b6713f4 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_ids_related.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_related.py @@ -1,10 +1,10 @@ import pyblish.api -import pype.api +import openpype.api from avalon import io -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): @@ -12,7 +12,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): """ - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder label = 'Node Ids Related (ID)' hosts = ['maya'] families = ["model", @@ -20,8 +20,8 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): "rig"] optional = True - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all nodes in instance (including hierarchy)""" diff --git a/pype/hosts/maya/plugins/publish/validate_node_ids_unique.py b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py similarity index 85% rename from pype/hosts/maya/plugins/publish/validate_node_ids_unique.py rename to openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py index 20320854f0..39bb148911 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_ids_unique.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_ids_unique.py @@ -1,9 +1,9 @@ from collections import defaultdict import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): @@ -12,7 +12,7 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): Here we ensure that what has been added to the instance is unique """ - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder label = 'Non Duplicate Instance Members (ID)' hosts = ['maya'] families = ["model", @@ -20,8 +20,8 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): "rig", "yetiRig"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.hosts.maya.api.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/hosts/maya/plugins/publish/validate_node_no_ghosting.py b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_node_no_ghosting.py rename to openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py index 3acd5b027d..671c744a22 100644 --- a/pype/hosts/maya/plugins/publish/validate_node_no_ghosting.py +++ b/openpype/hosts/maya/plugins/publish/validate_node_no_ghosting.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): @@ -17,11 +17,11 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model', 'rig'] label = "No Ghosting" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] _attributes = {'ghosting': 0} diff --git a/pype/hosts/maya/plugins/publish/validate_render_image_rule.py b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_render_image_rule.py rename to openpype/hosts/maya/plugins/publish/validate_render_image_rule.py index c05a15ab77..dad1691149 100644 --- a/pype/hosts/maya/plugins/publish/validate_render_image_rule.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_image_rule.py @@ -2,7 +2,7 @@ import maya.mel as mel import pymel.core as pm import pyblish.api -import pype.api +import openpype.api def get_file_rule(rule): @@ -15,11 +15,11 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Images File Rule (Workspace)" hosts = ["maya"] families = ["renderlayer"] - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py b/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py similarity index 84% rename from pype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py rename to openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py index 38383310a2..044cc7c6a2 100644 --- a/pype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_no_default_cameras.py @@ -1,18 +1,18 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): """Ensure no default (startup) cameras are to be rendered.""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['renderlayer'] label = "No Default Cameras Renderable" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_render_single_camera.py b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_render_single_camera.py rename to openpype/hosts/maya/plugins/publish/validate_render_single_camera.py index 268a6599e2..0838b4fbf8 100644 --- a/pype/hosts/maya/plugins/publish/validate_render_single_camera.py +++ b/openpype/hosts/maya/plugins/publish/validate_render_single_camera.py @@ -1,8 +1,8 @@ import re import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action from maya import cmds @@ -23,12 +23,12 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): prefix must contain token. """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Render Single Camera" hosts = ['maya'] families = ["renderlayer", "vrayscene"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] R_CAMERA_TOKEN = re.compile(r'%c|', re.IGNORECASE) diff --git a/pype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py rename to openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py index 7fdd8b377c..4eb445ac68 100644 --- a/pype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_renderlayer_aovs.py @@ -1,8 +1,8 @@ import pyblish.api -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action from avalon import io -import pype.api +import openpype.api class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): @@ -25,7 +25,7 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): label = "Render Passes / AOVs Are Registered" hosts = ["maya"] families = ["renderlayer"] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py similarity index 98% rename from pype/hosts/maya/plugins/publish/validate_rendersettings.py rename to openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 88a386db57..ba676bee83 100644 --- a/pype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -5,8 +5,8 @@ from maya import cmds, mel import pymel.core as pm import pyblish.api -import pype.api -from pype.hosts.maya.api import lib +import openpype.api +from openpype.hosts.maya.api import lib class ValidateRenderSettings(pyblish.api.InstancePlugin): @@ -38,11 +38,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Render Settings" hosts = ["maya"] families = ["renderlayer"] - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] ImagePrefixes = { 'mentalray': 'defaultRenderGlobals.imageFilePrefix', diff --git a/pype/hosts/maya/plugins/publish/validate_resources.py b/openpype/hosts/maya/plugins/publish/validate_resources.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_resources.py rename to openpype/hosts/maya/plugins/publish/validate_resources.py index 47a94e7529..08f0f5467c 100644 --- a/pype/hosts/maya/plugins/publish/validate_resources.py +++ b/openpype/hosts/maya/plugins/publish/validate_resources.py @@ -2,7 +2,7 @@ import os from collections import defaultdict import pyblish.api -import pype.api +import openpype.api class ValidateResources(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Resources Unique" def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py similarity index 98% rename from pype/hosts/maya/plugins/publish/validate_rig_contents.py rename to openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 0a9616ba1f..4a6914ef90 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api class ValidateRigContents(pyblish.api.InstancePlugin): @@ -13,7 +13,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Rig Contents" hosts = ["maya"] families = ["rig"] diff --git a/pype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_rig_controllers.py rename to openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index ef0df2df7c..4e028d1d24 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -2,9 +2,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api.lib import undo_chunk +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api.lib import undo_chunk class ValidateRigControllers(pyblish.api.InstancePlugin): @@ -25,12 +25,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): - Break all incoming connections to keyable attributes """ - order = pype.api.ValidateContentsOrder + 0.05 + order = openpype.api.ValidateContentsOrder + 0.05 label = "Rig Controllers" hosts = ["maya"] families = ["rig"] - actions = [pype.api.RepairAction, - pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.api.RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] # Default controller values CONTROLLER_DEFAULTS = { diff --git a/pype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py similarity index 90% rename from pype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py rename to openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index f19e3fbb61..1f1db9156b 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -1,10 +1,10 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api -from pype.hosts.maya.api import lib -import pype.hosts.maya.api.action +from openpype.hosts.maya.api import lib +import openpype.hosts.maya.api.action class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): @@ -26,12 +26,12 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): This validator will ensure they are hidden or unkeyable attributes. """ - order = pype.api.ValidateContentsOrder + 0.05 + order = openpype.api.ValidateContentsOrder + 0.05 label = "Rig Controllers (Arnold Attributes)" hosts = ["maya"] families = ["rig"] - actions = [pype.api.RepairAction, - pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.api.RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] attributes = [ "rcurve", diff --git a/pype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py similarity index 79% rename from pype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py rename to openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py index a102df50de..5df754fff4 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_joints_hidden.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateRigJointsHidden(pyblish.api.InstancePlugin): @@ -17,13 +17,13 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['rig'] version = (0, 1, 0) label = "Joints Hidden" - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py rename to openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index 1782395d96..e2090080f6 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -1,9 +1,9 @@ import maya.cmds as cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action -from pype.hosts.maya.api import lib +import openpype.api +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): @@ -16,13 +16,13 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["rig"] hosts = ['maya'] label = 'Rig Out Set Node Ids' actions = [ - pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction + openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction ] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_rig_output_ids.py rename to openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index 37a50da910..7c5c540c60 100644 --- a/pype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -2,8 +2,8 @@ import pymel.core as pc import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateRigOutputIds(pyblish.api.InstancePlugin): @@ -13,12 +13,12 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): to ensure the id from the model is preserved through animation. """ - order = pype.api.ValidateContentsOrder + 0.05 + order = openpype.api.ValidateContentsOrder + 0.05 label = "Rig Output Ids" hosts = ["maya"] families = ["rig"] - actions = [pype.api.RepairAction, - pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.api.RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance, compute=True) diff --git a/pype/hosts/maya/plugins/publish/validate_scene_set_workspace.py b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_scene_set_workspace.py rename to openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py index bda397cf2a..174bc44a6f 100644 --- a/pype/hosts/maya/plugins/publish/validate_scene_set_workspace.py +++ b/openpype/hosts/maya/plugins/publish/validate_scene_set_workspace.py @@ -3,7 +3,7 @@ import os import maya.cmds as cmds import pyblish.api -import pype.api +import openpype.api def is_subdir(path, root_dir): @@ -28,7 +28,7 @@ def is_subdir(path, root_dir): class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): """Validate the scene is inside the currently set Maya workspace""" - order = pype.api.ValidatePipelineOrder + order = openpype.api.ValidatePipelineOrder hosts = ['maya'] category = 'scene' version = (0, 1, 0) diff --git a/pype/hosts/maya/plugins/publish/validate_shader_name.py b/openpype/hosts/maya/plugins/publish/validate_shader_name.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_shader_name.py rename to openpype/hosts/maya/plugins/publish/validate_shader_name.py index a2951d5551..24111f0ad4 100644 --- a/pype/hosts/maya/plugins/publish/validate_shader_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_shader_name.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action import re @@ -13,11 +13,11 @@ class ValidateShaderName(pyblish.api.InstancePlugin): """ optional = True - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["look"] hosts = ['maya'] label = 'Validate Shaders Name' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] regex = r'(?P.*)_(.*)_SHD' # The default connections to check diff --git a/pype/hosts/maya/plugins/publish/validate_shape_default_names.py b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_shape_default_names.py rename to openpype/hosts/maya/plugins/publish/validate_shape_default_names.py index cd4e9905d9..e08e06b50e 100644 --- a/pype/hosts/maya/plugins/publish/validate_shape_default_names.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_default_names.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action def short_name(node): @@ -31,15 +31,15 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' optional = True version = (0, 1, 0) label = "Shape Default Naming" - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] @staticmethod def _define_default_name(shape): diff --git a/pype/hosts/maya/plugins/publish/validate_shape_render_stats.py b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py similarity index 89% rename from pype/hosts/maya/plugins/publish/validate_shape_render_stats.py rename to openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py index c94becabe9..667a1f13be 100644 --- a/pype/hosts/maya/plugins/publish/validate_shape_render_stats.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_render_stats.py @@ -1,20 +1,20 @@ import pyblish.api -import pype.api +import openpype.api from maya import cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateShapeRenderStats(pyblish.api.Validator): """Ensure all render stats are set to the default values.""" - order = pype.api.ValidateMeshOrder + order = openpype.api.ValidateMeshOrder hosts = ['maya'] families = ['model'] label = 'Shape Default Render Stats' - actions = [pype.hosts.maya.api.action.SelectInvalidAction, - pype.api.RepairAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction, + openpype.api.RepairAction] defaults = {'castsShadows': 1, 'receiveShadows': 1, diff --git a/pype/hosts/maya/plugins/publish/validate_single_assembly.py b/openpype/hosts/maya/plugins/publish/validate_single_assembly.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_single_assembly.py rename to openpype/hosts/maya/plugins/publish/validate_single_assembly.py index 6f40dfbfd2..9fb3a47e6d 100644 --- a/pype/hosts/maya/plugins/publish/validate_single_assembly.py +++ b/openpype/hosts/maya/plugins/publish/validate_single_assembly.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): @@ -17,7 +17,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['rig', 'animation'] label = 'Single Assembly' diff --git a/pype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py b/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py similarity index 93% rename from pype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py rename to openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py index 2d28d26b0f..8c804786f3 100644 --- a/pype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py +++ b/openpype/hosts/maya/plugins/publish/validate_skinCluster_deformer_set.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['fbx'] label = "Skincluster Deformer Relationships" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): """Process all the transform nodes in the instance""" diff --git a/pype/hosts/maya/plugins/publish/validate_step_size.py b/openpype/hosts/maya/plugins/publish/validate_step_size.py similarity index 85% rename from pype/hosts/maya/plugins/publish/validate_step_size.py rename to openpype/hosts/maya/plugins/publish/validate_step_size.py index 502a1c37c2..172ac5f26e 100644 --- a/pype/hosts/maya/plugins/publish/validate_step_size.py +++ b/openpype/hosts/maya/plugins/publish/validate_step_size.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateStepSize(pyblish.api.InstancePlugin): @@ -10,12 +10,12 @@ class ValidateStepSize(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = 'Step size' families = ['camera', 'pointcache', 'animation'] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] MIN = 0.01 MAX = 1.0 diff --git a/pype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py similarity index 95% rename from pype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py rename to openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index bd290c73c7..6f5ff24b9c 100644 --- a/pype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -3,8 +3,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): @@ -27,14 +27,14 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ['maya'] families = ['model'] category = 'cleanup' optional = True version = (0, 1, 0) label = 'Suffix Naming Conventions' - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] SUFFIX_NAMING_TABLE = {"mesh": ["_GEO", "_GES", "_GEP", "_OSD"], "nurbsCurve": ["_CRV"], "nurbsSurface": ["_NRB"], diff --git a/pype/hosts/maya/plugins/publish/validate_transform_zero.py b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py similarity index 91% rename from pype/hosts/maya/plugins/publish/validate_transform_zero.py rename to openpype/hosts/maya/plugins/publish/validate_transform_zero.py index 0cecc3124b..fdd09658d1 100644 --- a/pype/hosts/maya/plugins/publish/validate_transform_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_zero.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateTransformZero(pyblish.api.Validator): @@ -14,13 +14,13 @@ class ValidateTransformZero(pyblish.api.Validator): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["model"] category = "geometry" version = (0, 1, 0) label = "Transform Zero (Freeze)" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] _identity = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/pype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py similarity index 86% rename from pype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py rename to openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py index 9011b584fe..1c6aa3078e 100644 --- a/pype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_mesh_triangulated.py @@ -2,18 +2,18 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin): """Validate if mesh is made of triangles for Unreal Engine""" - order = pype.api.ValidateMeshOder + order = openpype.api.ValidateMeshOder hosts = ["maya"] families = ["unrealStaticMesh"] category = "geometry" label = "Mesh is Triangulated" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py similarity index 96% rename from pype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py rename to openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py index 8bda849b3f..99d6cfd4c5 100644 --- a/pype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_staticmesh_naming.py @@ -2,8 +2,8 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action import re @@ -47,11 +47,11 @@ class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin): """ optional = True - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["unrealStaticMesh"] label = "Unreal StaticMesh Name" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] regex_mesh = r"SM_(?P.*)_(\d{2})" regex_collision = r"((UBX)|(UCP)|(USP)|(UCX))_(?P.*)_(\d{2})" diff --git a/pype/hosts/maya/plugins/publish/validate_unreal_up_axis.py b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py similarity index 82% rename from pype/hosts/maya/plugins/publish/validate_unreal_up_axis.py rename to openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py index 6641edb4a5..5a8c29c22d 100644 --- a/pype/hosts/maya/plugins/publish/validate_unreal_up_axis.py +++ b/openpype/hosts/maya/plugins/publish/validate_unreal_up_axis.py @@ -2,18 +2,18 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api class ValidateUnrealUpAxis(pyblish.api.ContextPlugin): """Validate if Z is set as up axis in Maya""" optional = True - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["unrealStaticMesh"] label = "Unreal Up-Axis check" - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, context): assert cmds.upAxis(q=True, axis=True) == "z", ( diff --git a/pype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py similarity index 92% rename from pype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py rename to openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py index 0c3086fee0..5e35565383 100644 --- a/pype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_distributed_rendering.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.api -from pype.hosts.maya.api import lib +import openpype.api +from openpype.hosts.maya.api import lib from maya import cmds @@ -15,10 +15,10 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "VRay Distributed Rendering" families = ["renderlayer"] - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] # V-Ray attribute names enabled_attr = "vraySettings.sys_distributed_rendering_on" diff --git a/pype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py similarity index 97% rename from pype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py rename to openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py index f451a08b0b..6cfbd4049b 100644 --- a/pype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_referenced_aovs.py @@ -4,7 +4,7 @@ import pyblish.api import types from maya import cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin): label = 'VRay Referenced AOVs' hosts = ['maya'] families = ['renderlayer'] - actions = [pype.api.RepairContextAction] + actions = [openpype.api.RepairContextAction] def process(self, instance): """Plugin main entry point.""" diff --git a/pype/hosts/maya/plugins/publish/validate_vray_translator_settings.py b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py similarity index 94% rename from pype/hosts/maya/plugins/publish/validate_vray_translator_settings.py rename to openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py index fb290a2d5d..1deabde4a2 100644 --- a/pype/hosts/maya/plugins/publish/validate_vray_translator_settings.py +++ b/openpype/hosts/maya/plugins/publish/validate_vray_translator_settings.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """Validate VRay Translator settings.""" import pyblish.api -import pype.api -from pype.plugin import contextplugin_should_run +import openpype.api +from openpype.plugin import contextplugin_should_run from maya import cmds @@ -10,10 +10,10 @@ from maya import cmds class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): """Validate VRay Translator settings for extracting vrscenes.""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "VRay Translator Settings" families = ["vrayscene_layer"] - actions = [pype.api.RepairContextAction] + actions = [openpype.api.RepairContextAction] def process(self, context): """Plugin entry point.""" diff --git a/pype/hosts/maya/plugins/publish/validate_vrayproxy.py b/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py similarity index 100% rename from pype/hosts/maya/plugins/publish/validate_vrayproxy.py rename to openpype/hosts/maya/plugins/publish/validate_vrayproxy.py diff --git a/pype/hosts/maya/plugins/publish/validate_vrayproxy_members.py b/openpype/hosts/maya/plugins/publish/validate_vrayproxy_members.py similarity index 87% rename from pype/hosts/maya/plugins/publish/validate_vrayproxy_members.py rename to openpype/hosts/maya/plugins/publish/validate_vrayproxy_members.py index 829c2d67ae..b94e5cbbed 100644 --- a/pype/hosts/maya/plugins/publish/validate_vrayproxy_members.py +++ b/openpype/hosts/maya/plugins/publish/validate_vrayproxy_members.py @@ -1,9 +1,9 @@ import pyblish.api -import pype.api +import openpype.api from maya import cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): @@ -13,7 +13,7 @@ class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): label = 'VRay Proxy Members' hosts = ['maya'] families = ['vrayproxy'] - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py similarity index 98% rename from pype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py rename to openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py index cd9b0754b3..79cd09315e 100644 --- a/pype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_renderscript_callbacks.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import pype.api +import openpype.api class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Yeti Render Script Callbacks" hosts = ["maya"] families = ["renderlayer"] diff --git a/pype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py similarity index 90% rename from pype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py rename to openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py index 7eb3153d1e..5610733577 100644 --- a/pype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_cache_state.py @@ -1,7 +1,7 @@ import pyblish.api -import pype.action +import openpype.action import maya.cmds as cmds -import pype.hosts.maya.api.action +import openpype.hosts.maya.api.action class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): @@ -17,8 +17,8 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): label = "Yeti Rig Cache State" hosts = ["maya"] families = ["yetiRig"] - actions = [pype.action.RepairAction, - pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.action.RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py similarity index 88% rename from pype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py rename to openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py index c0bd46efc1..651c8da849 100644 --- a/pype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py +++ b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_input_in_instance.py @@ -1,18 +1,18 @@ from maya import cmds import pyblish.api -import pype.api -import pype.hosts.maya.api.action +import openpype.api +import openpype.hosts.maya.api.action class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): """Validate if all input nodes are part of the instance's hierarchy""" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder hosts = ["maya"] families = ["yetiRig"] label = "Yeti Rig Input Shapes In Instance" - actions = [pype.hosts.maya.api.action.SelectInvalidAction] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/hosts/maya/plugins/publish/validate_yeti_rig_settings.py b/openpype/hosts/maya/plugins/publish/validate_yeti_rig_settings.py similarity index 100% rename from pype/hosts/maya/plugins/publish/validate_yeti_rig_settings.py rename to openpype/hosts/maya/plugins/publish/validate_yeti_rig_settings.py diff --git a/pype/hosts/maya/resources/workspace.mel b/openpype/hosts/maya/resources/workspace.mel similarity index 100% rename from pype/hosts/maya/resources/workspace.mel rename to openpype/hosts/maya/resources/workspace.mel diff --git a/pype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py similarity index 71% rename from pype/hosts/maya/startup/userSetup.py rename to openpype/hosts/maya/startup/userSetup.py index a562d3ab9e..d556a89fa3 100644 --- a/pype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,10 +1,10 @@ import os -from pype.api import get_project_settings -import pype.hosts.maya.api.lib as mlib +from openpype.api import get_project_settings +import openpype.hosts.maya.api.lib as mlib from maya import cmds -print("starting PYPE usersetup") +print("starting OpenPype usersetup") # build a shelf settings = get_project_settings(os.environ['AVALON_PROJECT']) @@ -14,7 +14,7 @@ shelf_preset = settings['maya'].get('project_shelf') if shelf_preset: project = os.environ["AVALON_PROJECT"] - icon_path = os.path.join(os.environ['PYPE_PROJECT_SCRIPTS'], + icon_path = os.path.join(os.environ['OPENPYPE_PROJECT_SCRIPTS'], project, "icons") icon_path = os.path.abspath(icon_path) @@ -26,4 +26,4 @@ if shelf_preset: cmds.evalDeferred("mlib.shelf(name=shelf_preset['name'], iconPath=icon_path, preset=shelf_preset)") -print("finished PYPE usersetup") +print("finished OpenPype usersetup") diff --git a/pype/hosts/nuke/__init__.py b/openpype/hosts/nuke/__init__.py similarity index 100% rename from pype/hosts/nuke/__init__.py rename to openpype/hosts/nuke/__init__.py diff --git a/pype/hosts/nuke/api/__init__.py b/openpype/hosts/nuke/api/__init__.py similarity index 95% rename from pype/hosts/nuke/api/__init__.py rename to openpype/hosts/nuke/api/__init__.py index 26a8248f01..25504aa12b 100644 --- a/pype/hosts/nuke/api/__init__.py +++ b/openpype/hosts/nuke/api/__init__.py @@ -3,10 +3,10 @@ import sys import nuke from avalon import api as avalon -from avalon.tools import workfiles +from openpype.tools import workfiles from pyblish import api as pyblish -from pype.api import Logger -import pype.hosts.nuke +from openpype.api import Logger +import openpype.hosts.nuke from . import lib, menu @@ -15,7 +15,7 @@ self.workfiles_launched = False log = Logger().get_logger(__name__) AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.nuke.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.nuke.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") diff --git a/pype/hosts/nuke/api/actions.py b/openpype/hosts/nuke/api/actions.py similarity index 96% rename from pype/hosts/nuke/api/actions.py rename to openpype/hosts/nuke/api/actions.py index f5d5ffb143..fd18c787c4 100644 --- a/pype/hosts/nuke/api/actions.py +++ b/openpype/hosts/nuke/api/actions.py @@ -5,7 +5,7 @@ from avalon.nuke.lib import ( select_nodes ) -from pype.api import get_errored_instances_from_context +from openpype.api import get_errored_instances_from_context class SelectInvalidAction(pyblish.api.Action): diff --git a/pype/hosts/nuke/api/lib.py b/openpype/hosts/nuke/api/lib.py similarity index 99% rename from pype/hosts/nuke/api/lib.py rename to openpype/hosts/nuke/api/lib.py index bea5df48cf..8618b03cdc 100644 --- a/pype/hosts/nuke/api/lib.py +++ b/openpype/hosts/nuke/api/lib.py @@ -6,7 +6,7 @@ from collections import OrderedDict from avalon import api, io, lib import avalon.nuke from avalon.nuke import lib as anlib -from pype.api import ( +from openpype.api import ( Logger, Anatomy, get_version_from_path, @@ -25,7 +25,7 @@ log = Logger().get_logger(__name__) self = sys.modules[__name__] self._project = None - +self._node_tab_name = "{}".format(os.getenv("AVALON_LABEL") or "Avalon") def get_node_imageio_setting(**kwarg): ''' Get preset data for dataflow (fileType, compression, bitDepth) @@ -148,7 +148,7 @@ def writes_version_sync(): for each in nuke.allNodes(): if each.Class() == 'Write': # check if the node is avalon tracked - if "AvalonTab" not in each.knobs(): + if self._node_tab_name not in each.knobs(): continue avalon_knob_data = avalon.nuke.read( @@ -489,6 +489,9 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): # Deadline tab. add_deadline_tab(GN) + # open the our Tab as default + GN[self._node_tab_name].setFlag(0) + # set tile color tile_color = _data.get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) diff --git a/pype/hosts/nuke/api/menu.py b/openpype/hosts/nuke/api/menu.py similarity index 64% rename from pype/hosts/nuke/api/menu.py rename to openpype/hosts/nuke/api/menu.py index 3f97cc228a..2317066528 100644 --- a/pype/hosts/nuke/api/menu.py +++ b/openpype/hosts/nuke/api/menu.py @@ -1,15 +1,34 @@ +import os import nuke from avalon.api import Session from .lib import WorkfileSettings -from pype.api import Logger, BuildWorkfile, get_current_project_settings +from openpype.api import Logger, BuildWorkfile, get_current_project_settings +from openpype.tools import workfiles log = Logger().get_logger(__name__) +menu_label = os.environ["AVALON_LABEL"] def install(): menubar = nuke.menu("Nuke") - menu = menubar.findItem(Session["AVALON_LABEL"]) + menu = menubar.findItem(menu_label) + + # replace reset resolution from avalon core to pype's + name = "Work Files..." + rm_item = [ + (i, item) for i, item in enumerate(menu.items()) if name in item.name() + ][0] + + log.debug("Changing Item: {}".format(rm_item)) + + menu.removeItem(rm_item[1].name()) + menu.addCommand( + name, + workfiles.show, + index=(rm_item[0]) + ) + # replace reset resolution from avalon core to pype's name = "Reset Resolution" new_name = "Set Resolution" @@ -73,7 +92,7 @@ def install(): def uninstall(): menubar = nuke.menu("Nuke") - menu = menubar.findItem(Session["AVALON_LABEL"]) + menu = menubar.findItem(menu_label) for item in menu.items(): log.info("Removing menu item: {}".format(item.name())) @@ -82,7 +101,7 @@ def uninstall(): def add_shortcuts_from_presets(): menubar = nuke.menu("Nuke") - nuke_presets = get_current_project_settings()["nuke"] + nuke_presets = get_current_project_settings()["nuke"]["general"] if nuke_presets.get("menu"): menu_label_mapping = { @@ -92,15 +111,18 @@ def add_shortcuts_from_presets(): "build_workfile": "Build Workfile", "publish": "Publish..." } - for menu_name, menuitems in nuke_presets.get("menu").items(): - menu = menubar.findItem(menu_name) - for mitem_name, shortcut in menuitems.items(): - log.info("Adding Shortcut `{}` to `{}`".format( - shortcut, mitem_name - )) - try: - item_label = menu_label_mapping[mitem_name] - menuitem = menu.findItem(item_label) - menuitem.setShortcut(shortcut) - except AttributeError as e: - log.error(e) + + for command_name, shortcut_str in nuke_presets.get("menu").items(): + log.info("menu_name `{}` | menu_label `{}`".format( + command_name, menu_label + )) + log.info("Adding Shortcut `{}` to `{}`".format( + shortcut_str, command_name + )) + try: + menu = menubar.findItem(menu_label) + item_label = menu_label_mapping[command_name] + menuitem = menu.findItem(item_label) + menuitem.setShortcut(shortcut_str) + except AttributeError as e: + log.error(e) diff --git a/pype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py similarity index 96% rename from pype/hosts/nuke/api/plugin.py rename to openpype/hosts/nuke/api/plugin.py index 1b3e3419c6..0ad98146b1 100644 --- a/pype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1,5 +1,5 @@ import avalon.nuke -from pype.api import ( +from openpype.api import ( get_current_project_settings, PypeCreatorMixin ) diff --git a/pype/hosts/nuke/api/utils.py b/openpype/hosts/nuke/api/utils.py similarity index 98% rename from pype/hosts/nuke/api/utils.py rename to openpype/hosts/nuke/api/utils.py index e14b03d453..e43c11a380 100644 --- a/pype/hosts/nuke/api/utils.py +++ b/openpype/hosts/nuke/api/utils.py @@ -1,7 +1,7 @@ import os import nuke from avalon.nuke import lib as anlib -from pype.api import resources +from openpype.api import resources def set_context_favorites(favorites=None): diff --git a/pype/hosts/nuke/plugins/create/create_backdrop.py b/openpype/hosts/nuke/plugins/create/create_backdrop.py similarity index 97% rename from pype/hosts/nuke/plugins/create/create_backdrop.py rename to openpype/hosts/nuke/plugins/create/create_backdrop.py index 00539ceeaa..cda2629587 100644 --- a/pype/hosts/nuke/plugins/create/create_backdrop.py +++ b/openpype/hosts/nuke/plugins/create/create_backdrop.py @@ -1,5 +1,5 @@ from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api import plugin import nuke diff --git a/pype/hosts/nuke/plugins/create/create_camera.py b/openpype/hosts/nuke/plugins/create/create_camera.py similarity index 97% rename from pype/hosts/nuke/plugins/create/create_camera.py rename to openpype/hosts/nuke/plugins/create/create_camera.py index 51278c6f91..359086d48f 100644 --- a/pype/hosts/nuke/plugins/create/create_camera.py +++ b/openpype/hosts/nuke/plugins/create/create_camera.py @@ -1,5 +1,5 @@ from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api import plugin import nuke diff --git a/pype/hosts/nuke/plugins/create/create_gizmo.py b/openpype/hosts/nuke/plugins/create/create_gizmo.py similarity index 98% rename from pype/hosts/nuke/plugins/create/create_gizmo.py rename to openpype/hosts/nuke/plugins/create/create_gizmo.py index c3afbe22f2..c59713cff1 100644 --- a/pype/hosts/nuke/plugins/create/create_gizmo.py +++ b/openpype/hosts/nuke/plugins/create/create_gizmo.py @@ -1,5 +1,5 @@ from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import plugin +from openpype.hosts.nuke.api import plugin import nuke diff --git a/pype/hosts/nuke/plugins/create/create_read.py b/openpype/hosts/nuke/plugins/create/create_read.py similarity index 95% rename from pype/hosts/nuke/plugins/create/create_read.py rename to openpype/hosts/nuke/plugins/create/create_read.py index d0912b46fa..bf5de23346 100644 --- a/pype/hosts/nuke/plugins/create/create_read.py +++ b/openpype/hosts/nuke/plugins/create/create_read.py @@ -1,8 +1,8 @@ from collections import OrderedDict import avalon.api import avalon.nuke -from pype import api as pype -from pype.hosts.nuke.api import plugin +from openpype import api as pype +from openpype.hosts.nuke.api import plugin import nuke diff --git a/pype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py similarity index 99% rename from pype/hosts/nuke/plugins/create/create_write_prerender.py rename to openpype/hosts/nuke/plugins/create/create_write_prerender.py index d1969c7fba..38d1a0c2ed 100644 --- a/pype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from pype.hosts.nuke.api import ( +from openpype.hosts.nuke.api import ( plugin, lib) import nuke diff --git a/pype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py similarity index 98% rename from pype/hosts/nuke/plugins/create/create_write_render.py rename to openpype/hosts/nuke/plugins/create/create_write_render.py index 60db8800dd..72f851f19c 100644 --- a/pype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from pype.hosts.nuke.api import ( +from openpype.hosts.nuke.api import ( plugin, lib) import nuke diff --git a/pype/hosts/nuke/plugins/inventory/select_containers.py b/openpype/hosts/nuke/plugins/inventory/select_containers.py similarity index 100% rename from pype/hosts/nuke/plugins/inventory/select_containers.py rename to openpype/hosts/nuke/plugins/inventory/select_containers.py diff --git a/pype/hosts/nuke/plugins/inventory/set_tool_color.py b/openpype/hosts/nuke/plugins/inventory/set_tool_color.py similarity index 100% rename from pype/hosts/nuke/plugins/inventory/set_tool_color.py rename to openpype/hosts/nuke/plugins/inventory/set_tool_color.py diff --git a/pype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py similarity index 94% rename from pype/hosts/nuke/plugins/load/actions.py rename to openpype/hosts/nuke/plugins/load/actions.py index 9741e956fa..07dcf2d8e1 100644 --- a/pype/hosts/nuke/plugins/load/actions.py +++ b/openpype/hosts/nuke/plugins/load/actions.py @@ -3,7 +3,7 @@ """ from avalon import api -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) @@ -25,7 +25,7 @@ class SetFrameRangeLoader(api.Loader): def load(self, context, name, namespace, data): - from pype.hosts.nuke.api import lib + from openpype.hosts.nuke.api import lib version = context['version'] version_data = version.get("data", {}) @@ -59,7 +59,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader): def load(self, context, name, namespace, data): - from pype.hosts.nuke.api import lib + from openpype.hosts.nuke.api import lib version = context['version'] version_data = version.get("data", {}) diff --git a/pype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py similarity index 99% rename from pype/hosts/nuke/plugins/load/load_backdrop.py rename to openpype/hosts/nuke/plugins/load/load_backdrop.py index 00bf2df47d..e615af51ff 100644 --- a/pype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -1,7 +1,7 @@ from avalon import api, style, io import nuke import nukescripts -from pype.hosts.nuke.api import lib as pnlib +from openpype.hosts.nuke.api import lib as pnlib from avalon.nuke import lib as anlib from avalon.nuke import containerise, update_container reload(pnlib) diff --git a/pype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py similarity index 100% rename from pype/hosts/nuke/plugins/load/load_camera_abc.py rename to openpype/hosts/nuke/plugins/load/load_camera_abc.py diff --git a/pype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py similarity index 100% rename from pype/hosts/nuke/plugins/load/load_gizmo.py rename to openpype/hosts/nuke/plugins/load/load_gizmo.py diff --git a/pype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py similarity index 99% rename from pype/hosts/nuke/plugins/load/load_gizmo_ip.py rename to openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index b1a9748d16..5ca101d6cb 100644 --- a/pype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -1,6 +1,6 @@ from avalon import api, style, io import nuke -from pype.hosts.nuke.api import lib as pnlib +from openpype.hosts.nuke.api import lib as pnlib from avalon.nuke import lib as anlib from avalon.nuke import containerise, update_container diff --git a/pype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py similarity index 92% rename from pype/hosts/nuke/plugins/load/load_image.py rename to openpype/hosts/nuke/plugins/load/load_image.py index dcaf31c9e3..8bc266f01b 100644 --- a/pype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -4,7 +4,7 @@ import nuke from avalon.vendor import qargparse from avalon import api, io -from pype.hosts.nuke.api.lib import ( +from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) @@ -12,11 +12,7 @@ from pype.hosts.nuke.api.lib import ( class LoadImage(api.Loader): """Load still image into Nuke""" - families = [ - "render2d", "source", "plate", - "render", "prerender", "review", - "image" - ] + families = ["render", "source", "plate", "review", "image"] representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd"] label = "Load Image" @@ -24,6 +20,8 @@ class LoadImage(api.Loader): icon = "image" color = "white" + node_name_template = "{class_name}_{ext}" + options = [ qargparse.Integer( "frame_number", @@ -75,10 +73,16 @@ class LoadImage(api.Loader): frame, format(frame_number, "0{}".format(padding))) - read_name = "Read_{0}_{1}_{2}".format( - repr_cont["asset"], - repr_cont["subset"], - repr_cont["representation"]) + name_data = { + "asset": repr_cont["asset"], + "subset": repr_cont["subset"], + "representation": context["representation"]["name"], + "ext": repr_cont["representation"], + "id": context["representation"]["_id"], + "class_name": self.__class__.__name__ + } + + read_name = self.node_name_template.format(**name_data) # Create the Loader with the filename path set with viewer_update_and_undo_stop(): diff --git a/pype/hosts/nuke/plugins/load/load_luts.py b/openpype/hosts/nuke/plugins/load/load_luts.py similarity index 100% rename from pype/hosts/nuke/plugins/load/load_luts.py rename to openpype/hosts/nuke/plugins/load/load_luts.py diff --git a/pype/hosts/nuke/plugins/load/load_luts_ip.py b/openpype/hosts/nuke/plugins/load/load_luts_ip.py similarity index 99% rename from pype/hosts/nuke/plugins/load/load_luts_ip.py rename to openpype/hosts/nuke/plugins/load/load_luts_ip.py index 1e2469055f..a0af29c7f4 100644 --- a/pype/hosts/nuke/plugins/load/load_luts_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_luts_ip.py @@ -2,7 +2,7 @@ from avalon import api, style, io import nuke import json from collections import OrderedDict -from pype.hosts.nuke.api import lib +from openpype.hosts.nuke.api import lib class LoadLutsInputProcess(api.Loader): diff --git a/pype/hosts/nuke/plugins/load/load_matchmove.py b/openpype/hosts/nuke/plugins/load/load_matchmove.py similarity index 100% rename from pype/hosts/nuke/plugins/load/load_matchmove.py rename to openpype/hosts/nuke/plugins/load/load_matchmove.py diff --git a/pype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py similarity index 93% rename from pype/hosts/nuke/plugins/load/load_mov.py rename to openpype/hosts/nuke/plugins/load/load_mov.py index 830359ccf9..92726913af 100644 --- a/pype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -2,8 +2,8 @@ import nuke import contextlib from avalon import api, io -from pype.api import get_current_project_settings -from pype.hosts.nuke.api.lib import ( +from openpype.api import get_current_project_settings +from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) @@ -69,19 +69,8 @@ def add_review_presets_config(): class LoadMov(api.Loader): """Load mov file into Nuke""" - presets = add_review_presets_config() - families = [ - "source", - "plate", - "render", - "prerender", - "review"] + presets["families"] - - representations = [ - "mov", - "preview", - "review", - "mp4"] + presets["representations"] + families = ["render", "source", "plate", "review"] + representations = ["mov", "review", "mp4"] label = "Load mov" order = -10 @@ -90,6 +79,8 @@ class LoadMov(api.Loader): script_start = nuke.root()["first_frame"].value() + node_name_template = "{class_name}_{ext}" + def load(self, context, name, namespace, data): from avalon.nuke import ( containerise, @@ -133,10 +124,16 @@ class LoadMov(api.Loader): file = file.replace("\\", "/") - read_name = "Read_{0}_{1}_{2}".format( - repr_cont["asset"], - repr_cont["subset"], - repr_cont["representation"]) + name_data = { + "asset": repr_cont["asset"], + "subset": repr_cont["subset"], + "representation": context["representation"]["name"], + "ext": repr_cont["representation"], + "id": context["representation"]["_id"], + "class_name": self.__class__.__name__ + } + + read_name = self.node_name_template.format(**name_data) # Create the Loader with the filename path set with viewer_update_and_undo_stop(): diff --git a/pype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py similarity index 100% rename from pype/hosts/nuke/plugins/load/load_script_precomp.py rename to openpype/hosts/nuke/plugins/load/load_script_precomp.py diff --git a/pype/hosts/nuke/plugins/load/load_sequence.py b/openpype/hosts/nuke/plugins/load/load_sequence.py similarity index 95% rename from pype/hosts/nuke/plugins/load/load_sequence.py rename to openpype/hosts/nuke/plugins/load/load_sequence.py index f99b7be52f..df7aa55cd1 100644 --- a/pype/hosts/nuke/plugins/load/load_sequence.py +++ b/openpype/hosts/nuke/plugins/load/load_sequence.py @@ -2,7 +2,7 @@ import nuke import contextlib from avalon import api, io -from pype.hosts.nuke.api.lib import ( +from openpype.hosts.nuke.api.lib import ( get_imageio_input_colorspace ) @@ -72,14 +72,16 @@ def loader_shift(node, frame, relative=False): class LoadSequence(api.Loader): """Load image sequence into Nuke""" - families = ["render2d", "source", "plate", "render", "prerender", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png"] + families = ["render", "source", "plate", "review"] + representations = ["exr", "dpx"] label = "Load Image Sequence" order = -20 icon = "file-video-o" color = "white" + node_name_template = "{class_name}_{ext}" + def load(self, context, name, namespace, data): from avalon.nuke import ( containerise, @@ -125,10 +127,16 @@ class LoadSequence(api.Loader): padding = len(frame) file = file.replace(frame, "#" * padding) - read_name = "Read_{0}_{1}_{2}".format( - repr_cont["asset"], - repr_cont["subset"], - context["representation"]["name"]) + name_data = { + "asset": repr_cont["asset"], + "subset": repr_cont["subset"], + "representation": context["representation"]["name"], + "ext": repr_cont["representation"], + "id": context["representation"]["_id"], + "class_name": self.__class__.__name__ + } + + read_name = self.node_name_template.format(**name_data) # Create the Loader with the filename path set with viewer_update_and_undo_stop(): diff --git a/pype/hosts/nuke/plugins/publish/collect_backdrop.py b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/collect_backdrop.py rename to openpype/hosts/nuke/plugins/publish/collect_backdrop.py index 7e616ea7bb..4efbb88b8c 100644 --- a/pype/hosts/nuke/plugins/publish/collect_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/collect_backdrop.py @@ -1,5 +1,5 @@ import pyblish.api -from pype.hosts.nuke.api import lib as pnlib +from openpype.hosts.nuke.api import lib as pnlib import nuke diff --git a/pype/hosts/nuke/plugins/publish/collect_framerate.py b/openpype/hosts/nuke/plugins/publish/collect_framerate.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/collect_framerate.py rename to openpype/hosts/nuke/plugins/publish/collect_framerate.py diff --git a/pype/hosts/nuke/plugins/publish/collect_gizmo.py b/openpype/hosts/nuke/plugins/publish/collect_gizmo.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/collect_gizmo.py rename to openpype/hosts/nuke/plugins/publish/collect_gizmo.py diff --git a/pype/hosts/nuke/plugins/publish/collect_reads.py b/openpype/hosts/nuke/plugins/publish/collect_reads.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/collect_reads.py rename to openpype/hosts/nuke/plugins/publish/collect_reads.py diff --git a/pype/hosts/nuke/plugins/publish/collect_slate_node.py b/openpype/hosts/nuke/plugins/publish/collect_slate_node.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/collect_slate_node.py rename to openpype/hosts/nuke/plugins/publish/collect_slate_node.py diff --git a/pype/hosts/nuke/plugins/publish/extract_backdrop.py b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py similarity index 96% rename from pype/hosts/nuke/plugins/publish/extract_backdrop.py rename to openpype/hosts/nuke/plugins/publish/extract_backdrop.py index 92153d563e..13f8656005 100644 --- a/pype/hosts/nuke/plugins/publish/extract_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py @@ -1,12 +1,12 @@ import pyblish.api from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import lib as pnlib +from openpype.hosts.nuke.api import lib as pnlib import nuke import os -import pype +import openpype reload(pnlib) -class ExtractBackdropNode(pype.api.Extractor): +class ExtractBackdropNode(openpype.api.Extractor): """Extracting content of backdrop nodes Will create nuke script only with containing nodes. diff --git a/pype/hosts/nuke/plugins/publish/extract_camera.py b/openpype/hosts/nuke/plugins/publish/extract_camera.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/extract_camera.py rename to openpype/hosts/nuke/plugins/publish/extract_camera.py index 9a1efba1df..bc50dac108 100644 --- a/pype/hosts/nuke/plugins/publish/extract_camera.py +++ b/openpype/hosts/nuke/plugins/publish/extract_camera.py @@ -2,12 +2,12 @@ import nuke import os import math import pyblish.api -import pype.api +import openpype.api from avalon.nuke import lib as anlib from pprint import pformat -class ExtractCamera(pype.api.Extractor): +class ExtractCamera(openpype.api.Extractor): """ 3D camera exctractor """ label = 'Exctract Camera' diff --git a/pype/hosts/nuke/plugins/publish/extract_gizmo.py b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py similarity index 95% rename from pype/hosts/nuke/plugins/publish/extract_gizmo.py rename to openpype/hosts/nuke/plugins/publish/extract_gizmo.py index 32d9dd0f55..78bf9c998d 100644 --- a/pype/hosts/nuke/plugins/publish/extract_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py @@ -1,12 +1,12 @@ import pyblish.api from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import utils as pnutils +from openpype.hosts.nuke.api import utils as pnutils import nuke import os -import pype +import openpype -class ExtractGizmo(pype.api.Extractor): +class ExtractGizmo(openpype.api.Extractor): """Extracting Gizmo (Group) node Will create nuke script only with the Gizmo node. diff --git a/pype/hosts/nuke/plugins/publish/extract_ouput_node.py b/openpype/hosts/nuke/plugins/publish/extract_ouput_node.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/extract_ouput_node.py rename to openpype/hosts/nuke/plugins/publish/extract_ouput_node.py diff --git a/pype/hosts/nuke/plugins/publish/extract_output_directory.py b/openpype/hosts/nuke/plugins/publish/extract_output_directory.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/extract_output_directory.py rename to openpype/hosts/nuke/plugins/publish/extract_output_directory.py diff --git a/pype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py similarity index 97% rename from pype/hosts/nuke/plugins/publish/extract_render_local.py rename to openpype/hosts/nuke/plugins/publish/extract_render_local.py index 79662d62a8..49609f70e0 100644 --- a/pype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -1,11 +1,11 @@ import pyblish.api import nuke import os -import pype +import openpype import clique -class NukeRenderLocal(pype.api.Extractor): +class NukeRenderLocal(openpype.api.Extractor): # TODO: rewrite docstring to nuke """Render the current Fusion composition locally. diff --git a/pype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py similarity index 94% rename from pype/hosts/nuke/plugins/publish/extract_review_data_lut.py rename to openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 16ef7a9449..5611591b56 100644 --- a/pype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -1,12 +1,12 @@ import os import pyblish.api from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import lib as pnlib -import pype +from openpype.hosts.nuke.api import lib as pnlib +import openpype reload(pnlib) -class ExtractReviewDataLut(pype.api.Extractor): +class ExtractReviewDataLut(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/pype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py similarity index 94% rename from pype/hosts/nuke/plugins/publish/extract_review_data_mov.py rename to openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index c64c7adc93..5032e602a2 100644 --- a/pype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,11 +1,11 @@ import os import pyblish.api from avalon.nuke import lib as anlib -from pype.hosts.nuke.api import lib as pnlib -import pype +from openpype.hosts.nuke.api import lib as pnlib +import openpype -class ExtractReviewDataMov(pype.api.Extractor): +class ExtractReviewDataMov(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/pype/hosts/nuke/plugins/publish/extract_script_save.py b/openpype/hosts/nuke/plugins/publish/extract_script_save.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/extract_script_save.py rename to openpype/hosts/nuke/plugins/publish/extract_script_save.py diff --git a/pype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/extract_slate_frame.py rename to openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index e1c05c3d1a..0f68680742 100644 --- a/pype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -2,10 +2,10 @@ import os import nuke from avalon.nuke import lib as anlib import pyblish.api -import pype +import openpype -class ExtractSlateFrame(pype.api.Extractor): +class ExtractSlateFrame(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/pype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/extract_thumbnail.py rename to openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index fd3eaabe13..da30dcc632 100644 --- a/pype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -2,10 +2,10 @@ import os import nuke from avalon.nuke import lib as anlib import pyblish.api -import pype +import openpype -class ExtractThumbnail(pype.api.Extractor): +class ExtractThumbnail(openpype.api.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/pype/hosts/nuke/plugins/publish/increment_script_version.py b/openpype/hosts/nuke/plugins/publish/increment_script_version.py similarity index 93% rename from pype/hosts/nuke/plugins/publish/increment_script_version.py rename to openpype/hosts/nuke/plugins/publish/increment_script_version.py index c76083eb1e..47fccb9125 100644 --- a/pype/hosts/nuke/plugins/publish/increment_script_version.py +++ b/openpype/hosts/nuke/plugins/publish/increment_script_version.py @@ -17,7 +17,7 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): assert all(result["success"] for result in context.data["results"]), ( "Publishing not succesfull so version is not increased.") - from pype.lib import version_up + from openpype.lib import version_up path = context.data["currentFile"] nuke.scriptSaveAs(version_up(path)) self.log.info('Incrementing script version') diff --git a/pype/hosts/nuke/plugins/publish/precollect_instances.py b/openpype/hosts/nuke/plugins/publish/precollect_instances.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/precollect_instances.py rename to openpype/hosts/nuke/plugins/publish/precollect_instances.py diff --git a/pype/hosts/nuke/plugins/publish/precollect_workfile.py b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py similarity index 99% rename from pype/hosts/nuke/plugins/publish/precollect_workfile.py rename to openpype/hosts/nuke/plugins/publish/precollect_workfile.py index 288caa7a19..5d3eb5f609 100644 --- a/pype/hosts/nuke/plugins/publish/precollect_workfile.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_workfile.py @@ -1,7 +1,7 @@ import nuke import pyblish.api import os -import pype.api as pype +import openpype.api as pype from avalon.nuke import lib as anlib reload(anlib) diff --git a/pype/hosts/nuke/plugins/publish/precollect_writes.py b/openpype/hosts/nuke/plugins/publish/precollect_writes.py similarity index 99% rename from pype/hosts/nuke/plugins/publish/precollect_writes.py rename to openpype/hosts/nuke/plugins/publish/precollect_writes.py index 9806e38633..a519609f52 100644 --- a/pype/hosts/nuke/plugins/publish/precollect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/precollect_writes.py @@ -1,7 +1,7 @@ import os import nuke import pyblish.api -import pype.api as pype +import openpype.api as pype from avalon import io, api diff --git a/pype/hosts/nuke/plugins/publish/remove_ouput_node.py b/openpype/hosts/nuke/plugins/publish/remove_ouput_node.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/remove_ouput_node.py rename to openpype/hosts/nuke/plugins/publish/remove_ouput_node.py diff --git a/pype/hosts/nuke/plugins/publish/validate_backdrop.py b/openpype/hosts/nuke/plugins/publish/validate_backdrop.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/validate_backdrop.py rename to openpype/hosts/nuke/plugins/publish/validate_backdrop.py diff --git a/pype/hosts/nuke/plugins/publish/validate_gizmo.py b/openpype/hosts/nuke/plugins/publish/validate_gizmo.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/validate_gizmo.py rename to openpype/hosts/nuke/plugins/publish/validate_gizmo.py diff --git a/pype/hosts/nuke/plugins/publish/validate_knobs.py b/openpype/hosts/nuke/plugins/publish/validate_knobs.py similarity index 97% rename from pype/hosts/nuke/plugins/publish/validate_knobs.py rename to openpype/hosts/nuke/plugins/publish/validate_knobs.py index cbc02690cb..d290ff4541 100644 --- a/pype/hosts/nuke/plugins/publish/validate_knobs.py +++ b/openpype/hosts/nuke/plugins/publish/validate_knobs.py @@ -1,7 +1,7 @@ import nuke import pyblish.api -import pype.api +import openpype.api class ValidateKnobs(pyblish.api.ContextPlugin): @@ -23,7 +23,7 @@ class ValidateKnobs(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Knobs" hosts = ["nuke"] - actions = [pype.api.RepairContextAction] + actions = [openpype.api.RepairContextAction] optional = True def process(self, context): diff --git a/pype/hosts/nuke/plugins/publish/validate_output_resolution.py b/openpype/hosts/nuke/plugins/publish/validate_output_resolution.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/validate_output_resolution.py rename to openpype/hosts/nuke/plugins/publish/validate_output_resolution.py diff --git a/pype/hosts/nuke/plugins/publish/validate_read_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_read_legacy.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/validate_read_legacy.py rename to openpype/hosts/nuke/plugins/publish/validate_read_legacy.py diff --git a/pype/hosts/nuke/plugins/publish/validate_rendered_frames.py b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/validate_rendered_frames.py rename to openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py index 425789f18a..21afc5313b 100644 --- a/pype/hosts/nuke/plugins/publish/validate_rendered_frames.py +++ b/openpype/hosts/nuke/plugins/publish/validate_rendered_frames.py @@ -1,6 +1,6 @@ import os import pyblish.api -from pype.api import ValidationException +from openpype.api import ValidationException import clique diff --git a/pype/hosts/nuke/plugins/publish/validate_script.py b/openpype/hosts/nuke/plugins/publish/validate_script.py similarity index 99% rename from pype/hosts/nuke/plugins/publish/validate_script.py rename to openpype/hosts/nuke/plugins/publish/validate_script.py index 52980a8455..c35d09dcde 100644 --- a/pype/hosts/nuke/plugins/publish/validate_script.py +++ b/openpype/hosts/nuke/plugins/publish/validate_script.py @@ -1,6 +1,6 @@ import pyblish.api from avalon import io -from pype import lib +from openpype import lib @pyblish.api.log diff --git a/pype/hosts/nuke/plugins/publish/validate_write_bounding_box.py b/openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py similarity index 100% rename from pype/hosts/nuke/plugins/publish/validate_write_bounding_box.py rename to openpype/hosts/nuke/plugins/publish/validate_write_bounding_box.py diff --git a/pype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py b/openpype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py similarity index 86% rename from pype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py rename to openpype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py index e751a99771..72fd51a900 100644 --- a/pype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_deadline_tab.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.hosts.nuke.lib +import openpype.hosts.nuke.lib class RepairNukeWriteDeadlineTab(pyblish.api.Action): @@ -24,12 +24,12 @@ class RepairNukeWriteDeadlineTab(pyblish.api.Action): group_node = [x for x in instance if x.Class() == "Group"][0] # Remove exising knobs. - knob_names = pype.hosts.nuke.lib.get_deadline_knob_names() + knob_names = openpype.hosts.nuke.lib.get_deadline_knob_names() for name, knob in group_node.knobs().iteritems(): if name in knob_names: group_node.removeKnob(knob) - pype.hosts.nuke.lib.add_deadline_tab(group_node) + openpype.hosts.nuke.lib.add_deadline_tab(group_node) class ValidateNukeWriteDeadlineTab(pyblish.api.InstancePlugin): @@ -45,7 +45,7 @@ class ValidateNukeWriteDeadlineTab(pyblish.api.InstancePlugin): def process(self, instance): group_node = [x for x in instance if x.Class() == "Group"][0] - knob_names = pype.hosts.nuke.lib.get_deadline_knob_names() + knob_names = openpype.hosts.nuke.lib.get_deadline_knob_names() missing_knobs = [] for name in knob_names: if name not in group_node.knobs().keys(): diff --git a/pype/hosts/nuke/plugins/publish/validate_write_legacy.py b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py similarity index 98% rename from pype/hosts/nuke/plugins/publish/validate_write_legacy.py rename to openpype/hosts/nuke/plugins/publish/validate_write_legacy.py index 18f92336ab..ba34ec8338 100644 --- a/pype/hosts/nuke/plugins/publish/validate_write_legacy.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_legacy.py @@ -6,7 +6,7 @@ import nuke from avalon import api import re import pyblish.api -import pype.api +import openpype.api from avalon.nuke import get_avalon_knob_data @@ -18,7 +18,7 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): families = ["write"] label = "Validate Write Legacy" hosts = ["nuke"] - actions = [pype.api.RepairAction] + actions = [openpype.api.RepairAction] def process(self, instance): node = instance[0] diff --git a/pype/hosts/nuke/plugins/publish/validate_write_nodes.py b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py similarity index 93% rename from pype/hosts/nuke/plugins/publish/validate_write_nodes.py rename to openpype/hosts/nuke/plugins/publish/validate_write_nodes.py index bd33194897..732f321b85 100644 --- a/pype/hosts/nuke/plugins/publish/validate_write_nodes.py +++ b/openpype/hosts/nuke/plugins/publish/validate_write_nodes.py @@ -1,7 +1,7 @@ import os import pyblish.api -import pype.utils -import pype.hosts.nuke.lib as nukelib +import openpype.utils +import openpype.hosts.nuke.lib as nukelib import avalon.nuke @pyblish.api.log @@ -11,7 +11,7 @@ class RepairNukeWriteNodeAction(pyblish.api.Action): icon = "wrench" def process(self, context, plugin): - instances = pype.utils.filter_instances(context, plugin) + instances = openpype.utils.filter_instances(context, plugin) for instance in instances: node = instance[1] diff --git a/pype/hosts/nuke/startup/KnobScripter/__init__.py b/openpype/hosts/nuke/startup/KnobScripter/__init__.py similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/__init__.py rename to openpype/hosts/nuke/startup/KnobScripter/__init__.py diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_clearConsole.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_download.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_download.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_download.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_download.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_exitnode.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_pick.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_prefs2.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_refresh.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_run.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_run.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_run.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_save.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_save.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_save.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_search.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_search.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_search.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_search.png diff --git a/pype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png b/openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png rename to openpype/hosts/nuke/startup/KnobScripter/icons/icon_snippets.png diff --git a/pype/hosts/nuke/startup/KnobScripter/knob_scripter.py b/openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py similarity index 100% rename from pype/hosts/nuke/startup/KnobScripter/knob_scripter.py rename to openpype/hosts/nuke/startup/KnobScripter/knob_scripter.py diff --git a/pype/hosts/nuke/startup/init.py b/openpype/hosts/nuke/startup/init.py similarity index 100% rename from pype/hosts/nuke/startup/init.py rename to openpype/hosts/nuke/startup/init.py diff --git a/pype/hosts/nuke/startup/menu.py b/openpype/hosts/nuke/startup/menu.py similarity index 80% rename from pype/hosts/nuke/startup/menu.py rename to openpype/hosts/nuke/startup/menu.py index 8e2e06a169..9eb656afa9 100644 --- a/pype/hosts/nuke/startup/menu.py +++ b/openpype/hosts/nuke/startup/menu.py @@ -1,10 +1,10 @@ -from pype.hosts.nuke.api.lib import ( +from openpype.hosts.nuke.api.lib import ( on_script_load, check_inventory_versions ) import nuke -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/hosts/nuke/startup/write_to_read.py b/openpype/hosts/nuke/startup/write_to_read.py similarity index 99% rename from pype/hosts/nuke/startup/write_to_read.py rename to openpype/hosts/nuke/startup/write_to_read.py index d4556fa64c..deb5ce1b82 100644 --- a/pype/hosts/nuke/startup/write_to_read.py +++ b/openpype/hosts/nuke/startup/write_to_read.py @@ -2,7 +2,7 @@ import re import os import glob import nuke -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) SINGLE_FILE_FORMATS = ['avi', 'mp4', 'mxf', 'mov', 'mpg', 'mpeg', 'wmv', 'm4v', diff --git a/pype/hosts/photoshop/__init__.py b/openpype/hosts/photoshop/__init__.py similarity index 100% rename from pype/hosts/photoshop/__init__.py rename to openpype/hosts/photoshop/__init__.py diff --git a/pype/hosts/photoshop/api/__init__.py b/openpype/hosts/photoshop/api/__init__.py similarity index 94% rename from pype/hosts/photoshop/api/__init__.py rename to openpype/hosts/photoshop/api/__init__.py index 78d5311fed..7304574ffd 100644 --- a/pype/hosts/photoshop/api/__init__.py +++ b/openpype/hosts/photoshop/api/__init__.py @@ -5,13 +5,13 @@ import logging from avalon import io from avalon import api as avalon from avalon.vendor import Qt -from pype import lib +from openpype import lib from pyblish import api as pyblish -import pype.hosts.photoshop +import openpype.hosts.photoshop log = logging.getLogger("pype.hosts.photoshop") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.photoshop.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.photoshop.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") diff --git a/pype/hosts/photoshop/hooks/__init__.py b/openpype/hosts/photoshop/hooks/__init__.py similarity index 100% rename from pype/hosts/photoshop/hooks/__init__.py rename to openpype/hosts/photoshop/hooks/__init__.py diff --git a/pype/hosts/photoshop/plugins/__init__.py b/openpype/hosts/photoshop/plugins/__init__.py similarity index 100% rename from pype/hosts/photoshop/plugins/__init__.py rename to openpype/hosts/photoshop/plugins/__init__.py diff --git a/pype/hosts/photoshop/plugins/create/create_image.py b/openpype/hosts/photoshop/plugins/create/create_image.py similarity index 98% rename from pype/hosts/photoshop/plugins/create/create_image.py rename to openpype/hosts/photoshop/plugins/create/create_image.py index 03250acd48..21b5ea9243 100644 --- a/pype/hosts/photoshop/plugins/create/create_image.py +++ b/openpype/hosts/photoshop/plugins/create/create_image.py @@ -1,9 +1,9 @@ -import pype.api +import openpype.api from avalon.vendor import Qt from avalon import photoshop -class CreateImage(pype.api.Creator): +class CreateImage(openpype.api.Creator): """Image folder for publish.""" name = "imageDefault" diff --git a/pype/hosts/photoshop/plugins/load/load_image.py b/openpype/hosts/photoshop/plugins/load/load_image.py similarity index 100% rename from pype/hosts/photoshop/plugins/load/load_image.py rename to openpype/hosts/photoshop/plugins/load/load_image.py diff --git a/pype/hosts/photoshop/plugins/publish/collect_current_file.py b/openpype/hosts/photoshop/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/photoshop/plugins/publish/collect_current_file.py rename to openpype/hosts/photoshop/plugins/publish/collect_current_file.py diff --git a/pype/hosts/photoshop/plugins/publish/collect_instances.py b/openpype/hosts/photoshop/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/photoshop/plugins/publish/collect_instances.py rename to openpype/hosts/photoshop/plugins/publish/collect_instances.py diff --git a/pype/hosts/photoshop/plugins/publish/collect_review.py b/openpype/hosts/photoshop/plugins/publish/collect_review.py similarity index 100% rename from pype/hosts/photoshop/plugins/publish/collect_review.py rename to openpype/hosts/photoshop/plugins/publish/collect_review.py diff --git a/pype/hosts/photoshop/plugins/publish/collect_workfile.py b/openpype/hosts/photoshop/plugins/publish/collect_workfile.py similarity index 100% rename from pype/hosts/photoshop/plugins/publish/collect_workfile.py rename to openpype/hosts/photoshop/plugins/publish/collect_workfile.py diff --git a/pype/hosts/photoshop/plugins/publish/extract_image.py b/openpype/hosts/photoshop/plugins/publish/extract_image.py similarity index 97% rename from pype/hosts/photoshop/plugins/publish/extract_image.py rename to openpype/hosts/photoshop/plugins/publish/extract_image.py index af76caf379..b56f128831 100644 --- a/pype/hosts/photoshop/plugins/publish/extract_image.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_image.py @@ -1,10 +1,10 @@ import os -import pype.api +import openpype.api from avalon import photoshop -class ExtractImage(pype.api.Extractor): +class ExtractImage(openpype.api.Extractor): """Produce a flattened image file from instance This plug-in takes into account only the layers in the group. diff --git a/pype/hosts/photoshop/plugins/publish/extract_review.py b/openpype/hosts/photoshop/plugins/publish/extract_review.py similarity index 92% rename from pype/hosts/photoshop/plugins/publish/extract_review.py rename to openpype/hosts/photoshop/plugins/publish/extract_review.py index aa9151bf6d..3b6d8ef951 100644 --- a/pype/hosts/photoshop/plugins/publish/extract_review.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_review.py @@ -1,11 +1,11 @@ import os -import pype.api -import pype.lib +import openpype.api +import openpype.lib from avalon import photoshop -class ExtractReview(pype.api.Extractor): +class ExtractReview(openpype.api.Extractor): """Produce a flattened image file from all instances.""" label = "Extract Review" @@ -41,7 +41,7 @@ class ExtractReview(pype.api.Extractor): stub.saveAs(output_image_path, 'jpg', True) - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") instance.data["representations"].append({ "name": "jpg", @@ -60,7 +60,7 @@ class ExtractReview(pype.api.Extractor): "-vframes", "1", thumbnail_path ] - output = pype.lib.run_subprocess(args) + output = openpype.lib.run_subprocess(args) instance.data["representations"].append({ "name": "thumbnail", @@ -78,7 +78,7 @@ class ExtractReview(pype.api.Extractor): "-vframes", "1", mov_path ] - output = pype.lib.run_subprocess(args) + output = openpype.lib.run_subprocess(args) self.log.debug(output) instance.data["representations"].append({ "name": "mov", diff --git a/pype/hosts/photoshop/plugins/publish/extract_save_scene.py b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py similarity index 65% rename from pype/hosts/photoshop/plugins/publish/extract_save_scene.py rename to openpype/hosts/photoshop/plugins/publish/extract_save_scene.py index 63a4b7b7ea..0180640c90 100644 --- a/pype/hosts/photoshop/plugins/publish/extract_save_scene.py +++ b/openpype/hosts/photoshop/plugins/publish/extract_save_scene.py @@ -1,11 +1,11 @@ -import pype.api +import openpype.api from avalon import photoshop -class ExtractSaveScene(pype.api.Extractor): +class ExtractSaveScene(openpype.api.Extractor): """Save scene before extraction.""" - order = pype.api.Extractor.order - 0.49 + order = openpype.api.Extractor.order - 0.49 label = "Extract Save Scene" hosts = ["photoshop"] families = ["workfile"] diff --git a/pype/hosts/photoshop/plugins/publish/increment_workfile.py b/openpype/hosts/photoshop/plugins/publish/increment_workfile.py similarity index 90% rename from pype/hosts/photoshop/plugins/publish/increment_workfile.py rename to openpype/hosts/photoshop/plugins/publish/increment_workfile.py index 2005973ea0..709fb988fc 100644 --- a/pype/hosts/photoshop/plugins/publish/increment_workfile.py +++ b/openpype/hosts/photoshop/plugins/publish/increment_workfile.py @@ -1,7 +1,7 @@ import os import pyblish.api -from pype.action import get_errored_plugins_from_data -from pype.lib import version_up +from openpype.action import get_errored_plugins_from_data +from openpype.lib import version_up from avalon import photoshop diff --git a/pype/hosts/photoshop/plugins/publish/validate_instance_asset.py b/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py similarity index 95% rename from pype/hosts/photoshop/plugins/publish/validate_instance_asset.py rename to openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py index f05d9601dd..a1de02f319 100644 --- a/pype/hosts/photoshop/plugins/publish/validate_instance_asset.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_instance_asset.py @@ -1,7 +1,7 @@ import os import pyblish.api -import pype.api +import openpype.api from avalon import photoshop @@ -37,7 +37,7 @@ class ValidateInstanceAsset(pyblish.api.InstancePlugin): label = "Validate Instance Asset" hosts = ["photoshop"] actions = [ValidateInstanceAssetRepair] - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder def process(self, instance): instance_asset = instance.data["asset"] diff --git a/pype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py similarity index 96% rename from pype/hosts/photoshop/plugins/publish/validate_naming.py rename to openpype/hosts/photoshop/plugins/publish/validate_naming.py index 48f5901233..0fd6794313 100644 --- a/pype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api from avalon import photoshop @@ -45,7 +45,7 @@ class ValidateNaming(pyblish.api.InstancePlugin): label = "Validate Naming" hosts = ["photoshop"] - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["image"] actions = [ValidateNamingRepair] diff --git a/pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py b/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py similarity index 90% rename from pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py rename to openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py index 5871f79668..15ae5fbcea 100644 --- a/pype/hosts/photoshop/plugins/publish/validate_unique_subsets.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): @@ -9,7 +9,7 @@ class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): label = "Validate Subset Uniqueness" hosts = ["photoshop"] - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["image"] def process(self, context): diff --git a/pype/hosts/photoshop/resources/template.psd b/openpype/hosts/photoshop/resources/template.psd similarity index 100% rename from pype/hosts/photoshop/resources/template.psd rename to openpype/hosts/photoshop/resources/template.psd diff --git a/pype/hosts/resolve/README.markdown b/openpype/hosts/resolve/README.markdown similarity index 86% rename from pype/hosts/resolve/README.markdown rename to openpype/hosts/resolve/README.markdown index f8d2da0794..50664fbd21 100644 --- a/pype/hosts/resolve/README.markdown +++ b/openpype/hosts/resolve/README.markdown @@ -1,7 +1,7 @@ #### Basic setup - Install [latest DaVinci Resolve](https://sw.blackmagicdesign.com/DaVinciResolve/v16.2.8/DaVinci_Resolve_Studio_16.2.8_Windows.zip?Key-Pair-Id=APKAJTKA3ZJMJRQITVEA&Signature=EcFuwQFKHZIBu2zDj5LTCQaQDXcKOjhZY7Fs07WGw24xdDqfwuALOyKu+EVzDX2Tik0cWDunYyV0r7hzp+mHmczp9XP4YaQXHdyhD/2BGWDgiMsiTQbNkBgbfy5MsAMFY8FHCl724Rxm8ke1foWeUVyt/Cdkil+ay+9sL72yFhaSV16sncko1jCIlCZeMkHhbzqPwyRuqLGmxmp8ey9KgBhI3wGFFPN201VMaV+RHrpX+KAfaR6p6dwo3FrPbRHK9TvMI1RA/1lJ3fVtrkDW69LImIKAWmIxgcStUxR9/taqLOD66FNiflHd1tufHv3FBa9iYQsjb3VLMPx7OCwLyg==&Expires=1608308139) -- add absolute path to ffmpeg into pype settings +- add absolute path to ffmpeg into openpype settings ![image](https://user-images.githubusercontent.com/40640033/102630786-43294f00-414d-11eb-98de-f0ae51f62077.png) - install Python 3.6 into `%LOCALAPPDATA%/Programs/Python/Python36` (only respected path by Resolve) - install OpenTimelineIO for 3.6 `%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move builded files from `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `%LOCALAPPDATA%/Programs/Python/Python36/Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and @@ -16,11 +16,11 @@ This is how it looks on my testing project timeline ![image](https://user-images.githubusercontent.com/40640033/102637638-96ec6600-4156-11eb-9656-6e8e3ce4baf8.png) Notice I had renamed tracks to `main` (holding metadata markers) and `review` used for generating review data with ffmpeg confersion to jpg sequence. -1. you need to start Pype menu from Resolve/EditTab/Menu/Workspace/Scripts/**PYPE_MENU** +1. you need to start OpenPype menu from Resolve/EditTab/Menu/Workspace/Scripts/**__OpenPype_Menu__** 2. then select any clips in `main` track and change their color to `Chocolate` -3. in Pype Menu select `Create` +3. in OpenPype Menu select `Create` 4. in Creator select `Create Publishable Clip [New]` (temporary name) 5. set `Rename clips` to True, Master Track to `main` and Use review track to `review` as in picture ![image](https://user-images.githubusercontent.com/40640033/102643773-0d419600-4160-11eb-919e-9c2be0aecab8.png) -6. after you hit `ok` all clips are colored to `ping` and marked with pype metadata tag -7. git `Publish` on pype menu and see that all had been collected correctly. That is the last step for now as rest is Work in progress. Next steps will follow. +6. after you hit `ok` all clips are colored to `ping` and marked with openpype metadata tag +7. git `Publish` on openpype menu and see that all had been collected correctly. That is the last step for now as rest is Work in progress. Next steps will follow. diff --git a/pype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt b/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt similarity index 100% rename from pype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt rename to openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_down.txt diff --git a/pype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt b/openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt similarity index 100% rename from pype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt rename to openpype/hosts/resolve/RESOLVE_API_README_v16.2.0_up.txt diff --git a/pype/hosts/resolve/__init__.py b/openpype/hosts/resolve/__init__.py similarity index 100% rename from pype/hosts/resolve/__init__.py rename to openpype/hosts/resolve/__init__.py diff --git a/pype/hosts/resolve/api/__init__.py b/openpype/hosts/resolve/api/__init__.py similarity index 100% rename from pype/hosts/resolve/api/__init__.py rename to openpype/hosts/resolve/api/__init__.py diff --git a/pype/hosts/resolve/api/action.py b/openpype/hosts/resolve/api/action.py similarity index 100% rename from pype/hosts/resolve/api/action.py rename to openpype/hosts/resolve/api/action.py diff --git a/pype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py similarity index 97% rename from pype/hosts/resolve/api/lib.py rename to openpype/hosts/resolve/api/lib.py index 11bf8a3217..101e7bb572 100644 --- a/pype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -4,11 +4,11 @@ import re import os import contextlib from opentimelineio import opentime -import pype +import openpype from ..otio import davinci_export as otio_export -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) @@ -16,24 +16,24 @@ self = sys.modules[__name__] self.project_manager = None self.media_storage = None -# Pype sequencial rename variables +# OpenPype sequencial rename variables self.rename_index = 0 self.rename_add = 0 self.publish_clip_color = "Pink" self.pype_marker_workflow = True -# Pype compound clip workflow variable +# OpenPype compound clip workflow variable self.pype_tag_name = "VFX Notes" -# Pype marker workflow variables -self.pype_marker_name = "PYPEDATA" +# OpenPype marker workflow variables +self.pype_marker_name = "OpenPypeData" self.pype_marker_duration = 1 self.pype_marker_color = "Mint" self.temp_marker_frame = None -# Pype default timeline -self.pype_timeline_name = "PypeTimeline" +# OpenPype default timeline +self.pype_timeline_name = "OpenPypeTimeline" @contextlib.contextmanager @@ -360,13 +360,13 @@ def get_pype_timeline_item_by_name(name: str) -> object: def get_timeline_item_pype_tag(timeline_item): """ - Get pype track item tag created by creator or loader plugin. + Get openpype track item tag created by creator or loader plugin. Attributes: trackItem (resolve.TimelineItem): resolve object Returns: - dict: pype tag data + dict: openpype tag data """ return_tag = None @@ -389,7 +389,7 @@ def get_timeline_item_pype_tag(timeline_item): def set_timeline_item_pype_tag(timeline_item, data=None): """ - Set pype track item tag to input timeline_item. + Set openpype track item tag to input timeline_item. Attributes: trackItem (resolve.TimelineItem): resolve api object @@ -399,7 +399,7 @@ def set_timeline_item_pype_tag(timeline_item, data=None): """ data = data or dict() - # get available pype tag if any + # get available openpype tag if any tag_data = get_timeline_item_pype_tag(timeline_item) if self.pype_marker_workflow: @@ -418,7 +418,7 @@ def set_timeline_item_pype_tag(timeline_item, data=None): self.pype_tag_name, json.dumps(tag_data)) else: tag_data = data - # if pype tag available then update with input data + # if openpype tag available then update with input data # add it to the input track item timeline_item.SetMetadata(self.pype_tag_name, json.dumps(tag_data)) @@ -672,7 +672,7 @@ def _validate_tc(x): def get_pype_clip_metadata(clip): """ - Get pype metadata created by creator plugin + Get openpype metadata created by creator plugin Attributes: clip (resolve.TimelineItem): resolve's object @@ -814,7 +814,7 @@ def get_otio_clip_instance_data(otio_timeline, timeline_item_data): continue if otio_clip.name not in timeline_item.GetName(): continue - if pype.lib.is_overlapping_otio_ranges( + if openpype.lib.is_overlapping_otio_ranges( parent_range, timeline_range, strict=True): # add pypedata marker to otio_clip metadata diff --git a/pype/hosts/resolve/api/menu.py b/openpype/hosts/resolve/api/menu.py similarity index 70% rename from pype/hosts/resolve/api/menu.py rename to openpype/hosts/resolve/api/menu.py index 73ea937513..5ed7aeab34 100644 --- a/pype/hosts/resolve/api/menu.py +++ b/openpype/hosts/resolve/api/menu.py @@ -44,11 +44,11 @@ class Spacer(QtWidgets.QWidget): self.setLayout(layout) -class PypeMenu(QtWidgets.QWidget): +class OpenPypeMenu(QtWidgets.QWidget): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) - self.setObjectName("PypeMenu") + self.setObjectName("OpenPypeMenu") self.setWindowFlags( QtCore.Qt.Window @@ -58,20 +58,20 @@ class PypeMenu(QtWidgets.QWidget): | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle("Pype") - workfiles_btn = QtWidgets.QPushButton("Workfiles", self) - create_btn = QtWidgets.QPushButton("Create", self) - publish_btn = QtWidgets.QPushButton("Publish", self) - load_btn = QtWidgets.QPushButton("Load", self) - inventory_btn = QtWidgets.QPushButton("Inventory", self) - libload_btn = QtWidgets.QPushButton("Library", self) - rename_btn = QtWidgets.QPushButton("Rename", self) - set_colorspace_btn = QtWidgets.QPushButton( - "Set colorspace from presets", self - ) - reset_resolution_btn = QtWidgets.QPushButton( - "Reset Resolution from peresets", self - ) + self.setWindowTitle("OpenPype") + workfiles_btn = QtWidgets.QPushButton("Workfiles ...", self) + create_btn = QtWidgets.QPushButton("Create ...", self) + publish_btn = QtWidgets.QPushButton("Publish ...", self) + load_btn = QtWidgets.QPushButton("Load ...", self) + inventory_btn = QtWidgets.QPushButton("Inventory ...", self) + libload_btn = QtWidgets.QPushButton("Library ...", self) + # rename_btn = QtWidgets.QPushButton("Rename ...", self) + # set_colorspace_btn = QtWidgets.QPushButton( + # "Set colorspace from presets", self + # ) + # reset_resolution_btn = QtWidgets.QPushButton( + # "Reset Resolution from peresets", self + # ) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(10, 20, 10, 20) @@ -86,14 +86,14 @@ class PypeMenu(QtWidgets.QWidget): layout.addWidget(libload_btn) - layout.addWidget(Spacer(15, self)) + # layout.addWidget(Spacer(15, self)) - layout.addWidget(rename_btn) + # layout.addWidget(rename_btn) - layout.addWidget(Spacer(15, self)) + # layout.addWidget(Spacer(15, self)) - layout.addWidget(set_colorspace_btn) - layout.addWidget(reset_resolution_btn) + # layout.addWidget(set_colorspace_btn) + # layout.addWidget(reset_resolution_btn) self.setLayout(layout) @@ -103,9 +103,9 @@ class PypeMenu(QtWidgets.QWidget): load_btn.clicked.connect(self.on_load_clicked) inventory_btn.clicked.connect(self.on_inventory_clicked) libload_btn.clicked.connect(self.on_libload_clicked) - rename_btn.clicked.connect(self.on_rename_clicked) - set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) - reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + # rename_btn.clicked.connect(self.on_rename_clicked) + # set_colorspace_btn.clicked.connect(self.on_set_colorspace_clicked) + # reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) def on_workfile_clicked(self): print("Clicked Workfile") @@ -144,7 +144,7 @@ class PypeMenu(QtWidgets.QWidget): def launch_pype_menu(): app = QtWidgets.QApplication(sys.argv) - pype_menu = PypeMenu() + pype_menu = OpenPypeMenu() stylesheet = load_stylesheet() pype_menu.setStyleSheet(stylesheet) diff --git a/pype/hosts/resolve/api/menu_style.qss b/openpype/hosts/resolve/api/menu_style.qss similarity index 93% rename from pype/hosts/resolve/api/menu_style.qss rename to openpype/hosts/resolve/api/menu_style.qss index 5a1d39fe79..d2d3d1ed37 100644 --- a/pype/hosts/resolve/api/menu_style.qss +++ b/openpype/hosts/resolve/api/menu_style.qss @@ -51,7 +51,9 @@ QLineEdit { qproperty-alignment: AlignCenter; } -#PypeMenu { +#OpenPypeMenu { + qproperty-alignment: AlignLeft; + min-width: 10em; border: 1px solid #fef9ef; } diff --git a/pype/hosts/resolve/api/pipeline.py b/openpype/hosts/resolve/api/pipeline.py similarity index 96% rename from pype/hosts/resolve/api/pipeline.py rename to openpype/hosts/resolve/api/pipeline.py index 2d08203650..d4d928a7d9 100644 --- a/pype/hosts/resolve/api/pipeline.py +++ b/openpype/hosts/resolve/api/pipeline.py @@ -9,7 +9,7 @@ from avalon import api as avalon from avalon import schema from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish -from pype.api import Logger +from openpype.api import Logger from . import lib from . import PLUGINS_DIR log = Logger().get_logger(__name__) @@ -110,7 +110,7 @@ def containerise(timeline_item, """ data_imprint = OrderedDict({ - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": AVALON_CONTAINER_ID, "name": str(name), "namespace": str(namespace), @@ -150,7 +150,7 @@ def ls(): def parse_container(timeline_item, validate=True): - """Return container data from timeline_item's pype tag. + """Return container data from timeline_item's openpype tag. Args: timeline_item (hiero.core.TrackItem): A containerised track item. @@ -187,7 +187,7 @@ def parse_container(timeline_item, validate=True): def update_container(timeline_item, data=None): - """Update container data to input timeline_item's pype tag. + """Update container data to input timeline_item's openpype tag. Args: timeline_item (hiero.core.TrackItem): A containerised track item. @@ -251,7 +251,7 @@ def on_pyblish_instance_toggled(instance, old_value, new_value): log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) - from pype.hosts.resolve import ( + from openpype.hosts.resolve import ( set_publish_attribute ) diff --git a/pype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py similarity index 98% rename from pype/hosts/resolve/api/plugin.py rename to openpype/hosts/resolve/api/plugin.py index 0423f15c2a..3833795b96 100644 --- a/pype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -1,7 +1,7 @@ import re from avalon import api -import pype.api as pype -from pype.hosts import resolve +import openpype.api as pype +from openpype.hosts import resolve from avalon.vendor import qargparse from . import lib @@ -25,7 +25,7 @@ class CreatorWidget(QtWidgets.QDialog): | QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint ) - self.setWindowTitle(name or "Pype Creator Input") + self.setWindowTitle(name or "OpenPype Creator Input") self.resize(500, 700) # Where inputs and labels are set @@ -81,7 +81,7 @@ class CreatorWidget(QtWidgets.QDialog): ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) - stylesheet = resolve.menu.load_stylesheet() + stylesheet = resolve.api.menu.load_stylesheet() self.setStyleSheet(stylesheet) def _on_ok_clicked(self): @@ -499,7 +499,7 @@ class Creator(pype.PypeCreatorMixin, api.Creator): def __init__(self, *args, **kwargs): super(Creator, self).__init__(*args, **kwargs) - from pype.api import get_current_project_settings + from openpype.api import get_current_project_settings resolve_p_settings = get_current_project_settings().get("resolve") self.presets = dict() if resolve_p_settings: @@ -527,7 +527,7 @@ class PublishClip: kwargs (optional): additional data needed for rename=True (presets) Returns: - hiero.core.TrackItem: hiero track item object with pype tag + hiero.core.TrackItem: hiero track item object with openpype tag """ vertical_clip_match = dict() tag_data = dict() @@ -623,7 +623,7 @@ class PublishClip: "track_data": self.timeline_item_data["track"] }) - # create pype tag on timeline_item and add data + # create openpype tag on timeline_item and add data lib.imprint(self.timeline_item, self.tag_data) return self.timeline_item diff --git a/pype/hosts/resolve/api/preload_console.py b/openpype/hosts/resolve/api/preload_console.py similarity index 88% rename from pype/hosts/resolve/api/preload_console.py rename to openpype/hosts/resolve/api/preload_console.py index de55c3673c..1e3a56b4dd 100644 --- a/pype/hosts/resolve/api/preload_console.py +++ b/openpype/hosts/resolve/api/preload_console.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import time -from pype.hosts.resolve.utils import get_resolve_module -from pype.api import Logger +from openpype.hosts.resolve.utils import get_resolve_module +from openpype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/hosts/resolve/api/testing_utils.py b/openpype/hosts/resolve/api/testing_utils.py similarity index 100% rename from pype/hosts/resolve/api/testing_utils.py rename to openpype/hosts/resolve/api/testing_utils.py diff --git a/pype/hosts/resolve/api/todo-rendering.py b/openpype/hosts/resolve/api/todo-rendering.py similarity index 98% rename from pype/hosts/resolve/api/todo-rendering.py rename to openpype/hosts/resolve/api/todo-rendering.py index 87b04dd98f..5238d76dec 100644 --- a/pype/hosts/resolve/api/todo-rendering.py +++ b/openpype/hosts/resolve/api/todo-rendering.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# TODO: convert this script to be usable with PYPE +# TODO: convert this script to be usable with OpenPype """ Example DaVinci Resolve script: Load a still from DRX file, apply the still to all clips in all timelines. diff --git a/pype/hosts/resolve/api/utils.py b/openpype/hosts/resolve/api/utils.py similarity index 97% rename from pype/hosts/resolve/api/utils.py rename to openpype/hosts/resolve/api/utils.py index 32ac5b2aa9..3dee17cb01 100644 --- a/pype/hosts/resolve/api/utils.py +++ b/openpype/hosts/resolve/api/utils.py @@ -8,12 +8,12 @@ import sys import os import shutil from . import HOST_DIR -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) def get_resolve_module(): - from pype.hosts import resolve + from openpype.hosts import resolve # dont run if already loaded if resolve.api.bmdvr: log.info(("resolve module is assigned to " @@ -146,4 +146,4 @@ def setup(env=None): # synchronize resolve utility scripts _sync_utility_scripts(env) - log.info("Resolve Pype wrapper has been installed") + log.info("Resolve OpenPype wrapper has been installed") diff --git a/pype/hosts/resolve/api/workio.py b/openpype/hosts/resolve/api/workio.py similarity index 98% rename from pype/hosts/resolve/api/workio.py rename to openpype/hosts/resolve/api/workio.py index 59381ae50c..f175769387 100644 --- a/pype/hosts/resolve/api/workio.py +++ b/openpype/hosts/resolve/api/workio.py @@ -1,7 +1,7 @@ """Host API required Work Files tool""" import os -from pype.api import Logger +from openpype.api import Logger from .. import ( get_project_manager, get_current_project, diff --git a/pype/hosts/resolve/hooks/pre_resolve_setup.py b/openpype/hosts/resolve/hooks/pre_resolve_setup.py similarity index 93% rename from pype/hosts/resolve/hooks/pre_resolve_setup.py rename to openpype/hosts/resolve/hooks/pre_resolve_setup.py index d19c56fe09..0ee55d3790 100644 --- a/pype/hosts/resolve/hooks/pre_resolve_setup.py +++ b/openpype/hosts/resolve/hooks/pre_resolve_setup.py @@ -1,7 +1,7 @@ import os import importlib -from pype.lib import PreLaunchHook -from pype.hosts.resolve.api import utils +from openpype.lib import PreLaunchHook +from openpype.hosts.resolve.api import utils class ResolvePrelaunch(PreLaunchHook): @@ -14,7 +14,7 @@ class ResolvePrelaunch(PreLaunchHook): app_groups = ["resolve"] def execute(self): - # TODO: add OTIO installation from `pype/requirements.py` + # TODO: add OTIO installation from `openpype/requirements.py` # making sure pyton 3.6 is installed at provided path py36_dir = os.path.normpath( self.launch_context.env.get("PYTHON36_RESOLVE", "")) diff --git a/pype/hosts/resolve/otio/__init__.py b/openpype/hosts/resolve/otio/__init__.py similarity index 100% rename from pype/hosts/resolve/otio/__init__.py rename to openpype/hosts/resolve/otio/__init__.py diff --git a/pype/hosts/resolve/otio/davinci_export.py b/openpype/hosts/resolve/otio/davinci_export.py similarity index 100% rename from pype/hosts/resolve/otio/davinci_export.py rename to openpype/hosts/resolve/otio/davinci_export.py diff --git a/pype/hosts/resolve/otio/davinci_import.py b/openpype/hosts/resolve/otio/davinci_import.py similarity index 100% rename from pype/hosts/resolve/otio/davinci_import.py rename to openpype/hosts/resolve/otio/davinci_import.py diff --git a/pype/hosts/resolve/otio/utils.py b/openpype/hosts/resolve/otio/utils.py similarity index 100% rename from pype/hosts/resolve/otio/utils.py rename to openpype/hosts/resolve/otio/utils.py diff --git a/pype/hosts/resolve/plugins/create/create_shot_clip.py b/openpype/hosts/resolve/plugins/create/create_shot_clip.py similarity index 98% rename from pype/hosts/resolve/plugins/create/create_shot_clip.py rename to openpype/hosts/resolve/plugins/create/create_shot_clip.py index 09b2b73775..2916a52298 100644 --- a/pype/hosts/resolve/plugins/create/create_shot_clip.py +++ b/openpype/hosts/resolve/plugins/create/create_shot_clip.py @@ -1,6 +1,6 @@ # from pprint import pformat -from pype.hosts import resolve -from pype.hosts.resolve import lib +from openpype.hosts import resolve +from openpype.hosts.resolve.api import lib class CreateShotClip(resolve.Creator): @@ -12,7 +12,7 @@ class CreateShotClip(resolve.Creator): defaults = ["Main"] gui_tracks = resolve.get_video_track_names() - gui_name = "Pype publish attributes creator" + gui_name = "OpenPype publish attributes creator" gui_info = "Define sequential rename and fill hierarchy data." gui_inputs = { "renameHierarchy": { @@ -244,7 +244,7 @@ class CreateShotClip(resolve.Creator): sq_markers = self.timeline.GetMarkers() # create media bin for compound clips (trackItems) - mp_folder = resolve.create_current_sequence_media_bin(self.timeline) + mp_folder = resolve.create_bin(self.timeline.GetName()) kwargs = { "ui_inputs": widget.result, diff --git a/pype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py similarity index 99% rename from pype/hosts/resolve/plugins/load/load_clip.py rename to openpype/hosts/resolve/plugins/load/load_clip.py index 13f713a64f..e2e1c50365 100644 --- a/pype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -1,5 +1,5 @@ from avalon import io, api -from pype.hosts import resolve +from openpype.hosts import resolve from copy import deepcopy diff --git a/pype/hosts/resolve/plugins/publish/collect_instances.py b/openpype/hosts/resolve/plugins/publish/collect_instances.py similarity index 98% rename from pype/hosts/resolve/plugins/publish/collect_instances.py rename to openpype/hosts/resolve/plugins/publish/collect_instances.py index b1eafd512e..f4eeb39754 100644 --- a/pype/hosts/resolve/plugins/publish/collect_instances.py +++ b/openpype/hosts/resolve/plugins/publish/collect_instances.py @@ -1,5 +1,5 @@ import pyblish -from pype.hosts import resolve +from openpype.hosts import resolve # # developer reload modules from pprint import pformat @@ -26,7 +26,7 @@ class CollectInstances(pyblish.api.ContextPlugin): data = dict() timeline_item = timeline_item_data["clip"]["item"] - # get pype tag data + # get openpype tag data tag_data = resolve.get_timeline_item_pype_tag(timeline_item) self.log.debug(f"__ tag_data: {pformat(tag_data)}") diff --git a/pype/hosts/resolve/plugins/publish/collect_workfile.py b/openpype/hosts/resolve/plugins/publish/collect_workfile.py similarity index 94% rename from pype/hosts/resolve/plugins/publish/collect_workfile.py rename to openpype/hosts/resolve/plugins/publish/collect_workfile.py index f7f90c9689..a66284ed02 100644 --- a/pype/hosts/resolve/plugins/publish/collect_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/collect_workfile.py @@ -1,11 +1,11 @@ import pyblish.api -from pype.hosts import resolve +from openpype.hosts import resolve from avalon import api as avalon from pprint import pformat # dev from importlib import reload -from pype.hosts.resolve.otio import davinci_export +from openpype.hosts.resolve.otio import davinci_export reload(davinci_export) diff --git a/pype/hosts/resolve/plugins/publish/extract_workfile.py b/openpype/hosts/resolve/plugins/publish/extract_workfile.py similarity index 93% rename from pype/hosts/resolve/plugins/publish/extract_workfile.py rename to openpype/hosts/resolve/plugins/publish/extract_workfile.py index e52e829ee4..e3d60465a2 100644 --- a/pype/hosts/resolve/plugins/publish/extract_workfile.py +++ b/openpype/hosts/resolve/plugins/publish/extract_workfile.py @@ -1,10 +1,10 @@ import os import pyblish.api -import pype.api -from pype.hosts import resolve +import openpype.api +from openpype.hosts import resolve -class ExtractWorkfile(pype.api.Extractor): +class ExtractWorkfile(openpype.api.Extractor): """ Extractor export DRP workfile file representation """ diff --git a/pype/hosts/resolve/utility_scripts/OTIO_export.py b/openpype/hosts/resolve/utility_scripts/OTIO_export.py similarity index 96% rename from pype/hosts/resolve/utility_scripts/OTIO_export.py rename to openpype/hosts/resolve/utility_scripts/OTIO_export.py index a1142f56dd..91bc2c5700 100644 --- a/pype/hosts/resolve/utility_scripts/OTIO_export.py +++ b/openpype/hosts/resolve/utility_scripts/OTIO_export.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import os -from pype.hosts.resolve.otio import davinci_export as otio_export +from openpype.hosts.resolve.otio import davinci_export as otio_export resolve = bmd.scriptapp("Resolve") # noqa fu = resolve.Fusion() diff --git a/pype/hosts/resolve/utility_scripts/OTIO_import.py b/openpype/hosts/resolve/utility_scripts/OTIO_import.py similarity index 96% rename from pype/hosts/resolve/utility_scripts/OTIO_import.py rename to openpype/hosts/resolve/utility_scripts/OTIO_import.py index 5719ec3e3c..d8b630eb2a 100644 --- a/pype/hosts/resolve/utility_scripts/OTIO_import.py +++ b/openpype/hosts/resolve/utility_scripts/OTIO_import.py @@ -1,6 +1,6 @@ #!/usr/bin/env python import os -from pype.hosts.resolve.otio import davinci_import as otio_import +from openpype.hosts.resolve.otio import davinci_import as otio_import resolve = bmd.scriptapp("Resolve") # noqa fu = resolve.Fusion() diff --git a/pype/hosts/resolve/utility_scripts/PYPE_sync_util_scripts.py b/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py similarity index 56% rename from pype/hosts/resolve/utility_scripts/PYPE_sync_util_scripts.py rename to openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py index 753bddc1da..ac66916b91 100644 --- a/pype/hosts/resolve/utility_scripts/PYPE_sync_util_scripts.py +++ b/openpype/hosts/resolve/utility_scripts/OpenPype_sync_util_scripts.py @@ -1,13 +1,13 @@ #!/usr/bin/env python import os import sys -import pype +import openpype def main(env): - import pype.hosts.resolve as bmdvr - # Registers pype's Global pyblish plugins - pype.install() + import openpype.hosts.resolve as bmdvr + # Registers openpype's Global pyblish plugins + openpype.install() bmdvr.setup(env) diff --git a/pype/hosts/resolve/utility_scripts/README.markdown b/openpype/hosts/resolve/utility_scripts/README.markdown similarity index 100% rename from pype/hosts/resolve/utility_scripts/README.markdown rename to openpype/hosts/resolve/utility_scripts/README.markdown diff --git a/pype/hosts/resolve/utility_scripts/__PYPE__MENU__.py b/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py similarity index 61% rename from pype/hosts/resolve/utility_scripts/__PYPE__MENU__.py rename to openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py index 230a7a80f0..b1037a9c93 100644 --- a/pype/hosts/resolve/utility_scripts/__PYPE__MENU__.py +++ b/openpype/hosts/resolve/utility_scripts/__OpenPype__Menu__.py @@ -1,19 +1,19 @@ import os import sys import avalon.api as avalon -import pype +import openpype -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger(__name__) def main(env): - import pype.hosts.resolve as bmdvr - # Registers pype's Global pyblish plugins - pype.install() + import openpype.hosts.resolve as bmdvr + # Registers openpype's Global pyblish plugins + openpype.install() - # activate resolve from pype + # activate resolve from openpype avalon.install(bmdvr) log.info(f"Avalon registred hosts: {avalon.registered_host()}") diff --git a/pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py b/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py similarity index 83% rename from pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py rename to openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py index f6f9454625..5430ad32df 100644 --- a/pype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py +++ b/openpype/hosts/resolve/utility_scripts/tests/test_otio_as_edl.py @@ -2,11 +2,11 @@ import os import sys import avalon.api as avalon -import pype +import openpype import opentimelineio as otio -from pype.hosts.resolve import TestGUI -import pype.hosts.resolve as bmdvr -from pype.hosts.resolve.otio import davinci_export as otio_export +from openpype.hosts.resolve import TestGUI +import openpype.hosts.resolve as bmdvr +from openpype.hosts.resolve.otio import davinci_export as otio_export class ThisTestGUI(TestGUI): @@ -14,9 +14,9 @@ class ThisTestGUI(TestGUI): def __init__(self): super(ThisTestGUI, self).__init__() - # Registers pype's Global pyblish plugins - pype.install() - # activate resolve from pype + # Registers openpype's Global pyblish plugins + openpype.install() + # activate resolve from openpype avalon.install(bmdvr) def _open_dir_button_pressed(self, event): diff --git a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py b/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py similarity index 89% rename from pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py rename to openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py index da1f980388..afa311e0b8 100644 --- a/pype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py +++ b/openpype/hosts/resolve/utility_scripts/tests/testing_create_timeline_item_from_path.py @@ -2,9 +2,9 @@ import os import sys import avalon.api as avalon -import pype -from pype.hosts.resolve import TestGUI -import pype.hosts.resolve as bmdvr +import openpype +from openpype.hosts.resolve import TestGUI +import openpype.hosts.resolve as bmdvr import clique @@ -13,9 +13,9 @@ class ThisTestGUI(TestGUI): def __init__(self): super(ThisTestGUI, self).__init__() - # Registers pype's Global pyblish plugins - pype.install() - # activate resolve from pype + # Registers openpype's Global pyblish plugins + openpype.install() + # activate resolve from openpype avalon.install(bmdvr) def _open_dir_button_pressed(self, event): diff --git a/pype/hosts/standalonepublisher/__init__.py b/openpype/hosts/standalonepublisher/__init__.py similarity index 100% rename from pype/hosts/standalonepublisher/__init__.py rename to openpype/hosts/standalonepublisher/__init__.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py similarity index 88% rename from pype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py index 545efcb303..4ca1f72cc4 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_batch_instances.py @@ -9,12 +9,11 @@ class CollectBatchInstances(pyblish.api.InstancePlugin): label = "Collect Batch Instances" order = pyblish.api.CollectorOrder + 0.489 hosts = ["standalonepublisher"] - families = ["background_batch", "render_mov_batch"] + families = ["background_batch"] # presets default_subset_task = { - "background_batch": "background", - "render_mov_batch": "compositing" + "background_batch": "background" } subsets = { "background_batch": { @@ -30,12 +29,6 @@ class CollectBatchInstances(pyblish.api.InstancePlugin): "task": "background", "family": "workfile" } - }, - "render_mov_batch": { - "renderCompositingDefault": { - "task": "compositing", - "family": "render" - } } } unchecked_by_default = [] diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py new file mode 100644 index 0000000000..a5177335b3 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_bulk_mov_instances.py @@ -0,0 +1,98 @@ +import copy +import json +import pyblish.api + +from avalon import io +from openpype.lib import get_subset_name + + +class CollectBulkMovInstances(pyblish.api.InstancePlugin): + """Collect all available instances for batch publish.""" + + label = "Collect Bulk Mov Instances" + order = pyblish.api.CollectorOrder + 0.489 + hosts = ["standalonepublisher"] + families = ["render_mov_batch"] + + new_instance_family = "render" + instance_task_names = [ + "compositing", + "comp" + ] + default_task_name = "compositing" + subset_name_variant = "Default" + + def process(self, instance): + context = instance.context + asset_name = instance.data["asset"] + + asset_doc = io.find_one( + { + "type": "asset", + "name": asset_name + }, + { + "_id": 1, + "data.tasks": 1 + } + ) + if not asset_doc: + raise AssertionError(( + "Couldn't find Asset document with name \"{}\"" + ).format(asset_name)) + + available_task_names = {} + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + for task_name in asset_tasks.keys(): + available_task_names[task_name.lower()] = task_name + + task_name = self.default_task_name + for _task_name in self.instance_task_names: + _task_name_low = _task_name.lower() + if _task_name_low in available_task_names: + task_name = available_task_names[_task_name_low] + break + + subset_name = get_subset_name( + self.new_instance_family, + self.subset_name_variant, + task_name, + asset_doc["_id"], + io.Session["AVALON_PROJECT"] + ) + instance_name = f"{asset_name}_{subset_name}" + + # create new instance + new_instance = context.create_instance(instance_name) + new_instance_data = { + "name": instance_name, + "label": instance_name, + "family": self.new_instance_family, + "subset": subset_name, + "task": task_name + } + new_instance.data.update(new_instance_data) + # add original instance data except name key + for key, value in instance.data.items(): + if key in new_instance_data: + continue + # Make sure value is copy since value may be object which + # can be shared across all new created objects + new_instance.data[key] = copy.deepcopy(value) + + # Add `render_mov_batch` for specific validators + if "families" not in new_instance.data: + new_instance.data["families"] = [] + new_instance.data["families"].append("render_mov_batch") + + # delete original instance + context.remove(instance) + + self.log.info(f"Created new instance: {instance_name}") + + def convertor(value): + return str(value) + + self.log.debug("Instance data: {}".format( + json.dumps(new_instance.data, indent=4, default=convertor) + )) diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_clear_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_clear_instances.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_clear_instances.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_clear_instances.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_context.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_context.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_context.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_context.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_editorial.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py similarity index 99% rename from pype/hosts/standalonepublisher/plugins/publish/collect_editorial.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py index afbdd87b2d..fc9d95d3d7 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/collect_editorial.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_editorial.py @@ -17,7 +17,7 @@ Optional: import os import opentimelineio as otio import pyblish.api -from pype import lib as plib +from openpype import lib as plib class OTIO_View(pyblish.api.Action): diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_scenes.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_harmony_zips.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_hierarchy.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_instance_data.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_instance_data.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_instance_data.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_instance_data.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_instance_resources.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_instances.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py similarity index 99% rename from pype/hosts/standalonepublisher/plugins/publish/collect_instances.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py index 090ffe2cbb..eb04217136 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/collect_instances.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_instances.py @@ -1,7 +1,7 @@ import os import opentimelineio as otio import pyblish.api -from pype import lib as plib +from openpype import lib as plib class CollectInstances(pyblish.api.InstancePlugin): diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_matching_asset.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_matchmove.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_matchmove.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_matchmove.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_matchmove.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_remove_marked.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_remove_marked.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_remove_marked.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_remove_marked.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/collect_representation_names.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_representation_names.py similarity index 100% rename from pype/hosts/standalonepublisher/plugins/publish/collect_representation_names.py rename to openpype/hosts/standalonepublisher/plugins/publish/collect_representation_names.py diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py similarity index 99% rename from pype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py index db64a408e9..f07499c15d 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_for_compositing.py @@ -1,13 +1,13 @@ import os import json import copy -import pype.api +import openpype.api from avalon import io PSDImage = None -class ExtractBGForComp(pype.api.Extractor): +class ExtractBGForComp(openpype.api.Extractor): label = "Extract Background for Compositing" families = ["backgroundComp"] hosts = ["standalonepublisher"] diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py similarity index 99% rename from pype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py index e18baf378e..2c92366ae9 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_bg_main_groups.py @@ -1,14 +1,14 @@ import os import copy import json -import pype.api +import openpype.api import pyblish.api from avalon import io PSDImage = None -class ExtractBGMainGroups(pype.api.Extractor): +class ExtractBGMainGroups(openpype.api.Extractor): label = "Extract Background Layout" order = pyblish.api.ExtractorOrder + 0.02 families = ["backgroundLayout"] diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py similarity index 98% rename from pype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py index 16811e5707..f7f96c7d03 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_harmony_zip.py @@ -10,10 +10,10 @@ import zipfile import pyblish.api from avalon import api, io -import pype.api +import openpype.api -class ExtractHarmonyZip(pype.api.Extractor): +class ExtractHarmonyZip(openpype.api.Extractor): """Extract Harmony zip.""" # Pyblish settings @@ -216,7 +216,7 @@ class ExtractHarmonyZip(pype.api.Extractor): "scene.xstage") # Setup the data needed to form a valid work path filename - anatomy = pype.api.Anatomy() + anatomy = openpype.api.Anatomy() project_entity = instance.context.data["projectEntity"] data = { @@ -226,7 +226,7 @@ class ExtractHarmonyZip(pype.api.Extractor): "code": project_entity["data"].get("code", '') }, "asset": instance.data["asset"], - "hierarchy": pype.api.get_hierarchy(instance.data["asset"]), + "hierarchy": openpype.api.get_hierarchy(instance.data["asset"]), "family": instance.data["family"], "task": instance.data.get("task"), "subset": instance.data["subset"], diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py similarity index 98% rename from pype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py index 3cffaf17d9..e3094b2e3f 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_images_from_psd.py @@ -1,13 +1,13 @@ import os import copy -import pype.api +import openpype.api import pyblish.api from avalon import io PSDImage = None -class ExtractImagesFromPSD(pype.api.Extractor): +class ExtractImagesFromPSD(openpype.api.Extractor): # PLUGIN is not currently enabled because was decided to use different # approach enabled = False diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py similarity index 97% rename from pype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py index 533421e46d..ac1dfa13d4 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_thumbnail.py @@ -2,8 +2,8 @@ import os import tempfile import subprocess import pyblish.api -import pype.api -import pype.lib +import openpype.api +import openpype.lib class ExtractThumbnailSP(pyblish.api.InstancePlugin): @@ -73,7 +73,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin): full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] self.log.info("output {}".format(full_thumbnail_path)) - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} diff --git a/pype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py similarity index 94% rename from pype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py rename to openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py index 1c53ae5f46..eb613fa951 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/extract_trim_video_audio.py @@ -1,11 +1,11 @@ import os import pyblish.api -import pype.api +import openpype.api from pprint import pformat -class ExtractTrimVideoAudio(pype.api.Extractor): +class ExtractTrimVideoAudio(openpype.api.Extractor): """Trim with ffmpeg "mov" and "wav" files.""" # must be before `ExtractThumbnailSP` @@ -27,7 +27,7 @@ class ExtractTrimVideoAudio(pype.api.Extractor): instance.data["representations"] = list() # get ffmpet path - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") # get staging dir staging_dir = self.staging_dir(instance) @@ -80,7 +80,7 @@ class ExtractTrimVideoAudio(pype.api.Extractor): self.log.info(f"Processing: {args}") ffmpeg_args = " ".join(args) - pype.api.run_subprocess( + openpype.api.run_subprocess( ffmpeg_args, shell=True, logger=self.log ) diff --git a/pype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py similarity index 91% rename from pype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py rename to openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py index 0dfca92f66..6759b87ceb 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_editorial_resources.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateEditorialResources(pyblish.api.InstancePlugin): @@ -12,7 +12,7 @@ class ValidateEditorialResources(pyblish.api.InstancePlugin): # make sure it is enabled only if at least both families are available match = pyblish.api.Subset - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder def process(self, instance): self.log.debug( diff --git a/pype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py similarity index 90% rename from pype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py rename to openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py index 04d2f3ea6c..85ec9379ce 100644 --- a/pype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_shot_duplicates.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateShotDuplicates(pyblish.api.ContextPlugin): @@ -7,7 +7,7 @@ class ValidateShotDuplicates(pyblish.api.ContextPlugin): label = "Validate Shot Duplicates" hosts = ["standalonepublisher"] - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder def process(self, context): shot_names = [] diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py new file mode 100644 index 0000000000..e3b2ae1646 --- /dev/null +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_task_existence.py @@ -0,0 +1,56 @@ +import pyblish.api +from avalon import io + + +class ValidateTaskExistence(pyblish.api.ContextPlugin): + """Validating tasks on instances are filled and existing.""" + + label = "Validate Task Existence" + order = pyblish.api.ValidatorOrder + + hosts = ["standalonepublisher"] + families = ["render_mov_batch"] + + def process(self, context): + asset_names = set() + for instance in context: + asset_names.add(instance.data["asset"]) + + asset_docs = io.find( + { + "type": "asset", + "name": {"$in": list(asset_names)} + }, + { + "name": 1, + "data.tasks": 1 + } + ) + tasks_by_asset_names = {} + for asset_doc in asset_docs: + asset_name = asset_doc["name"] + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + tasks_by_asset_names[asset_name] = list(asset_tasks.keys()) + + missing_tasks = [] + for instance in context: + asset_name = instance.data["asset"] + task_name = instance.data["task"] + task_names = tasks_by_asset_names.get(asset_name) or [] + if task_name and task_name in task_names: + continue + missing_tasks.append((asset_name, task_name)) + + # Everything is OK + if not missing_tasks: + return + + # Raise an exception + msg = "Couldn't find task name/s required for publishing.\n{}" + pair_msgs = [] + for missing_pair in missing_tasks: + pair_msgs.append( + "Asset: \"{}\" Task: \"{}\"".format(*missing_pair) + ) + + raise AssertionError(msg.format("\n".join(pair_msgs))) diff --git a/pype/hosts/tvpaint/__init__.py b/openpype/hosts/tvpaint/__init__.py similarity index 100% rename from pype/hosts/tvpaint/__init__.py rename to openpype/hosts/tvpaint/__init__.py diff --git a/pype/hosts/tvpaint/api/__init__.py b/openpype/hosts/tvpaint/api/__init__.py similarity index 89% rename from pype/hosts/tvpaint/api/__init__.py rename to openpype/hosts/tvpaint/api/__init__.py index 08484895c9..068559049b 100644 --- a/pype/hosts/tvpaint/api/__init__.py +++ b/openpype/hosts/tvpaint/api/__init__.py @@ -6,9 +6,9 @@ from avalon.tvpaint import pipeline import avalon.api import pyblish.api -from pype.hosts import tvpaint +from openpype.hosts import tvpaint -log = logging.getLogger("pype.hosts.tvpaint") +log = logging.getLogger("openpype.hosts.tvpaint") HOST_DIR = os.path.dirname(os.path.abspath(tvpaint.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") @@ -35,7 +35,7 @@ def on_instance_toggle(instance, old_value, new_value): def install(): - log.info("Pype - Installing TVPaint integration") + log.info("OpenPype - Installing TVPaint integration") localization_file = os.path.join(HOST_DIR, "resources", "avalon.loc") register_localization_file(localization_file) @@ -51,7 +51,7 @@ def install(): def uninstall(): - log.info("Pype - Uninstalling TVPaint integration") + log.info("OpenPype - Uninstalling TVPaint integration") pyblish.api.deregister_plugin_path(PUBLISH_PATH) avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH) diff --git a/pype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py similarity index 100% rename from pype/hosts/tvpaint/api/lib.py rename to openpype/hosts/tvpaint/api/lib.py diff --git a/pype/hosts/tvpaint/api/plugin.py b/openpype/hosts/tvpaint/api/plugin.py similarity index 70% rename from pype/hosts/tvpaint/api/plugin.py rename to openpype/hosts/tvpaint/api/plugin.py index 6f069586a5..3eb9a5be31 100644 --- a/pype/hosts/tvpaint/api/plugin.py +++ b/openpype/hosts/tvpaint/api/plugin.py @@ -1,4 +1,4 @@ -from pype.api import PypeCreatorMixin +from openpype.api import PypeCreatorMixin from avalon.tvpaint import pipeline diff --git a/pype/hosts/tvpaint/hooks/pre_launch_args.py b/openpype/hosts/tvpaint/hooks/pre_launch_args.py similarity index 98% rename from pype/hosts/tvpaint/hooks/pre_launch_args.py rename to openpype/hosts/tvpaint/hooks/pre_launch_args.py index 2bba9c950f..04cca5d789 100644 --- a/pype/hosts/tvpaint/hooks/pre_launch_args.py +++ b/openpype/hosts/tvpaint/hooks/pre_launch_args.py @@ -1,8 +1,8 @@ import os import shutil -from pype.hosts import tvpaint -from pype.lib import ( +from openpype.hosts import tvpaint +from openpype.lib import ( PreLaunchHook, get_pype_execute_args ) diff --git a/pype/hosts/tvpaint/plugins/create/create_render_layer.py b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py similarity index 99% rename from pype/hosts/tvpaint/plugins/create/create_render_layer.py rename to openpype/hosts/tvpaint/plugins/create/create_render_layer.py index ed7c96c904..585f0c87d7 100644 --- a/pype/hosts/tvpaint/plugins/create/create_render_layer.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_layer.py @@ -1,5 +1,5 @@ from avalon.tvpaint import pipeline, lib -from pype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api import plugin class CreateRenderlayer(plugin.Creator): diff --git a/pype/hosts/tvpaint/plugins/create/create_render_pass.py b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py similarity index 98% rename from pype/hosts/tvpaint/plugins/create/create_render_pass.py rename to openpype/hosts/tvpaint/plugins/create/create_render_pass.py index 8583f20451..09c68930f2 100644 --- a/pype/hosts/tvpaint/plugins/create/create_render_pass.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render_pass.py @@ -1,5 +1,5 @@ from avalon.tvpaint import pipeline, lib -from pype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api import plugin class CreateRenderPass(plugin.Creator): diff --git a/pype/hosts/tvpaint/plugins/create/create_review.py b/openpype/hosts/tvpaint/plugins/create/create_review.py similarity index 91% rename from pype/hosts/tvpaint/plugins/create/create_review.py rename to openpype/hosts/tvpaint/plugins/create/create_review.py index cfc49a8ac6..88b039c8e4 100644 --- a/pype/hosts/tvpaint/plugins/create/create_review.py +++ b/openpype/hosts/tvpaint/plugins/create/create_review.py @@ -1,5 +1,5 @@ from avalon.tvpaint import pipeline -from pype.hosts.tvpaint.api import plugin +from openpype.hosts.tvpaint.api import plugin class CreateReview(plugin.Creator): diff --git a/pype/hosts/tvpaint/plugins/load/load_image.py b/openpype/hosts/tvpaint/plugins/load/load_image.py similarity index 100% rename from pype/hosts/tvpaint/plugins/load/load_image.py rename to openpype/hosts/tvpaint/plugins/load/load_image.py diff --git a/pype/hosts/tvpaint/plugins/load/load_reference_image.py b/openpype/hosts/tvpaint/plugins/load/load_reference_image.py similarity index 100% rename from pype/hosts/tvpaint/plugins/load/load_reference_image.py rename to openpype/hosts/tvpaint/plugins/load/load_reference_image.py diff --git a/pype/hosts/tvpaint/plugins/load/load_sound.py b/openpype/hosts/tvpaint/plugins/load/load_sound.py similarity index 100% rename from pype/hosts/tvpaint/plugins/load/load_sound.py rename to openpype/hosts/tvpaint/plugins/load/load_sound.py diff --git a/pype/hosts/tvpaint/plugins/publish/collect_instances.py b/openpype/hosts/tvpaint/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/collect_instances.py rename to openpype/hosts/tvpaint/plugins/publish/collect_instances.py diff --git a/pype/hosts/tvpaint/plugins/publish/collect_workfile_data.py b/openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/collect_workfile_data.py rename to openpype/hosts/tvpaint/plugins/publish/collect_workfile_data.py diff --git a/pype/hosts/tvpaint/plugins/publish/extract_sequence.py b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py similarity index 99% rename from pype/hosts/tvpaint/plugins/publish/extract_sequence.py rename to openpype/hosts/tvpaint/plugins/publish/extract_sequence.py index 41117851b7..0d125a1a50 100644 --- a/pype/hosts/tvpaint/plugins/publish/extract_sequence.py +++ b/openpype/hosts/tvpaint/plugins/publish/extract_sequence.py @@ -6,7 +6,7 @@ import multiprocessing import pyblish.api from avalon.tvpaint import lib -from pype.hosts.tvpaint.api.lib import composite_images +from openpype.hosts.tvpaint.api.lib import composite_images from PIL import Image, ImageDraw diff --git a/pype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py b/openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py rename to openpype/hosts/tvpaint/plugins/publish/validate_duplicated_layer_names.py diff --git a/pype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py b/openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py rename to openpype/hosts/tvpaint/plugins/publish/validate_layers_visibility.py diff --git a/pype/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py b/openpype/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py rename to openpype/hosts/tvpaint/plugins/publish/validate_missing_layer_names.py diff --git a/pype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py b/openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py rename to openpype/hosts/tvpaint/plugins/publish/validate_render_pass_group.py diff --git a/pype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py b/openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py similarity index 100% rename from pype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py rename to openpype/hosts/tvpaint/plugins/publish/validate_workfile_project_name.py diff --git a/pype/hosts/tvpaint/resources/avalon.loc b/openpype/hosts/tvpaint/resources/avalon.loc similarity index 97% rename from pype/hosts/tvpaint/resources/avalon.loc rename to openpype/hosts/tvpaint/resources/avalon.loc index cda27162e6..3cfb7e9db4 100644 --- a/pype/hosts/tvpaint/resources/avalon.loc +++ b/openpype/hosts/tvpaint/resources/avalon.loc @@ -10,7 +10,7 @@ #------------ COMMON ----------------------------- #------------------------------------------------- -$100 "Pype Tools" +$100 "OpenPype Tools" $10010 "Workfiles" $10020 "Load" diff --git a/pype/hosts/tvpaint/resources/template.tvpp b/openpype/hosts/tvpaint/resources/template.tvpp similarity index 100% rename from pype/hosts/tvpaint/resources/template.tvpp rename to openpype/hosts/tvpaint/resources/template.tvpp diff --git a/pype/hosts/unreal/api/__init__.py b/openpype/hosts/unreal/api/__init__.py similarity index 81% rename from pype/hosts/unreal/api/__init__.py rename to openpype/hosts/unreal/api/__init__.py index 15479ba952..38469e0ddb 100644 --- a/pype/hosts/unreal/api/__init__.py +++ b/openpype/hosts/unreal/api/__init__.py @@ -3,11 +3,11 @@ import logging from avalon import api as avalon from pyblish import api as pyblish -import pype.hosts.unreal +import openpype.hosts.unreal -logger = logging.getLogger("pype.hosts.unreal") +logger = logging.getLogger("openpype.hosts.unreal") -HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.unreal.__file__)) +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.unreal.__file__)) PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "load") @@ -30,9 +30,9 @@ def install(): . ''' print(logo) - print("installing Pype for Unreal ...") + print("installing OpenPype for Unreal ...") print("-=" * 40) - logger.info("installing Pype for Unreal") + logger.info("installing OpenPype for Unreal") pyblish.register_plugin_path(str(PUBLISH_PATH)) avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH)) avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH)) diff --git a/pype/hosts/unreal/api/lib.py b/openpype/hosts/unreal/api/lib.py similarity index 97% rename from pype/hosts/unreal/api/lib.py rename to openpype/hosts/unreal/api/lib.py index 02b1ae5bf5..7e706a2789 100644 --- a/pype/hosts/unreal/api/lib.py +++ b/openpype/hosts/unreal/api/lib.py @@ -4,7 +4,7 @@ import platform import json from distutils import dir_util import subprocess -from pype.api import get_project_settings +from openpype.api import get_project_settings def get_engine_versions(): @@ -146,7 +146,7 @@ def create_unreal_project(project_name: str, directory is not found in plugin folders as this indicates this is only source distribution of the plugin. Dev mode is also set by preset file `unreal/project_setup.json` in - **PYPE_CONFIG**. + **OPENPYPE_CONFIG**. :type dev_mode: bool :returns: None """ @@ -180,17 +180,17 @@ def create_unreal_project(project_name: str, } if preset["install_unreal_python_engine"]: - # If `PYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there to - # support offline installation. + # If `OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN` is set, copy it from there + # to support offline installation. # Otherwise clone UnrealEnginePython to Plugins directory # https://github.com/20tab/UnrealEnginePython.git uep_path = os.path.join(plugins_path, "UnrealEnginePython") - if os.environ.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): + if os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"): os.makedirs(uep_path, exist_ok=True) dir_util._path_created = {} dir_util.copy_tree( - os.environ.get("PYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), + os.environ.get("OPENPYPE_UNREAL_ENGINE_PYTHON_PLUGIN"), uep_path) else: # WARNING: this will trigger dev_mode, because we need to compile diff --git a/openpype/hosts/unreal/api/plugin.py b/openpype/hosts/unreal/api/plugin.py new file mode 100644 index 0000000000..a69b12d6f5 --- /dev/null +++ b/openpype/hosts/unreal/api/plugin.py @@ -0,0 +1,12 @@ +from avalon import api +import openpype.api + + +class Creator(openpype.api.Creator): + """This serves as skeleton for future OpenPype specific functionality""" + pass + + +class Loader(api.Loader): + """This serves as skeleton for future OpenPype specific functionality""" + pass diff --git a/pype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py similarity index 97% rename from pype/hosts/unreal/hooks/pre_workfile_preparation.py rename to openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 942cb3bdab..5945d0486b 100644 --- a/pype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -1,10 +1,10 @@ import os -from pype.lib import ( +from openpype.lib import ( PreLaunchHook, ApplicationLaunchFailed ) -from pype.hosts.unreal.api import lib as unreal_lib +from openpype.hosts.unreal.api import lib as unreal_lib class UnrealPrelaunchHook(PreLaunchHook): diff --git a/pype/hosts/unreal/plugins/__init__.py b/openpype/hosts/unreal/plugins/__init__.py similarity index 100% rename from pype/hosts/unreal/plugins/__init__.py rename to openpype/hosts/unreal/plugins/__init__.py diff --git a/pype/hosts/unreal/plugins/create/create_layout.py b/openpype/hosts/unreal/plugins/create/create_layout.py similarity index 95% rename from pype/hosts/unreal/plugins/create/create_layout.py rename to openpype/hosts/unreal/plugins/create/create_layout.py index e810357c8a..239b72787b 100644 --- a/pype/hosts/unreal/plugins/create/create_layout.py +++ b/openpype/hosts/unreal/plugins/create/create_layout.py @@ -1,5 +1,5 @@ from unreal import EditorLevelLibrary as ell -from pype.hosts.unreal.api.plugin import Creator +from openpype.hosts.unreal.api.plugin import Creator from avalon.unreal import ( instantiate, ) diff --git a/pype/hosts/unreal/plugins/create/create_staticmeshfbx.py b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py similarity index 94% rename from pype/hosts/unreal/plugins/create/create_staticmeshfbx.py rename to openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py index 8f414ac56d..4cc67e0f1f 100644 --- a/pype/hosts/unreal/plugins/create/create_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/create/create_staticmeshfbx.py @@ -1,5 +1,5 @@ import unreal -from pype.hosts.unreal.api.plugin import Creator +from openpype.hosts.unreal.api.plugin import Creator from avalon.unreal import ( instantiate, ) diff --git a/pype/hosts/unreal/plugins/load/load_animation.py b/openpype/hosts/unreal/plugins/load/load_animation.py similarity index 99% rename from pype/hosts/unreal/plugins/load/load_animation.py rename to openpype/hosts/unreal/plugins/load/load_animation.py index 5e106788ce..18910983ea 100644 --- a/pype/hosts/unreal/plugins/load/load_animation.py +++ b/openpype/hosts/unreal/plugins/load/load_animation.py @@ -99,7 +99,7 @@ class AnimationFBXLoader(api.Loader): container=container_name, path=asset_dir) data = { - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": pipeline.AVALON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, diff --git a/pype/hosts/unreal/plugins/load/load_rig.py b/openpype/hosts/unreal/plugins/load/load_rig.py similarity index 99% rename from pype/hosts/unreal/plugins/load/load_rig.py rename to openpype/hosts/unreal/plugins/load/load_rig.py index 56351e388b..7f6e31618a 100644 --- a/pype/hosts/unreal/plugins/load/load_rig.py +++ b/openpype/hosts/unreal/plugins/load/load_rig.py @@ -96,7 +96,7 @@ class SkeletalMeshFBXLoader(api.Loader): container=container_name, path=asset_dir) data = { - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": pipeline.AVALON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, diff --git a/pype/hosts/unreal/plugins/load/load_setdress.py b/openpype/hosts/unreal/plugins/load/load_setdress.py similarity index 98% rename from pype/hosts/unreal/plugins/load/load_setdress.py rename to openpype/hosts/unreal/plugins/load/load_setdress.py index 08330e349b..da302deb1c 100644 --- a/pype/hosts/unreal/plugins/load/load_setdress.py +++ b/openpype/hosts/unreal/plugins/load/load_setdress.py @@ -67,7 +67,7 @@ class AnimationCollectionLoader(api.Loader): container=container_name, path=asset_dir) data = { - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": pipeline.AVALON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, diff --git a/pype/hosts/unreal/plugins/load/load_staticmeshfbx.py b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py similarity index 99% rename from pype/hosts/unreal/plugins/load/load_staticmeshfbx.py rename to openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py index 149bafcacc..dbea1d5951 100644 --- a/pype/hosts/unreal/plugins/load/load_staticmeshfbx.py +++ b/openpype/hosts/unreal/plugins/load/load_staticmeshfbx.py @@ -79,7 +79,7 @@ class StaticMeshFBXLoader(api.Loader): container=container_name, path=asset_dir) data = { - "schema": "avalon-core:container-2.0", + "schema": "openpype:container-2.0", "id": pipeline.AVALON_CONTAINER_ID, "asset": asset, "namespace": asset_dir, diff --git a/pype/hosts/unreal/plugins/publish/collect_current_file.py b/openpype/hosts/unreal/plugins/publish/collect_current_file.py similarity index 100% rename from pype/hosts/unreal/plugins/publish/collect_current_file.py rename to openpype/hosts/unreal/plugins/publish/collect_current_file.py diff --git a/pype/hosts/unreal/plugins/publish/collect_instances.py b/openpype/hosts/unreal/plugins/publish/collect_instances.py similarity index 100% rename from pype/hosts/unreal/plugins/publish/collect_instances.py rename to openpype/hosts/unreal/plugins/publish/collect_instances.py diff --git a/pype/hosts/unreal/plugins/publish/extract_layout.py b/openpype/hosts/unreal/plugins/publish/extract_layout.py similarity index 98% rename from pype/hosts/unreal/plugins/publish/extract_layout.py rename to openpype/hosts/unreal/plugins/publish/extract_layout.py index 6345b8da51..5924221f51 100644 --- a/pype/hosts/unreal/plugins/publish/extract_layout.py +++ b/openpype/hosts/unreal/plugins/publish/extract_layout.py @@ -6,11 +6,11 @@ import unreal from unreal import EditorLevelLibrary as ell from unreal import EditorAssetLibrary as eal -import pype.api +import openpype.api from avalon import io -class ExtractLayout(pype.api.Extractor): +class ExtractLayout(openpype.api.Extractor): """Extract a layout.""" label = "Extract Layout" diff --git a/pype/launcher_actions.py b/openpype/launcher_actions.py similarity index 100% rename from pype/launcher_actions.py rename to openpype/launcher_actions.py diff --git a/pype/lib/__init__.py b/openpype/lib/__init__.py similarity index 94% rename from pype/lib/__init__.py rename to openpype/lib/__init__.py index 2150e53b0e..554c0d8ec3 100644 --- a/pype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -9,7 +9,7 @@ import site # add Python version specific vendor folder site.addsitedir( os.path.join( - os.getenv("PYPE_ROOT", ""), + os.getenv("OPENPYPE_ROOT", ""), "vendor", "python", "python_{}".format(sys.version[0]))) from .terminal import Terminal @@ -24,7 +24,7 @@ from .mongo import ( compose_url, get_default_components, validate_mongo_connection, - PypeMongoConnection + OpenPypeMongoConnection ) from .anatomy import ( merge_dict, @@ -89,6 +89,8 @@ from .applications import ( from .profiles_filtering import filter_profiles from .plugin_tools import ( + TaskNotSetError, + get_subset_name, filter_pyblish_plugins, source_hash, get_unique_layer_name, @@ -104,7 +106,7 @@ from .local_settings import ( JSONSettingRegistry, PypeSettingsRegistry, get_local_site_id, - change_pype_mongo_url + change_openpype_mongo_url ) from .path_tools import ( @@ -181,6 +183,8 @@ __all__ = [ "filter_profiles", + "TaskNotSetError", + "get_subset_name", "filter_pyblish_plugins", "source_hash", "get_unique_layer_name", @@ -209,13 +213,13 @@ __all__ = [ "compose_url", "get_default_components", "validate_mongo_connection", - "PypeMongoConnection", + "OpenPypeMongoConnection", "IniSettingRegistry", "JSONSettingRegistry", "PypeSettingsRegistry", "get_local_site_id", - "change_pype_mongo_url", + "change_openpype_mongo_url", "timeit", diff --git a/pype/lib/abstract_collect_render.py b/openpype/lib/abstract_collect_render.py similarity index 100% rename from pype/lib/abstract_collect_render.py rename to openpype/lib/abstract_collect_render.py diff --git a/pype/lib/abstract_expected_files.py b/openpype/lib/abstract_expected_files.py similarity index 100% rename from pype/lib/abstract_expected_files.py rename to openpype/lib/abstract_expected_files.py diff --git a/pype/lib/abstract_metaplugins.py b/openpype/lib/abstract_metaplugins.py similarity index 100% rename from pype/lib/abstract_metaplugins.py rename to openpype/lib/abstract_metaplugins.py diff --git a/pype/lib/abstract_submit_deadline.py b/openpype/lib/abstract_submit_deadline.py similarity index 99% rename from pype/lib/abstract_submit_deadline.py rename to openpype/lib/abstract_submit_deadline.py index 9862f534f1..12014ddfb5 100644 --- a/pype/lib/abstract_submit_deadline.py +++ b/openpype/lib/abstract_submit_deadline.py @@ -607,7 +607,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.post(*args, **kwargs) @@ -626,7 +626,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin): """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.get(*args, **kwargs) diff --git a/pype/lib/anatomy.py b/openpype/lib/anatomy.py similarity index 98% rename from pype/lib/anatomy.py rename to openpype/lib/anatomy.py index 4e7643dbbb..ff15aec41a 100644 --- a/pype/lib/anatomy.py +++ b/openpype/lib/anatomy.py @@ -5,7 +5,7 @@ import platform import collections import numbers -from pype.settings.lib import ( +from openpype.settings.lib import ( get_default_anatomy_settings, get_anatomy_settings ) @@ -156,7 +156,7 @@ class Anatomy: return self._roots_obj def root_environments(self): - """Return PYPE_ROOT_* environments for current project in dict.""" + """Return OPENPYPE_ROOT_* environments for current project in dict.""" return self._roots_obj.root_environments() def root_environmets_fill_data(self, template=None): @@ -181,7 +181,7 @@ class Anatomy: return self.roots_obj.all_root_paths() def set_root_environments(self): - """Set PYPE_ROOT_* environments for current project.""" + """Set OPENPYPE_ROOT_* environments for current project.""" self._roots_obj.set_root_environments() def root_names(self): @@ -320,7 +320,7 @@ class Anatomy: "<{}>" ## Output - "/project/asset/task/animation_v001.ma" + "/project/asset/task/animation_v001.ma" Args: filepath (str): Full file path where root should be replaced. @@ -1359,7 +1359,7 @@ class Roots: anatomy Anatomy: Anatomy object created for a specific project. """ - env_prefix = "PYPE_PROJECT_ROOT" + env_prefix = "OPENPYPE_PROJECT_ROOT" roots_filename = "roots.json" def __init__(self, anatomy): @@ -1465,7 +1465,8 @@ class Roots: def root_environments(self): """Use root keys to create unique keys for environment variables. - Concatenates prefix "PYPE_ROOT" with root keys to create unique keys. + Concatenates prefix "OPENPYPE_ROOT" with root keys to create unique + keys. Returns: dict: Result is `{(str): (str)}` dicitonary where key represents @@ -1487,13 +1488,13 @@ class Roots: Result on windows platform:: { - "PYPE_ROOT_WORK": "P:/projects/work", - "PYPE_ROOT_PUBLISH": "P:/projects/publish" + "OPENPYPE_ROOT_WORK": "P:/projects/work", + "OPENPYPE_ROOT_PUBLISH": "P:/projects/publish" } Short example when multiroot is not used:: { - "PYPE_ROOT": "P:/projects" + "OPENPYPE_ROOT": "P:/projects" } """ return self._root_environments() diff --git a/pype/lib/applications.py b/openpype/lib/applications.py similarity index 81% rename from pype/lib/applications.py rename to openpype/lib/applications.py index 4b2cab99aa..e043c9d05c 100644 --- a/pype/lib/applications.py +++ b/openpype/lib/applications.py @@ -4,6 +4,7 @@ import copy import json import platform import getpass +import collections import inspect import subprocess import distutils.spawn @@ -11,7 +12,7 @@ from abc import ABCMeta, abstractmethod import six -from pype.settings import ( +from openpype.settings import ( get_system_settings, get_project_settings, get_environments @@ -71,7 +72,7 @@ class ApplictionExecutableNotFound(Exception): for executable in application.executables: details += "\n- " + executable.executable_path - self.msg = msg.format(application.full_label, application.app_name) + self.msg = msg.format(application.full_label, application.name) self.details = details exc_mgs = str(self.msg) @@ -90,63 +91,199 @@ class ApplicationLaunchFailed(Exception): pass +class ApplicationGroup: + """Hold information about application group. + + Application group wraps different versions(variants) of application. + e.g. "maya" is group and "maya_2020" is variant. + + Group hold `host_name` which is implementation name used in pype. Also + holds `enabled` if whole app group is enabled or `icon` for application + icon path in resources. + + Group has also `environment` which hold same environments for all variants. + + Args: + name (str): Groups' name. + data (dict): Group defying data loaded from settings. + manager (ApplicationManager): Manager that created the group. + """ + def __init__(self, name, data, manager): + self.name = name + self.manager = manager + self._data = data + + self.enabled = data.get("enabled", True) + self.label = data.get("label") or None + self.icon = data.get("icon") or None + self._environment = data.get("environment") or {} + + host_name = data.get("host_name", None) + self.is_host = host_name is not None + self.host_name = host_name + + variants = data.get("variants") or {} + for variant_name, variant_data in variants.items(): + variants[variant_name] = Application( + variant_name, variant_data, self + ) + + self.variants = variants + + def __repr__(self): + return "<{}> - {}".format(self.__class__.__name__, self.name) + + def __iter__(self): + for variant in self.variants.values(): + yield variant + + @property + def environment(self): + return copy.deepcopy(self._environment) + + +class Application: + """Hold information about application. + + Object by itself does nothing special. + + Args: + name (str): Specific version (or variant) of application. + e.g. "maya2020", "nuke11.3", etc. + data (dict): Data for the version containing information about + executables, variant label or if is enabled. + Only required key is `executables`. + group (ApplicationGroup): App group object that created the applicaiton + and under which application belongs. + """ + + def __init__(self, name, data, group): + self.name = name + self.group = group + self._data = data + + enabled = False + if group.enabled: + enabled = data.get("enabled", True) + self.enabled = enabled + + self.label = data.get("variant_label") or name + self.full_name = "/".join((group.name, name)) + + if group.label: + full_label = " ".join((group.label, self.label)) + else: + full_label = self.label + self.full_label = full_label + self._environment = data.get("environment") or {} + + _executables = data["executables"] + if not _executables: + _executables = [] + + elif isinstance(_executables, dict): + _executables = _executables.get(platform.system().lower()) or [] + + _arguments = data["arguments"] + if not _arguments: + _arguments = [] + + elif isinstance(_arguments, dict): + _arguments = _arguments.get(platform.system().lower()) or [] + + executables = [] + for executable in _executables: + executables.append(ApplicationExecutable(executable)) + + self.executables = executables + self.arguments = _arguments + + def __repr__(self): + return "<{}> - {}".format(self.__class__.__name__, self.full_name) + + @property + def environment(self): + return copy.deepcopy(self._environment) + + @property + def manager(self): + return self.group.manager + + @property + def host_name(self): + return self.group.host_name + + @property + def icon(self): + return self.group.icon + + @property + def is_host(self): + return self.group.is_host + + def find_executable(self): + """Try to find existing executable for application. + + Returns (str): Path to executable from `executables` or None if any + exists. + """ + for executable in self.executables: + if executable.exists(): + return executable + return None + + def launch(self, *args, **kwargs): + """Launch the application. + + For this purpose is used manager's launch method to keep logic at one + place. + + Arguments must match with manager's launch method. That's why *args + **kwargs are used. + + Returns: + subprocess.Popen: Return executed process as Popen object. + """ + return self.manager.launch(self.name, *args, **kwargs) + + class ApplicationManager: def __init__(self): self.log = PypeLogger().get_logger(self.__class__.__name__) + self.app_groups = {} self.applications = {} + self.tool_groups = {} self.tools = {} self.refresh() def refresh(self): """Refresh applications from settings.""" + self.app_groups.clear() self.applications.clear() + self.tool_groups.clear() self.tools.clear() settings = get_system_settings() - hosts_definitions = settings["applications"] - for app_group, variant_definitions in hosts_definitions.items(): - enabled = variant_definitions["enabled"] - label = variant_definitions.get("label") or app_group - variants = variant_definitions.get("variants") or {} - icon = variant_definitions.get("icon") - group_host_name = variant_definitions.get("host_name") or None - for app_name, app_data in variants.items(): - if app_name in self.applications: - raise AssertionError(( - "BUG: Duplicated application name in settings \"{}\"" - ).format(app_name)) - - # If host is disabled then disable all variants - if not enabled: - app_data["enabled"] = enabled - - # Pass label from host definition - if not app_data.get("label"): - app_data["label"] = label - - if not app_data.get("icon"): - app_data["icon"] = icon - - host_name = app_data.get("host_name") or group_host_name - - app_data["is_host"] = host_name is not None - - self.applications[app_name] = Application( - app_group, app_name, host_name, app_data, self - ) + app_defs = settings["applications"] + for group_name, variant_defs in app_defs.items(): + group = ApplicationGroup(group_name, variant_defs, self) + self.app_groups[group_name] = group + for app in group: + # TODO This should be replaced with `full_name` in future + self.applications[app.name] = app tools_definitions = settings["tools"]["tool_groups"] for tool_group_name, tool_group_data in tools_definitions.items(): - tool_variants = tool_group_data.get("variants") or {} - for tool_name, tool_data in tool_variants.items(): - tool = ApplicationTool(tool_name, tool_group_name) - if tool.full_name in self.tools: - self.log.warning(( - "Duplicated tool name in settings \"{}\"" - ).format(tool.full_name)) + if not tool_group_name: + continue + group = EnvironmentToolGroup( + tool_group_name, tool_group_data, self + ) + self.tool_groups[tool_group_name] = group + for tool in group: self.tools[tool.full_name] = tool def launch(self, app_name, **data): @@ -184,23 +321,69 @@ class ApplicationManager: return context.launch() -class ApplicationTool: +class EnvironmentToolGroup: + """Hold information about environment tool group. + + Environment tool group may hold different variants of same tool and set + environments that are same for all of them. + + e.g. "mtoa" may have different versions but all environments except one + are same. + + Args: + name (str): Name of the tool group. + data (dict): Group's information with it's variants. + manager (ApplicationManager): Manager that creates the group. + """ + + def __init__(self, name, data, manager): + self.name = name + self._data = data + self.manager = manager + self._environment = data["environment"] + + variants = data.get("variants") or {} + variants_by_name = {} + for variant_name, variant_env in variants.items(): + tool = EnvironmentTool(variant_name, variant_env, self) + variants_by_name[variant_name] = tool + self.variants = variants_by_name + + def __repr__(self): + return "<{}> - {}".format(self.__class__.__name__, self.name) + + def __iter__(self): + for variant in self.variants.values(): + yield variant + + @property + def environment(self): + return copy.deepcopy(self._environment) + + +class EnvironmentTool: """Hold information about application tool. Structure of tool information. Args: - tool_name (str): Name of the tool. - group_name (str): Name of group which wraps tool. + name (str): Name of the tool. + environment (dict): Variant environments. + group (str): Name of group which wraps tool. """ - def __init__(self, tool_name, group_name): - self.name = tool_name - self.group_name = group_name + def __init__(self, name, environment, group): + self.name = name + self.group = group + self._environment = environment + self.full_name = "/".join((group.name, name)) + + def __repr__(self): + return "<{}> - {}".format(self.__class__.__name__, self.full_name) @property - def full_name(self): - return "/".join((self.group_name, self.name)) + def environment(self): + return copy.deepcopy(self._environment) class ApplicationExecutable: @@ -235,94 +418,6 @@ class ApplicationExecutable: return bool(self._realpath()) -class Application: - """Hold information about application. - - Object by itself does nothing special. - - Args: - app_group (str): App group name. - e.g. "maya", "nuke", "photoshop", etc. - app_name (str): Specific version (or variant) of host. - e.g. "maya2020", "nuke11.3", etc. - host_name (str): Name of host implementation. - app_data (dict): Data for the version containing information about - executables, label, variant label, icon or if is enabled. - Only required key is `executables`. - manager (ApplicationManager): Application manager that created object. - """ - - def __init__(self, app_group, app_name, host_name, app_data, manager): - self.app_group = app_group - self.app_name = app_name - self.host_name = host_name - self.app_data = app_data - self.manager = manager - - self.label = app_data.get("label") or app_name - self.variant_label = app_data.get("variant_label") or None - self.icon = app_data.get("icon") or None - self.enabled = app_data.get("enabled", True) - self.is_host = app_data.get("is_host", False) - - _executables = app_data["executables"] - if not _executables: - _executables = [] - - elif isinstance(_executables, dict): - _executables = _executables.get(platform.system().lower()) or [] - - _arguments = app_data["arguments"] - if not _arguments: - _arguments = [] - - elif isinstance(_arguments, dict): - _arguments = _arguments.get(platform.system().lower()) or [] - - executables = [] - for executable in _executables: - executables.append(ApplicationExecutable(executable)) - - self.executables = executables - self.arguments = _arguments - - @property - def full_label(self): - """Full label of application. - - Concatenate `label` and `variant_label` attributes if `variant_label` - is set. - """ - if self.variant_label: - return "{} {}".format(self.label, self.variant_label) - return str(self.label) - - def find_executable(self): - """Try to find existing executable for application. - - Returns (str): Path to executable from `executables` or None if any - exists. - """ - for executable in self.executables: - if executable.exists(): - return executable - return None - - def launch(self, *args, **kwargs): - """Launch the application. - - For this purpose is used manager's launch method to keep logic at one - place. - - Arguments must match with manager's launch method. That's why *args - **kwargs are used. - - Returns: - subprocess.Popen: Return executed process as Popen object. - """ - return self.manager.launch(self.app_name, *args, **kwargs) - - @six.add_metaclass(ABCMeta) class LaunchHook: """Abstract base class of launch hook.""" @@ -376,7 +471,7 @@ class LaunchHook: return False if cls.app_groups: - if launch_context.app_group not in cls.app_groups: + if launch_context.app_group.name not in cls.app_groups: return False if cls.app_names: @@ -403,11 +498,11 @@ class LaunchHook: @property def app_group(self): - return getattr(self.application, "app_group", None) + return getattr(self.application, "group", None) @property def app_name(self): - return getattr(self.application, "app_name", None) + return getattr(self.application, "name", None) def validate(self): """Optional validation of launch hook on initialization. @@ -482,12 +577,6 @@ class ApplicationLaunchContext: self.data = dict(data) - # Load settings if were not passed in data - settings_env = self.data.get("settings_env") - if settings_env is None: - settings_env = get_environments() - self.data["settings_env"] = settings_env - # subprocess.Popen launch arguments (first argument in constructor) self.launch_args = executable.as_args() self.launch_args.extend(application.arguments) @@ -550,8 +639,8 @@ class ApplicationLaunchContext: paths = [] # TODO load additional studio paths from settings - import pype - pype_dir = os.path.dirname(os.path.abspath(pype.__file__)) + import openpype + pype_dir = os.path.dirname(os.path.abspath(openpype.__file__)) # --- START: Backwards compatibility --- hooks_dir = os.path.join(pype_dir, "hooks") @@ -581,7 +670,7 @@ class ApplicationLaunchContext: paths.append(path) # Load modules paths - from pype.modules import ModulesManager + from openpype.modules import ModulesManager manager = ModulesManager() paths.extend(manager.collect_launch_hook_paths()) @@ -676,7 +765,7 @@ class ApplicationLaunchContext: @property def app_name(self): - return self.application.app_name + return self.application.name @property def host_name(self): @@ -684,7 +773,7 @@ class ApplicationLaunchContext: @property def app_group(self): - return self.application.app_group + return self.application.group @property def manager(self): @@ -806,9 +895,6 @@ class EnvironmentPrepData(dict): if data.get("env") is None: data["env"] = os.environ.copy() - if data.get("settings_env") is None: - data["settings_env"] = get_environments() - super(EnvironmentPrepData, self).__init__(data) @@ -898,29 +984,42 @@ def prepare_host_environments(data): app = data["app"] log = data["log"] - # Keys for getting environments - env_keys = [app.app_group, app.app_name] + # `added_env_keys` has debug purpose + added_env_keys = {app.group.name, app.name} + # Environments for application + environments = [ + app.group.environment, + app.environment + ] asset_doc = data.get("asset_doc") + # Add tools environments + groups_by_name = {} + tool_by_group_name = collections.defaultdict(list) if asset_doc: - # Add tools environments + # Make sure each tool group can be added only once for key in asset_doc["data"].get("tools_env") or []: tool = app.manager.tools.get(key) - if tool: - if tool.group_name not in env_keys: - env_keys.append(tool.group_name) + if not tool: + continue + groups_by_name[tool.group.name] = tool.group + tool_by_group_name[tool.group.name].append(tool) - if tool.name not in env_keys: - env_keys.append(tool.name) + for group_name, group in groups_by_name.items(): + environments.append(group.environment) + added_env_keys.add(group_name) + for tool in tool_by_group_name[group_name]: + environments.append(tool.environment) + added_env_keys.add(tool.name) log.debug( - "Finding environment groups for keys: {}".format(env_keys) + "Will add environments for apps and tools: {}".format( + ", ".join(added_env_keys) + ) ) - settings_env = data["settings_env"] env_values = {} - for env_key in env_keys: - _env_values = settings_env.get(env_key) + for _env_values in environments: if not _env_values: continue @@ -1018,7 +1117,8 @@ def prepare_context_environments(data): "AVALON_ASSET": asset_doc["name"], "AVALON_TASK": task_name, "AVALON_APP": app.host_name, - "AVALON_APP_NAME": app.app_name, + # TODO this hould be `app.full_name` in future PRs + "AVALON_APP_NAME": app.name, "AVALON_WORKDIR": workdir } log.debug( @@ -1087,7 +1187,7 @@ def _prepare_last_workfile(data, workdir): file_template = anatomy.templates["work"]["file"] workdir_data.update({ "version": 1, - "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), + "user": os.environ.get("OPENPYPE_USERNAME") or getpass.getuser(), "ext": extensions[0] }) diff --git a/pype/lib/avalon_context.py b/openpype/lib/avalon_context.py similarity index 99% rename from pype/lib/avalon_context.py rename to openpype/lib/avalon_context.py index d4daf22142..1f7c693b85 100644 --- a/pype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -7,7 +7,7 @@ import logging import collections import functools -from pype.settings import get_project_settings +from openpype.settings import get_project_settings from .anatomy import Anatomy # avalon module is not imported at the top @@ -1153,7 +1153,7 @@ def get_creator_by_name(creator_name, case_sensitive=False): @with_avalon def change_timer_to_current_context(): """Called after context change to change timers""" - webserver_url = os.environ.get("PYPE_WEBSERVER_URL") + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") if not webserver_url: log.warning("Couldn't find webserver url") return diff --git a/pype/lib/config.py b/openpype/lib/config.py similarity index 100% rename from pype/lib/config.py rename to openpype/lib/config.py diff --git a/pype/lib/editorial.py b/openpype/lib/editorial.py similarity index 100% rename from pype/lib/editorial.py rename to openpype/lib/editorial.py diff --git a/pype/lib/env_tools.py b/openpype/lib/env_tools.py similarity index 97% rename from pype/lib/env_tools.py rename to openpype/lib/env_tools.py index 7e392fad6e..025c13a322 100644 --- a/pype/lib/env_tools.py +++ b/openpype/lib/env_tools.py @@ -1,5 +1,5 @@ import os -from pype.settings import get_environments +from openpype.settings import get_environments def env_value_to_bool(env_key=None, value=None, default=False): @@ -88,7 +88,7 @@ def get_global_environments(env=None): """ import acre - from pype.modules import ModulesManager + from openpype.modules import ModulesManager if env is None: env = {} diff --git a/pype/lib/execute.py b/openpype/lib/execute.py similarity index 97% rename from pype/lib/execute.py rename to openpype/lib/execute.py index f815d05f1b..441dcfa754 100644 --- a/pype/lib/execute.py +++ b/openpype/lib/execute.py @@ -150,13 +150,13 @@ def get_pype_execute_args(*args): It is possible to pass any arguments that will be added after pype executables. """ - pype_executable = os.environ["PYPE_EXECUTABLE"] + pype_executable = os.environ["OPENPYPE_EXECUTABLE"] pype_args = [pype_executable] executable_filename = os.path.basename(pype_executable) if "python" in executable_filename.lower(): pype_args.append( - os.path.join(os.environ["PYPE_ROOT"], "start.py") + os.path.join(os.environ["OPENPYPE_ROOT"], "start.py") ) if args: diff --git a/pype/lib/ffmpeg_utils.py b/openpype/lib/ffmpeg_utils.py similarity index 100% rename from pype/lib/ffmpeg_utils.py rename to openpype/lib/ffmpeg_utils.py diff --git a/pype/lib/git_progress.py b/openpype/lib/git_progress.py similarity index 100% rename from pype/lib/git_progress.py rename to openpype/lib/git_progress.py diff --git a/pype/lib/import_utils.py b/openpype/lib/import_utils.py similarity index 93% rename from pype/lib/import_utils.py rename to openpype/lib/import_utils.py index 5c832a925c..5fca0ae5f9 100644 --- a/pype/lib/import_utils.py +++ b/openpype/lib/import_utils.py @@ -8,7 +8,7 @@ log = Logger().get_logger(__name__) def discover_host_vendor_module(module_name): host = os.environ["AVALON_APP"] - pype_root = os.environ["PYPE_ROOT"] + pype_root = os.environ["OPENPYPE_ROOT"] main_module = module_name.split(".")[0] module_path = os.path.join( pype_root, "hosts", host, "vendor", main_module) diff --git a/pype/lib/local_settings.py b/openpype/lib/local_settings.py similarity index 98% rename from pype/lib/local_settings.py rename to openpype/lib/local_settings.py index aa372a52d2..82507cb0c0 100644 --- a/pype/lib/local_settings.py +++ b/openpype/lib/local_settings.py @@ -213,7 +213,7 @@ class IniSettingRegistry(ASettingRegistry): # type: (str, str) -> IniSettingRegistry super(IniSettingRegistry, self).__init__(name) # get registry file - version = os.getenv("PYPE_VERSION", "N/A") + version = os.getenv("OPENPYPE_VERSION", "N/A") self._registry_file = os.path.join(path, "{}.ini".format(name)) if not os.path.exists(self._registry_file): with open(self._registry_file, mode="w") as cfg: @@ -369,7 +369,7 @@ class JSONSettingRegistry(ASettingRegistry): now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") header = { "__metadata__": { - "pype-version": os.getenv("PYPE_VERSION", "N/A"), + "pype-version": os.getenv("OPENPYPE_VERSION", "N/A"), "generated": now }, "registry": {} @@ -496,10 +496,10 @@ def get_local_site_id(): return _create_local_site_id() -def change_pype_mongo_url(new_mongo_url): +def change_openpype_mongo_url(new_mongo_url): """Change mongo url in pype registry. - Change of Pype mongo URL require restart of running pype processes or + Change of OpenPype mongo URL require restart of running pype processes or processes using pype. """ diff --git a/pype/lib/log.py b/openpype/lib/log.py similarity index 96% rename from pype/lib/log.py rename to openpype/lib/log.py index f6e95eea8a..9745279e28 100644 --- a/pype/lib/log.py +++ b/openpype/lib/log.py @@ -1,11 +1,11 @@ """ Logging to console and to mongo. For mongo logging, you need to set either -``PYPE_LOG_MONGO_URL`` to something like: +``OPENPYPE_LOG_MONGO_URL`` to something like: .. example:: mongo://user:password@hostname:port/database/collection?authSource=avalon -or set ``PYPE_LOG_MONGO_HOST`` and other variables. +or set ``OPENPYPE_LOG_MONGO_HOST`` and other variables. See :func:`_mongo_settings` Best place for it is in ``repos/pype-config/environments/global.json`` @@ -28,7 +28,7 @@ from . import Terminal from .mongo import ( MongoEnvNotSet, decompose_url, - PypeMongoConnection + OpenPypeMongoConnection ) try: import log4mongo @@ -203,11 +203,11 @@ class PypeLogger: log_mongo_url_components = None # Database name in Mongo - log_database_name = "pype" + log_database_name = os.environ["OPENPYPE_DATABASE_NAME"] # Collection name under database in Mongo log_collection_name = "logs" - # PYPE_DEBUG + # OPENPYPE_DEBUG pype_debug = 0 # Data same for all record documents @@ -335,7 +335,7 @@ class PypeLogger: # like Ftrack event server has 3 other subprocesses that should # use same mongo id if use_mongo_logging: - mongo_id = os.environ.pop("PYPE_PROCESS_MONGO_ID", None) + mongo_id = os.environ.pop("OPENPYPE_PROCESS_MONGO_ID", None) if not mongo_id: # Create new object id mongo_id = ObjectId() @@ -347,11 +347,11 @@ class PypeLogger: # Store result to class definition cls.use_mongo_logging = use_mongo_logging - # Define if is in PYPE_DEBUG mode - cls.pype_debug = int(os.getenv("PYPE_DEBUG") or "0") + # Define if is in OPENPYPE_DEBUG mode + cls.pype_debug = int(os.getenv("OPENPYPE_DEBUG") or "0") # Mongo URL where logs will be stored - cls.log_mongo_url = os.environ.get("PYPE_MONGO") + cls.log_mongo_url = os.environ.get("OPENPYPE_MONGO") if not cls.log_mongo_url: cls.use_mongo_logging = False @@ -470,7 +470,7 @@ class PypeLogger: if not cls.initialized: cls.initialize() - return PypeMongoConnection.get_mongo_client(cls.log_mongo_url) + return OpenPypeMongoConnection.get_mongo_client(cls.log_mongo_url) def timeit(method): diff --git a/pype/lib/mongo.py b/openpype/lib/mongo.py similarity index 96% rename from pype/lib/mongo.py rename to openpype/lib/mongo.py index 3ee43bb934..8bfaba75d6 100644 --- a/pype/lib/mongo.py +++ b/openpype/lib/mongo.py @@ -77,7 +77,7 @@ def compose_url(scheme=None, def get_default_components(): - mongo_url = os.environ.get("PYPE_MONGO") + mongo_url = os.environ.get("OPENPYPE_MONGO") if mongo_url is None: raise MongoEnvNotSet( "URL for Mongo logging connection is not set." @@ -129,17 +129,17 @@ def validate_mongo_connection(mongo_uri): client.close() -class PypeMongoConnection: +class OpenPypeMongoConnection: """Singleton MongoDB connection. Keeps MongoDB connections by url. """ mongo_clients = {} - log = logging.getLogger("PypeMongoConnection") + log = logging.getLogger("OpenPypeMongoConnection") @staticmethod def get_default_mongo_url(): - return os.environ["PYPE_MONGO"] + return os.environ["OPENPYPE_MONGO"] @classmethod def get_mongo_client(cls, mongo_url=None): diff --git a/pype/lib/path_tools.py b/openpype/lib/path_tools.py similarity index 100% rename from pype/lib/path_tools.py rename to openpype/lib/path_tools.py diff --git a/pype/lib/plugin_tools.py b/openpype/lib/plugin_tools.py similarity index 75% rename from pype/lib/plugin_tools.py rename to openpype/lib/plugin_tools.py index c03d978ad4..eb024383d3 100644 --- a/pype/lib/plugin_tools.py +++ b/openpype/lib/plugin_tools.py @@ -8,12 +8,93 @@ import json import tempfile from .execute import run_subprocess +from .profiles_filtering import filter_profiles -from pype.settings import get_project_settings +from openpype.settings import get_project_settings log = logging.getLogger(__name__) +# Subset name template used when plugin does not have defined any +DEFAULT_SUBSET_TEMPLATE = "{family}{Variant}" + + +class TaskNotSetError(KeyError): + def __init__(self, msg=None): + if not msg: + msg = "Creator's subset name template requires task name." + super(TaskNotSetError, self).__init__(msg) + + +def get_subset_name( + family, + variant, + task_name, + asset_id, + project_name=None, + host_name=None, + default_template=None +): + if not family: + return "" + + if not host_name: + host_name = os.environ["AVALON_APP"] + + # Use only last part of class family value split by dot (`.`) + family = family.rsplit(".", 1)[-1] + + # Get settings + tools_settings = get_project_settings(project_name)["global"]["tools"] + profiles = tools_settings["creator"]["subset_name_profiles"] + filtering_criteria = { + "families": family, + "hosts": host_name, + "tasks": task_name + } + + matching_profile = filter_profiles(profiles, filtering_criteria) + template = None + if matching_profile: + template = matching_profile["template"] + + # Make sure template is set (matching may have empty string) + if not template: + template = default_template or DEFAULT_SUBSET_TEMPLATE + + # Simple check of task name existence for template with {task} in + # - missing task should be possible only in Standalone publisher + if not task_name and "{task" in template.lower(): + raise TaskNotSetError() + + fill_pairs = ( + ("variant", variant), + ("family", family), + ("task", task_name) + ) + fill_data = {} + for key, value in fill_pairs: + # Handle cases when value is `None` (standalone publisher) + if value is None: + continue + # Keep value as it is + fill_data[key] = value + # Both key and value are with upper case + fill_data[key.upper()] = value.upper() + + # Capitalize only first char of value + # - conditions are because of possible index errors + capitalized = "" + if value: + # Upper first character + capitalized += value[0].upper() + # Append rest of string if there is any + if len(value) > 1: + capitalized += value[1:] + fill_data[key.capitalize()] = capitalized + + return template.format(**fill_data) + def filter_pyblish_plugins(plugins): """Filter pyblish plugins by presets. @@ -154,7 +235,7 @@ def oiio_supported(): Returns: (bool) """ - oiio_path = os.getenv("PYPE_OIIO_PATH", "") + oiio_path = os.getenv("OPENPYPE_OIIO_PATH", "") if not oiio_path or not os.path.exists(oiio_path): log.debug("OIIOTool is not configured or not present at {}". format(oiio_path)) @@ -188,7 +269,7 @@ def decompress(target_dir, file_url, (int(input_frame_end) > int(input_frame_start)) oiio_cmd = [] - oiio_cmd.append(os.getenv("PYPE_OIIO_PATH")) + oiio_cmd.append(os.getenv("OPENPYPE_OIIO_PATH")) oiio_cmd.append("--compression none") @@ -247,7 +328,7 @@ def should_decompress(file_url): """ if oiio_supported(): output = run_subprocess([ - os.getenv("PYPE_OIIO_PATH"), + os.getenv("OPENPYPE_OIIO_PATH"), "--info", "-v", file_url]) return "compression: \"dwaa\"" in output or \ "compression: \"dwab\"" in output diff --git a/pype/lib/profiles_filtering.py b/openpype/lib/profiles_filtering.py similarity index 94% rename from pype/lib/profiles_filtering.py rename to openpype/lib/profiles_filtering.py index 32c17cbd12..c4410204dd 100644 --- a/pype/lib/profiles_filtering.py +++ b/openpype/lib/profiles_filtering.py @@ -59,6 +59,14 @@ def _profile_exclusion(matching_profiles, logger): return matching_profiles[0][0] +def fullmatch(regex, string, flags=0): + """Emulate python-3.4 re.fullmatch().""" + matched = re.match(regex, string, flags=flags) + if matched and matched.span()[1] == len(string): + return matched + return None + + def validate_value_by_regexes(value, in_list): """Validates in any regex from list match entered value. @@ -87,7 +95,11 @@ def validate_value_by_regexes(value, in_list): regexes = compile_list_of_regexes(in_list) for regex in regexes: - if re.match(regex, value): + if hasattr(regex, "fullmatch"): + result = regex.fullmatch(value) + else: + result = fullmatch(regex, value) + if result: return 1 return -1 diff --git a/pype/lib/profiling.py b/openpype/lib/profiling.py similarity index 100% rename from pype/lib/profiling.py rename to openpype/lib/profiling.py diff --git a/pype/lib/pype_info.py b/openpype/lib/pype_info.py similarity index 90% rename from pype/lib/pype_info.py rename to openpype/lib/pype_info.py index cbcc5811a0..93d669eb0d 100644 --- a/pype/lib/pype_info.py +++ b/openpype/lib/pype_info.py @@ -5,15 +5,15 @@ import platform import getpass import socket -import pype.version -from pype.settings.lib import get_local_settings +import openpype.version +from openpype.settings.lib import get_local_settings from .execute import get_pype_execute_args from .local_settings import get_local_site_id def get_pype_version(): """Version of pype that is currently used.""" - return pype.version.__version__ + return openpype.version.__version__ def get_pype_info(): @@ -28,8 +28,8 @@ def get_pype_info(): "version": get_pype_version(), "version_type": version_type, "executable": executable_args[-1], - "pype_root": os.environ["PYPE_ROOT"], - "mongo_url": os.environ["PYPE_MONGO"] + "pype_root": os.environ["OPENPYPE_ROOT"], + "mongo_url": os.environ["OPENPYPE_MONGO"] } diff --git a/pype/lib/python_module_tools.py b/openpype/lib/python_module_tools.py similarity index 100% rename from pype/lib/python_module_tools.py rename to openpype/lib/python_module_tools.py diff --git a/pype/lib/splash.txt b/openpype/lib/splash.txt similarity index 100% rename from pype/lib/splash.txt rename to openpype/lib/splash.txt diff --git a/pype/lib/terminal.py b/openpype/lib/terminal.py similarity index 94% rename from pype/lib/terminal.py rename to openpype/lib/terminal.py index 51b0bcebd6..ddc917ac4e 100644 --- a/pype/lib/terminal.py +++ b/openpype/lib/terminal.py @@ -21,7 +21,7 @@ class Terminal: If :mod:`Colorama` is not found, it will still work, but without colors. Depends on :mod:`Colorama` - Using **PYPE_LOG_NO_COLORS** environment variable. + Using **OPENPYPE_LOG_NO_COLORS** environment variable. """ # Is Terminal initialized @@ -38,7 +38,8 @@ class Terminal: """Initialize Terminal class as object. First check if colorized output is disabled by environment variable - `PYPE_LOG_NO_COLORS` value. By default is colorized output turned on. + `OPENPYPE_LOG_NO_COLORS` value. By default is colorized output turned + on. Then tries to import python module that do the colors magic and create it's terminal object. Colorized output is not used if import of python @@ -47,9 +48,9 @@ class Terminal: Set `_initialized` attribute to `True` when is done. """ - from pype.lib import env_value_to_bool + from openpype.lib import env_value_to_bool use_colors = env_value_to_bool( - "PYPE_LOG_NO_COLORS", default=Terminal.use_colors + "OPENPYPE_LOG_NO_COLORS", default=Terminal.use_colors ) if not use_colors: Terminal.use_colors = use_colors @@ -166,7 +167,7 @@ class Terminal: def log(message): """Return color formatted message. - If environment variable `PYPE_LOG_NO_COLORS` is set to + If environment variable `OPENPYPE_LOG_NO_COLORS` is set to whatever value, message will be formatted but not colorized. Args: diff --git a/pype/lib/terminal_splash.py b/openpype/lib/terminal_splash.py similarity index 100% rename from pype/lib/terminal_splash.py rename to openpype/lib/terminal_splash.py diff --git a/pype/modules/README.md b/openpype/modules/README.md similarity index 100% rename from pype/modules/README.md rename to openpype/modules/README.md diff --git a/pype/modules/__init__.py b/openpype/modules/__init__.py similarity index 100% rename from pype/modules/__init__.py rename to openpype/modules/__init__.py diff --git a/pype/modules/avalon_apps/__init__.py b/openpype/modules/avalon_apps/__init__.py similarity index 100% rename from pype/modules/avalon_apps/__init__.py rename to openpype/modules/avalon_apps/__init__.py diff --git a/pype/modules/avalon_apps/avalon_app.py b/openpype/modules/avalon_apps/avalon_app.py similarity index 96% rename from pype/modules/avalon_apps/avalon_app.py rename to openpype/modules/avalon_apps/avalon_app.py index 38d6e4394c..243c4f928a 100644 --- a/pype/modules/avalon_apps/avalon_app.py +++ b/openpype/modules/avalon_apps/avalon_app.py @@ -1,6 +1,6 @@ import os -import pype -from pype import resources +import openpype +from openpype import resources from .. import ( PypeModule, ITrayModule, @@ -23,7 +23,7 @@ class AvalonModule(PypeModule, ITrayModule, IWebServerRoutes): avalon_mongo_url = avalon_settings["AVALON_MONGO"] # Use pype mongo if Avalon's mongo not defined if not avalon_mongo_url: - avalon_mongo_url = os.environ["PYPE_MONGO"] + avalon_mongo_url = os.environ["OPENPYPE_MONGO"] thumbnail_root = os.environ.get("AVALON_THUMBNAIL_ROOT") if not thumbnail_root: diff --git a/pype/modules/avalon_apps/rest_api.py b/openpype/modules/avalon_apps/rest_api.py similarity index 98% rename from pype/modules/avalon_apps/rest_api.py rename to openpype/modules/avalon_apps/rest_api.py index 459103594a..b77c256398 100644 --- a/pype/modules/avalon_apps/rest_api.py +++ b/openpype/modules/avalon_apps/rest_api.py @@ -10,7 +10,7 @@ import bson.json_util from aiohttp.web_response import Response from avalon.api import AvalonMongoDB -from pype.modules.webserver.base_routes import RestApiEndpoint +from openpype.modules.webserver.base_routes import RestApiEndpoint class _RestApiEndpoint(RestApiEndpoint): diff --git a/pype/modules/base.py b/openpype/modules/base.py similarity index 98% rename from pype/modules/base.py rename to openpype/modules/base.py index fe68c14705..b8d76aa028 100644 --- a/pype/modules/base.py +++ b/openpype/modules/base.py @@ -8,10 +8,10 @@ from uuid import uuid4 from abc import ABCMeta, abstractmethod import six -import pype -from pype.settings import get_system_settings -from pype.lib import PypeLogger -from pype import resources +import openpype +from openpype.settings import get_system_settings +from openpype.lib import PypeLogger +from openpype import resources @six.add_metaclass(ABCMeta) @@ -322,13 +322,13 @@ class ModulesManager: prev_start_time = time_start # Go through globals in `pype.modules` - for name in dir(pype.modules): - modules_item = getattr(pype.modules, name, None) + for name in dir(openpype.modules): + modules_item = getattr(openpype.modules, name, None) # Filter globals that are not classes which inherit from PypeModule if ( not inspect.isclass(modules_item) - or modules_item is pype.modules.PypeModule - or not issubclass(modules_item, pype.modules.PypeModule) + or modules_item is openpype.modules.PypeModule + or not issubclass(modules_item, openpype.modules.PypeModule) ): continue diff --git a/pype/modules/clockify/__init__.py b/openpype/modules/clockify/__init__.py similarity index 100% rename from pype/modules/clockify/__init__.py rename to openpype/modules/clockify/__init__.py diff --git a/pype/modules/clockify/clockify_api.py b/openpype/modules/clockify/clockify_api.py similarity index 100% rename from pype/modules/clockify/clockify_api.py rename to openpype/modules/clockify/clockify_api.py diff --git a/pype/modules/clockify/clockify_module.py b/openpype/modules/clockify/clockify_module.py similarity index 99% rename from pype/modules/clockify/clockify_module.py rename to openpype/modules/clockify/clockify_module.py index a91addb971..e3751c46b8 100644 --- a/pype/modules/clockify/clockify_module.py +++ b/openpype/modules/clockify/clockify_module.py @@ -7,7 +7,7 @@ from .constants import ( CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH ) -from pype.modules import ( +from openpype.modules import ( PypeModule, ITrayModule, IPluginPaths, diff --git a/pype/modules/clockify/constants.py b/openpype/modules/clockify/constants.py similarity index 100% rename from pype/modules/clockify/constants.py rename to openpype/modules/clockify/constants.py diff --git a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py b/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py similarity index 97% rename from pype/modules/clockify/ftrack/server/action_clockify_sync_server.py rename to openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py index be58e0c7cc..495f87dc7e 100644 --- a/pype/modules/clockify/ftrack/server/action_clockify_sync_server.py +++ b/openpype/modules/clockify/ftrack/server/action_clockify_sync_server.py @@ -1,7 +1,7 @@ import os import json -from pype.modules.ftrack.lib import ServerAction -from pype.modules.clockify.clockify_api import ClockifyAPI +from openpype.modules.ftrack.lib import ServerAction +from openpype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyServer(ServerAction): diff --git a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py b/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py similarity index 96% rename from pype/modules/clockify/ftrack/user/action_clockify_sync_local.py rename to openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py index 87682286ab..4f4579a8bf 100644 --- a/pype/modules/clockify/ftrack/user/action_clockify_sync_local.py +++ b/openpype/modules/clockify/ftrack/user/action_clockify_sync_local.py @@ -1,6 +1,6 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.clockify.clockify_api import ClockifyAPI +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.clockify.clockify_api import ClockifyAPI class SyncClocifyLocal(BaseAction): diff --git a/pype/modules/clockify/launcher_actions/ClockifyStart.py b/openpype/modules/clockify/launcher_actions/ClockifyStart.py similarity index 92% rename from pype/modules/clockify/launcher_actions/ClockifyStart.py rename to openpype/modules/clockify/launcher_actions/ClockifyStart.py index d02005e2de..c431ea240d 100644 --- a/pype/modules/clockify/launcher_actions/ClockifyStart.py +++ b/openpype/modules/clockify/launcher_actions/ClockifyStart.py @@ -1,6 +1,6 @@ from avalon import api, io -from pype.api import Logger -from pype.modules.clockify.clockify_api import ClockifyAPI +from openpype.api import Logger +from openpype.modules.clockify.clockify_api import ClockifyAPI log = Logger().get_logger(__name__) diff --git a/pype/modules/clockify/launcher_actions/ClockifySync.py b/openpype/modules/clockify/launcher_actions/ClockifySync.py similarity index 95% rename from pype/modules/clockify/launcher_actions/ClockifySync.py rename to openpype/modules/clockify/launcher_actions/ClockifySync.py index 5f0e57b8c8..1bb168a80b 100644 --- a/pype/modules/clockify/launcher_actions/ClockifySync.py +++ b/openpype/modules/clockify/launcher_actions/ClockifySync.py @@ -1,6 +1,6 @@ from avalon import api, io -from pype.modules.clockify.clockify_api import ClockifyAPI -from pype.api import Logger +from openpype.modules.clockify.clockify_api import ClockifyAPI +from openpype.api import Logger log = Logger().get_logger(__name__) diff --git a/pype/modules/clockify/widgets.py b/openpype/modules/clockify/widgets.py similarity index 99% rename from pype/modules/clockify/widgets.py rename to openpype/modules/clockify/widgets.py index 718e6668bd..76f3a3f365 100644 --- a/pype/modules/clockify/widgets.py +++ b/openpype/modules/clockify/widgets.py @@ -1,6 +1,6 @@ from Qt import QtCore, QtGui, QtWidgets from avalon import style -from pype import resources +from openpype import resources class MessageWidget(QtWidgets.QWidget): diff --git a/pype/modules/deadline/__init__.py b/openpype/modules/deadline/__init__.py similarity index 100% rename from pype/modules/deadline/__init__.py rename to openpype/modules/deadline/__init__.py diff --git a/pype/modules/deadline/deadline_module.py b/openpype/modules/deadline/deadline_module.py similarity index 88% rename from pype/modules/deadline/deadline_module.py rename to openpype/modules/deadline/deadline_module.py index ba920f7f13..2a2fba41d6 100644 --- a/pype/modules/deadline/deadline_module.py +++ b/openpype/modules/deadline/deadline_module.py @@ -1,5 +1,5 @@ import os -from pype.modules import ( +from openpype.modules import ( PypeModule, IPluginPaths) @@ -13,7 +13,7 @@ class DeadlineModule(PypeModule, IPluginPaths): self.deadline_url = deadline_settings["DEADLINE_REST_URL"] def get_global_environments(self): - """Deadline global environments for pype implementation.""" + """Deadline global environments for OpenPype implementation.""" return { "DEADLINE_REST_URL": self.deadline_url } diff --git a/pype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py similarity index 93% rename from pype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py rename to openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py index c1a6de4ce3..38a6b9b246 100644 --- a/pype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_aftereffects_deadline.py @@ -1,5 +1,5 @@ -from pype.lib import abstract_submit_deadline -from pype.lib.abstract_submit_deadline import DeadlineJobInfo +from openpype.lib import abstract_submit_deadline +from openpype.lib.abstract_submit_deadline import DeadlineJobInfo import pyblish.api import os import attr @@ -64,9 +64,9 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "PYPE_USERNAME", - "PYPE_DEV", - "PYPE_LOG_NO_COLORS" + "OPENPYPE_USERNAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS" ] environment = dict({key: os.environ[key] for key in keys @@ -78,7 +78,7 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline key=key, value=val) # to recognize job from PYPE for turning Event On/Off - dln_job_info.EnvironmentKeyValue = "PYPE_RENDER_JOB=1" + dln_job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" return dln_job_info diff --git a/pype/modules/deadline/plugins/publish/submit_harmony_deadline..py b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline..py similarity index 97% rename from pype/modules/deadline/plugins/publish/submit_harmony_deadline..py rename to openpype/modules/deadline/plugins/publish/submit_harmony_deadline..py index 8e85937353..58379443f7 100644 --- a/pype/modules/deadline/plugins/publish/submit_harmony_deadline..py +++ b/openpype/modules/deadline/plugins/publish/submit_harmony_deadline..py @@ -9,8 +9,8 @@ import re import attr import pyblish.api -import pype.lib.abstract_submit_deadline -from pype.lib.abstract_submit_deadline import DeadlineJobInfo +import openpype.lib.abstract_submit_deadline +from openpype.lib.abstract_submit_deadline import DeadlineJobInfo from avalon import api @@ -217,7 +217,7 @@ class PluginInfo(object): class HarmonySubmitDeadline( - pype.lib.abstract_submit_deadline.AbstractSubmitDeadline): + openpype.lib.abstract_submit_deadline.AbstractSubmitDeadline): """Submit render write of Harmony scene to Deadline. Renders are submitted to a Deadline Web Service as @@ -273,9 +273,9 @@ class HarmonySubmitDeadline( "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "PYPE_USERNAME", - "PYPE_DEV", - "PYPE_LOG_NO_COLORS" + "OPENPYPE_USERNAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS" ] environment = dict({key: os.environ[key] for key in keys @@ -288,7 +288,7 @@ class HarmonySubmitDeadline( value=val) # to recognize job from PYPE for turning Event On/Off - job_info.EnvironmentKeyValue = "PYPE_RENDER_JOB=1" + job_info.EnvironmentKeyValue = "OPENPYPE_RENDER_JOB=1" return job_info diff --git a/pype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py similarity index 97% rename from pype/modules/deadline/plugins/publish/submit_maya_deadline.py rename to openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index b17dd6ba8d..3aea837bb1 100644 --- a/pype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -35,7 +35,7 @@ from maya import cmds from avalon import api import pyblish.api -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib # Documentation for keys available at: # https://docs.thinkboxsoftware.com @@ -252,7 +252,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): families = ["renderlayer"] use_published = True - tile_assembler_plugin = "PypeTileAssembler" + tile_assembler_plugin = "OpenPypeTileAssembler" asset_dependencies = False limit_groups = [] group = "none" @@ -441,17 +441,17 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "PYPE_USERNAME", - "PYPE_DEV", - "PYPE_LOG_NO_COLORS" + "OPENPYPE_USERNAME", + "OPENPYPE_DEV", + "OPENPYPE_LOG_NO_COLORS" ] environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **api.Session) - environment["PYPE_LOG_NO_COLORS"] = "1" - environment["PYPE_MAYA_VERSION"] = cmds.about(v=True) + environment["OPENPYPE_LOG_NO_COLORS"] = "1" + environment["OPENPYPE_MAYA_VERSION"] = cmds.about(v=True) # to recognize job from PYPE for turning Event On/Off - environment["PYPE_RENDER_JOB"] = "1" + environment["OPENPYPE_RENDER_JOB"] = "1" self.payload_skeleton["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( key=key, @@ -816,7 +816,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): def _get_arnold_export_payload(self, data): try: - from pype.scripts import export_maya_ass_job + from openpype.scripts import export_maya_ass_job except Exception: raise AssertionError( "Expected module 'export_maya_ass_job' to be available") @@ -858,20 +858,20 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): envs.append( "AVALON_APP_NAME={}".format(os.environ.get("AVALON_APP_NAME"))) envs.append( - "PYPE_ASS_EXPORT_RENDER_LAYER={}".format(data["renderlayer"])) + "OPENPYPE_ASS_EXPORT_RENDER_LAYER={}".format(data["renderlayer"])) envs.append( - "PYPE_ASS_EXPORT_SCENE_FILE={}".format(data["filepath"])) + "OPENPYPE_ASS_EXPORT_SCENE_FILE={}".format(data["filepath"])) envs.append( - "PYPE_ASS_EXPORT_OUTPUT={}".format( + "OPENPYPE_ASS_EXPORT_OUTPUT={}".format( payload['JobInfo']['OutputFilename0'])) envs.append( - "PYPE_ASS_EXPORT_START={}".format( + "OPENPYPE_ASS_EXPORT_START={}".format( int(self._instance.data["frameStartHandle"]))) envs.append( - "PYPE_ASS_EXPORT_END={}".format( + "OPENPYPE_ASS_EXPORT_END={}".format( int(self._instance.data["frameEndHandle"]))) envs.append( - "PYPE_ASS_EXPORT_STEP={}".format(1)) + "OPENPYPE_ASS_EXPORT_STEP={}".format(1)) i = 0 for e in envs: @@ -984,7 +984,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.post(*args, **kwargs) @@ -1003,7 +1003,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.get(*args, **kwargs) diff --git a/pype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py similarity index 87% rename from pype/modules/deadline/plugins/publish/submit_nuke_deadline.py rename to openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 60cc179a9b..2e30e624ef 100644 --- a/pype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -6,6 +6,7 @@ from avalon import api from avalon.vendor import requests import re import pyblish.api +import nuke class NukeSubmitDeadline(pyblish.api.InstancePlugin): @@ -29,6 +30,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): secondary_pool = "" group = "" department = "" + limit_groups = {} def process(self, instance): instance.data["toBeRenderedOn"] = "deadline" @@ -149,6 +151,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): if not priority: priority = self.priority + # resolve any limit groups + limit_groups = self.get_limit_groups() + self.log.info("Limit groups: `{}`".format(limit_groups)) + payload = { "JobInfo": { # Top-level group name @@ -180,7 +186,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/") + "OutputFilename0": output_filename_0.replace("\\", "/"), + + # limiting groups + "LimitGroups": ",".join(limit_groups) }, "PluginInfo": { @@ -228,7 +237,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "PYBLISHPLUGINPATH", "NUKE_PATH", "TOOL_ENV", - "PYPE_DEV", + "OPENPYPE_DEV", "FOUNDRY_LICENSE" ] environment = dict({key: os.environ[key] for key in keys @@ -270,7 +279,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): environment = clean_environment # to recognize job from PYPE for turning Event On/Off - environment["PYPE_RENDER_JOB"] = "1" + environment["OPENPYPE_RENDER_JOB"] = "1" payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( key=key, @@ -329,9 +338,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): return int(search_results[1]) if "#" in path: self.log.debug("_ path: `{}`".format(path)) - return path - else: - return path + return path def expected_files(self, instance, @@ -339,7 +346,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): """ Create expected files in instance data """ if not instance.data.get("expectedFiles"): - instance.data["expectedFiles"] = list() + instance.data["expectedFiles"] = [] dir = os.path.dirname(path) file = os.path.basename(path) @@ -356,3 +363,28 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): for i in range(self._frame_start, (self._frame_end + 1)): instance.data["expectedFiles"].append( os.path.join(dir, (file % i)).replace("\\", "/")) + + def get_limit_groups(self): + """Search for limit group nodes and return group name. + Limit groups will be defined as pairs in Nuke deadline submitter + presents where the key will be name of limit group and value will be + a list of plugin's node class names. Thus, when a plugin uses more + than one node, these will be captured and the triggered process + will add the appropriate limit group to the payload jobinfo attributes. + Returning: + list: captured groups list + """ + captured_groups = [] + for lg_name, list_node_class in self.deadline_limit_groups.items(): + for node_class in list_node_class: + for node in nuke.allNodes(recurseGroups=True): + # ignore all nodes not member of defined class + if node.Class() not in node_class: + continue + # ignore all disabled nodes + if node["disable"].value(): + continue + # add group name if not already added + if lg_name not in captured_groups: + captured_groups.append(lg_name) + return captured_groups diff --git a/pype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py similarity index 97% rename from pype/modules/deadline/plugins/publish/submit_publish_job.py rename to openpype/modules/deadline/plugins/publish/submit_publish_job.py index 38d328b1cb..a2e21fb766 100644 --- a/pype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -6,7 +6,7 @@ import json import re from copy import copy, deepcopy import sys -import pype.api +import openpype.api from avalon import api, io from avalon.vendor import requests, clique @@ -98,7 +98,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline or Muster" order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" - deadline_plugin = "Pype" + deadline_plugin = "OpenPype" hosts = ["fusion", "maya", "nuke", "celaction", "aftereffects", "harmony"] @@ -114,16 +114,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "PYPE_METADATA_FILE", + "OPENPYPE_METADATA_FILE", "AVALON_PROJECT", "AVALON_ASSET", "AVALON_TASK", "AVALON_APP_NAME", - "PYPE_PUBLISH_JOB" - "PYPE_LOG_NO_COLORS", - "PYPE_USERNAME", - "PYPE_RENDER_JOB", - "PYPE_PUBLISH_JOB" + "OPENPYPE_PUBLISH_JOB" + + "OPENPYPE_LOG_NO_COLORS", + "OPENPYPE_USERNAME", + "OPENPYPE_RENDER_JOB", + "OPENPYPE_PUBLISH_JOB" ] # custom deadline atributes @@ -223,10 +224,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): environment["AVALON_ASSET"] = io.Session["AVALON_ASSET"] environment["AVALON_TASK"] = io.Session["AVALON_TASK"] environment["AVALON_APP_NAME"] = os.environ.get("AVALON_APP_NAME") - environment["PYPE_LOG_NO_COLORS"] = "1" - environment["PYPE_USERNAME"] = instance.context.data["user"] - environment["PYPE_PUBLISH_JOB"] = "1" - environment["PYPE_RENDER_JOB"] = "0" + environment["OPENPYPE_LOG_NO_COLORS"] = "1" + environment["OPENPYPE_USERNAME"] = instance.context.data["user"] + environment["OPENPYPE_PUBLISH_JOB"] = "1" + environment["OPENPYPE_RENDER_JOB"] = "0" args = [ 'publish', @@ -314,8 +315,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # get latest version of subset # this will stop if subset wasn't published yet - version = pype.api.get_latest_version(instance.data.get("asset"), - instance.data.get("subset")) + version = openpype.api.get_latest_version(instance.data.get("asset"), + instance.data.get("subset")) # get its files based on extension subset_resources = get_resources(version, representation.get("ext")) r_col, _ = clique.assemble(subset_resources) @@ -974,9 +975,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): prev_start = None prev_end = None - version = pype.api.get_latest_version(asset_name=asset, - subset_name=subset - ) + version = openpype.api.get_latest_version(asset_name=asset, + subset_name=subset + ) # Set prev start / end frames for comparison if not prev_start and not prev_end: @@ -1021,7 +1022,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): based on 'publish' template """ if not version: - version = pype.api.get_latest_version(asset, subset) + version = openpype.api.get_latest_version(asset, subset) if version: version = int(version["name"]) + 1 else: @@ -1048,4 +1049,4 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Directory publish_folder = os.path.dirname(file_path) - return publish_folder \ No newline at end of file + return publish_folder diff --git a/pype/modules/deadline/plugins/publish/validate_deadline_connection.py b/openpype/modules/deadline/plugins/publish/validate_deadline_connection.py similarity index 90% rename from pype/modules/deadline/plugins/publish/validate_deadline_connection.py rename to openpype/modules/deadline/plugins/publish/validate_deadline_connection.py index 1c49e68ee1..9b10619c0b 100644 --- a/pype/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/openpype/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -1,7 +1,7 @@ import pyblish.api from avalon.vendor import requests -from pype.plugin import contextplugin_should_run +from openpype.plugin import contextplugin_should_run import os @@ -44,5 +44,5 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): of defense SSL is providing and it is not recommended. """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa return requests.get(*args, **kwargs) diff --git a/pype/modules/ftrack/__init__.py b/openpype/modules/ftrack/__init__.py similarity index 100% rename from pype/modules/ftrack/__init__.py rename to openpype/modules/ftrack/__init__.py diff --git a/pype/modules/ftrack/event_handlers_server/action_clone_review_session.py b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py similarity index 98% rename from pype/modules/ftrack/event_handlers_server/action_clone_review_session.py rename to openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py index 9af04b45c1..d29316c795 100644 --- a/pype/modules/ftrack/event_handlers_server/action_clone_review_session.py +++ b/openpype/modules/ftrack/event_handlers_server/action_clone_review_session.py @@ -1,6 +1,6 @@ import json -from pype.modules.ftrack.lib import ServerAction +from openpype.modules.ftrack.lib import ServerAction def clone_review_session(session, entity): diff --git a/pype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py rename to openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py index 6df27682e0..bc6a58624a 100644 --- a/pype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/action_push_frame_values_to_task.py @@ -1,7 +1,7 @@ import json import collections import ftrack_api -from pype.modules.ftrack.lib import ServerAction +from openpype.modules.ftrack.lib import ServerAction class PushHierValuesToNonHier(ServerAction): @@ -38,7 +38,7 @@ class PushHierValuesToNonHier(ServerAction): """ identifier = "admin.push_hier_values_to_non_hier" - label = "Pype Admin" + label = "OpenPype Admin" variant = "- Push Hierarchical values To Non-Hierarchical" hierarchy_entities_query = ( diff --git a/pype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py similarity index 97% rename from pype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py rename to openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py index 6bec1fb259..8f78f998ac 100644 --- a/pype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/action_sync_to_avalon.py @@ -1,8 +1,8 @@ import time import traceback -from pype.modules.ftrack.lib import ServerAction -from pype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory +from openpype.modules.ftrack.lib import ServerAction +from openpype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory class SyncToAvalonServer(ServerAction): @@ -32,7 +32,7 @@ class SyncToAvalonServer(ServerAction): #: Action identifier. identifier = "sync.to.avalon.server" #: Action label. - label = "Pype Admin" + label = "OpenPype Admin" variant = "- Sync To Avalon (Server)" #: Action description. description = "Send data from Ftrack to Avalon" diff --git a/pype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py b/openpype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py similarity index 88% rename from pype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py rename to openpype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py index b30d21e05a..078596cc2e 100644 --- a/pype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py +++ b/openpype/modules/ftrack/event_handlers_server/event_del_avalon_id_from_new.py @@ -1,6 +1,6 @@ -from pype.modules.ftrack.lib import BaseEvent -from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY -from pype.modules.ftrack.event_handlers_server.event_sync_to_avalon import ( +from openpype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from openpype.modules.ftrack.event_handlers_server.event_sync_to_avalon import ( SyncToAvalonEvent ) diff --git a/pype/modules/ftrack/event_handlers_server/event_first_version_status.py b/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_first_version_status.py rename to openpype/modules/ftrack/event_handlers_server/event_first_version_status.py index 440b455edf..511f62a207 100644 --- a/pype/modules/ftrack/event_handlers_server/event_first_version_status.py +++ b/openpype/modules/ftrack/event_handlers_server/event_first_version_status.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class FirstVersionStatus(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_next_task_update.py b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_next_task_update.py rename to openpype/modules/ftrack/event_handlers_server/event_next_task_update.py index e546f00c77..ad62beb296 100644 --- a/pype/modules/ftrack/event_handlers_server/event_next_task_update.py +++ b/openpype/modules/ftrack/event_handlers_server/event_next_task_update.py @@ -1,5 +1,5 @@ import collections -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class NextTaskUpdate(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py rename to openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py index 338866ba5b..c0b3137455 100644 --- a/pype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py +++ b/openpype/modules/ftrack/event_handlers_server/event_push_frame_values_to_task.py @@ -2,7 +2,7 @@ import collections import datetime import ftrack_api -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class PushFrameValuesToTaskEvent(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_radio_buttons.py b/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py similarity index 96% rename from pype/modules/ftrack/event_handlers_server/event_radio_buttons.py rename to openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py index 90811e5f45..1ebd7b68d2 100644 --- a/pype/modules/ftrack/event_handlers_server/event_radio_buttons.py +++ b/openpype/modules/ftrack/event_handlers_server/event_radio_buttons.py @@ -1,5 +1,5 @@ import ftrack_api -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class RadioButtons(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py rename to openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py index be3a15b049..347b227dd3 100644 --- a/pype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_server/event_sync_to_avalon.py @@ -17,11 +17,11 @@ import ftrack_api from avalon import schema from avalon.api import AvalonMongoDB -from pype.modules.ftrack.lib import ( +from openpype.modules.ftrack.lib import ( avalon_sync, BaseEvent ) -from pype.modules.ftrack.lib.avalon_sync import ( +from openpype.modules.ftrack.lib.avalon_sync import ( CUST_ATTR_ID_KEY, CUST_ATTR_AUTO_SYNC, EntitySchemas diff --git a/pype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py b/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py rename to openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py index 72b6675404..4192a4bed0 100644 --- a/pype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py +++ b/openpype/modules/ftrack/event_handlers_server/event_task_to_parent_status.py @@ -1,5 +1,5 @@ import collections -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class TaskStatusToParent(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_task_to_version_status.py b/openpype/modules/ftrack/event_handlers_server/event_task_to_version_status.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_task_to_version_status.py rename to openpype/modules/ftrack/event_handlers_server/event_task_to_version_status.py index 14f456831f..f2d3723021 100644 --- a/pype/modules/ftrack/event_handlers_server/event_task_to_version_status.py +++ b/openpype/modules/ftrack/event_handlers_server/event_task_to_version_status.py @@ -1,5 +1,5 @@ import collections -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class TaskToVersionStatus(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py b/openpype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py rename to openpype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py index de189463d0..cbeeeee5c5 100644 --- a/pype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py +++ b/openpype/modules/ftrack/event_handlers_server/event_thumbnail_updates.py @@ -1,5 +1,5 @@ import collections -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class ThumbnailEvents(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_server/event_user_assigment.py b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py similarity index 97% rename from pype/modules/ftrack/event_handlers_server/event_user_assigment.py rename to openpype/modules/ftrack/event_handlers_server/event_user_assigment.py index 85cf6db12b..a0734e14a1 100644 --- a/pype/modules/ftrack/event_handlers_server/event_user_assigment.py +++ b/openpype/modules/ftrack/event_handlers_server/event_user_assigment.py @@ -2,13 +2,13 @@ import os import re import subprocess -from pype.modules.ftrack.lib import BaseEvent -from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from openpype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from avalon.api import AvalonMongoDB from bson.objectid import ObjectId -from pype.api import Anatomy, get_project_settings +from openpype.api import Anatomy, get_project_settings class UserAssigmentEvent(BaseEvent): @@ -148,7 +148,7 @@ class UserAssigmentEvent(BaseEvent): """ Get data to fill template from task - .. seealso:: :mod:`pype.api.Anatomy` + .. seealso:: :mod:`openpype.api.Anatomy` :param task: Task entity :type task: dict diff --git a/pype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py similarity index 99% rename from pype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py rename to openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py index 58caf7db51..d20e2ff5a8 100644 --- a/pype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py +++ b/openpype/modules/ftrack/event_handlers_server/event_version_to_task_statuses.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseEvent +from openpype.modules.ftrack.lib import BaseEvent class VersionToTaskStatus(BaseEvent): diff --git a/pype/modules/ftrack/event_handlers_user/action_applications.py b/openpype/modules/ftrack/event_handlers_user/action_applications.py similarity index 97% rename from pype/modules/ftrack/event_handlers_user/action_applications.py rename to openpype/modules/ftrack/event_handlers_user/action_applications.py index 5b6657793a..23c96e1b9f 100644 --- a/pype/modules/ftrack/event_handlers_user/action_applications.py +++ b/openpype/modules/ftrack/event_handlers_user/action_applications.py @@ -1,8 +1,8 @@ import os from uuid import uuid4 -from pype.modules.ftrack.lib import BaseAction -from pype.lib import ( +from openpype.modules.ftrack.lib import BaseAction +from openpype.lib import ( ApplicationManager, ApplicationLaunchFailed, ApplictionExecutableNotFound @@ -26,7 +26,7 @@ class AppplicationsAction(BaseAction): type = "Application" label = "Application action" identifier = "pype_app.{}.".format(str(uuid4())) - icon_url = os.environ.get("PYPE_STATICS_SERVER") + icon_url = os.environ.get("OPENPYPE_STATICS_SERVER") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -133,8 +133,8 @@ class AppplicationsAction(BaseAction): app_icon = None items.append({ - "label": app.label, - "variant": app.variant_label, + "label": app.group.label, + "variant": app.label, "description": None, "actionIdentifier": self.identifier + app_name, "icon": app_icon diff --git a/pype/modules/ftrack/event_handlers_user/action_batch_task_creation.py b/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py similarity index 94% rename from pype/modules/ftrack/event_handlers_user/action_batch_task_creation.py rename to openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py index 477971773d..b9f0e7c5d3 100644 --- a/pype/modules/ftrack/event_handlers_user/action_batch_task_creation.py +++ b/openpype/modules/ftrack/event_handlers_user/action_batch_task_creation.py @@ -2,7 +2,7 @@ Taken from https://github.com/tokejepsen/ftrack-hooks/tree/master/batch_tasks """ -from pype.modules.ftrack.lib import BaseAction +from openpype.modules.ftrack.lib import BaseAction, statics_icon class BatchTasksAction(BaseAction): @@ -13,10 +13,11 @@ class BatchTasksAction(BaseAction): `identifier` a unique identifier for your action. `description` a verbose descriptive text for you action ''' - label = "Batch Tasks" + label = "Batch Task Create" variant = None identifier = "batch-tasks" description = None + icon = statics_icon("ftrack", "action_icons", "BatchTasks.svg") def discover(self, session, entities, event): '''Return true if we can handle the selected entities. @@ -29,11 +30,13 @@ class BatchTasksAction(BaseAction): or Asset Build. *event* the unmodified original event ''' - # Only discover the action if any selection is made. - if entities: - return True - return False + not_allowed = ["assetversion", "project", "ReviewSession"] + if entities[0].entity_type.lower() in not_allowed: + return False + + return True + def get_task_form_items(self, session, number_of_tasks): items = [] diff --git a/pype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py b/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py similarity index 93% rename from pype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py rename to openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py index f9824ec8ea..c326c56a7c 100644 --- a/pype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py +++ b/openpype/modules/ftrack/event_handlers_user/action_clean_hierarchical_attributes.py @@ -1,15 +1,15 @@ import collections import ftrack_api -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import get_pype_attr +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib.avalon_sync import get_pype_attr class CleanHierarchicalAttrsAction(BaseAction): identifier = "clean.hierarchical.attr" - label = "Pype Admin" + label = "OpenPype Admin" variant = "- Clean hierarchical custom attributes" description = "Unset empty hierarchical attribute values." - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") all_project_entities_query = ( "select id, name, parent_id, link" diff --git a/pype/modules/ftrack/event_handlers_user/action_client_review_sort.py b/openpype/modules/ftrack/event_handlers_user/action_client_review_sort.py similarity index 94% rename from pype/modules/ftrack/event_handlers_user/action_client_review_sort.py rename to openpype/modules/ftrack/event_handlers_user/action_client_review_sort.py index 1c5c429cf2..7c9a2881d6 100644 --- a/pype/modules/ftrack/event_handlers_user/action_client_review_sort.py +++ b/openpype/modules/ftrack/event_handlers_user/action_client_review_sort.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseAction +from openpype.modules.ftrack.lib import BaseAction, statics_icon try: from functools import cmp_to_key except Exception: @@ -50,6 +50,8 @@ class ClientReviewSort(BaseAction): #: Action label. label = 'Sort Review' + icon = statics_icon("ftrack", "action_icons", "SortReview.svg") + def discover(self, session, entities, event): ''' Validation ''' diff --git a/pype/modules/ftrack/event_handlers_user/action_component_open.py b/openpype/modules/ftrack/event_handlers_user/action_component_open.py similarity index 96% rename from pype/modules/ftrack/event_handlers_user/action_component_open.py rename to openpype/modules/ftrack/event_handlers_user/action_component_open.py index 2928f54b15..b3cdac0722 100644 --- a/pype/modules/ftrack/event_handlers_user/action_component_open.py +++ b/openpype/modules/ftrack/event_handlers_user/action_component_open.py @@ -1,7 +1,7 @@ import os import sys import subprocess -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class ComponentOpen(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py rename to openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py index 8ff0cade7b..0ebd221e9f 100644 --- a/pype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_cust_attrs.py @@ -2,12 +2,12 @@ import collections import json import arrow import ftrack_api -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import ( +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib.avalon_sync import ( CUST_ATTR_ID_KEY, CUST_ATTR_GROUP, default_custom_attributes_definition ) -from pype.api import get_system_settings -from pype.lib import ApplicationManager +from openpype.api import get_system_settings +from openpype.lib import ApplicationManager """ This action creates/updates custom attributes. @@ -17,7 +17,7 @@ This action creates/updates custom attributes. - `tools` based on tools usages ## Second part is based on json file in ftrack module. -File location: `~/pype/pype/modules/ftrack/ftrack_custom_attributes.json` +File location: `~/OpenPype/pype/modules/ftrack/ftrack_custom_attributes.json` Data in json file is nested dictionary. Keys in first dictionary level represents Ftrack entity type (task, show, assetversion, user, list, asset) @@ -33,7 +33,7 @@ dictionary level, task's attributes are nested more. group (string) - name of group - - based on attribute `pype.modules.ftrack.lib.CUST_ATTR_GROUP` + - based on attribute `openpype.modules.ftrack.lib.CUST_ATTR_GROUP` - "pype" by default *** Required *************************************************************** @@ -127,11 +127,11 @@ class CustomAttributes(BaseAction): #: Action identifier. identifier = 'create.update.attributes' #: Action label. - label = "Pype Admin" + label = "OpenPype Admin" variant = '- Create/Update Avalon Attributes' #: Action description. description = 'Creates Avalon/Mongo ID for double check' - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "create_update_attributes" required_keys = ("key", "label", "type") diff --git a/pype/modules/ftrack/event_handlers_user/action_create_folders.py b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_create_folders.py rename to openpype/modules/ftrack/event_handlers_user/action_create_folders.py index d70232ae8f..075b8d3d25 100644 --- a/pype/modules/ftrack/event_handlers_user/action_create_folders.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_folders.py @@ -1,11 +1,11 @@ import os -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon from avalon import lib as avalonlib -from pype.api import ( +from openpype.api import ( Anatomy, get_project_settings ) -from pype.lib import ApplicationManager +from openpype.lib import ApplicationManager class CreateFolders(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_create_project_structure.py b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_create_project_structure.py rename to openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py index f42f952314..d7ac866e42 100644 --- a/pype/modules/ftrack/event_handlers_user/action_create_project_structure.py +++ b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py @@ -1,8 +1,8 @@ import os import re -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.api import Anatomy, get_project_settings +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.api import Anatomy, get_project_settings class CreateProjectFolders(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_delete_asset.py b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py similarity index 99% rename from pype/modules/ftrack/event_handlers_user/action_delete_asset.py rename to openpype/modules/ftrack/event_handlers_user/action_delete_asset.py index 3bdbbe2470..ff39db4383 100644 --- a/pype/modules/ftrack/event_handlers_user/action_delete_asset.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_asset.py @@ -4,7 +4,7 @@ from datetime import datetime from queue import Queue from bson.objectid import ObjectId -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon from avalon.api import AvalonMongoDB diff --git a/pype/modules/ftrack/event_handlers_user/action_delete_old_versions.py b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_delete_old_versions.py rename to openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py index e1c1e173a3..dbddc7a95e 100644 --- a/pype/modules/ftrack/event_handlers_user/action_delete_old_versions.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delete_old_versions.py @@ -5,9 +5,9 @@ import uuid import clique from pymongo import UpdateOne -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon from avalon.api import AvalonMongoDB -from pype.api import Anatomy +from openpype.api import Anatomy import avalon.pipeline @@ -15,13 +15,13 @@ import avalon.pipeline class DeleteOldVersions(BaseAction): identifier = "delete.old.versions" - label = "Pype Admin" + label = "OpenPype Admin" variant = "- Delete old versions" description = ( "Delete files from older publishes so project can be" " archived with only lates versions." ) - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") dbcon = AvalonMongoDB() diff --git a/pype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py similarity index 99% rename from pype/modules/ftrack/event_handlers_user/action_delivery.py rename to openpype/modules/ftrack/event_handlers_user/action_delivery.py index e9e939bb47..88fdbe3669 100644 --- a/pype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -10,9 +10,9 @@ from bson.objectid import ObjectId from avalon import pipeline from avalon.vendor import filelink -from pype.api import Anatomy, config -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from openpype.api import Anatomy, config +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY from avalon.api import AvalonMongoDB diff --git a/pype/modules/ftrack/event_handlers_user/action_djvview.py b/openpype/modules/ftrack/event_handlers_user/action_djvview.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_djvview.py rename to openpype/modules/ftrack/event_handlers_user/action_djvview.py index dfaa1ebeb9..c05fbed2d0 100644 --- a/pype/modules/ftrack/event_handlers_user/action_djvview.py +++ b/openpype/modules/ftrack/event_handlers_user/action_djvview.py @@ -1,7 +1,7 @@ import os import subprocess from operator import itemgetter -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class DJVViewAction(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_job_killer.py b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py similarity index 95% rename from pype/modules/ftrack/event_handlers_user/action_job_killer.py rename to openpype/modules/ftrack/event_handlers_user/action_job_killer.py index 1ddd1383a7..47ed1e7895 100644 --- a/pype/modules/ftrack/event_handlers_user/action_job_killer.py +++ b/openpype/modules/ftrack/event_handlers_user/action_job_killer.py @@ -1,5 +1,5 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class JobKiller(BaseAction): @@ -8,12 +8,12 @@ class JobKiller(BaseAction): #: Action identifier. identifier = 'job.killer' #: Action label. - label = "Pype Admin" + label = "OpenPype Admin" variant = '- Job Killer' #: Action description. description = 'Killing selected running jobs' #: roles that are allowed to register this action - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "job_killer" def discover(self, session, entities, event): diff --git a/pype/modules/ftrack/event_handlers_user/action_multiple_notes.py b/openpype/modules/ftrack/event_handlers_user/action_multiple_notes.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_multiple_notes.py rename to openpype/modules/ftrack/event_handlers_user/action_multiple_notes.py index d88a91dd92..8db65fe39b 100644 --- a/pype/modules/ftrack/event_handlers_user/action_multiple_notes.py +++ b/openpype/modules/ftrack/event_handlers_user/action_multiple_notes.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class MultipleNotes(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_prepare_project.py b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py similarity index 98% rename from pype/modules/ftrack/event_handlers_user/action_prepare_project.py rename to openpype/modules/ftrack/event_handlers_user/action_prepare_project.py index 3a955067d8..7f674310fc 100644 --- a/pype/modules/ftrack/event_handlers_user/action_prepare_project.py +++ b/openpype/modules/ftrack/event_handlers_user/action_prepare_project.py @@ -1,9 +1,9 @@ import os import json -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.api import config, Anatomy -from pype.modules.ftrack.lib.avalon_sync import get_pype_attr +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.api import config, Anatomy +from openpype.modules.ftrack.lib.avalon_sync import get_pype_attr class PrepareProject(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_rv.py b/openpype/modules/ftrack/event_handlers_user/action_rv.py similarity index 99% rename from pype/modules/ftrack/event_handlers_user/action_rv.py rename to openpype/modules/ftrack/event_handlers_user/action_rv.py index 1c5ccfaed0..3172b74261 100644 --- a/pype/modules/ftrack/event_handlers_user/action_rv.py +++ b/openpype/modules/ftrack/event_handlers_user/action_rv.py @@ -3,7 +3,7 @@ import subprocess import traceback import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon import ftrack_api from avalon import io, api diff --git a/pype/modules/ftrack/event_handlers_user/action_seed.py b/openpype/modules/ftrack/event_handlers_user/action_seed.py similarity index 99% rename from pype/modules/ftrack/event_handlers_user/action_seed.py rename to openpype/modules/ftrack/event_handlers_user/action_seed.py index 549afc660c..1f01f0af1d 100644 --- a/pype/modules/ftrack/event_handlers_user/action_seed.py +++ b/openpype/modules/ftrack/event_handlers_user/action_seed.py @@ -1,6 +1,6 @@ import os from operator import itemgetter -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class SeedDebugProject(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py similarity index 97% rename from pype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py rename to openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py index 4fbea6b8a5..4464e51d3d 100644 --- a/pype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_store_thumbnails_to_avalon.py @@ -4,24 +4,24 @@ import json import requests from bson.objectid import ObjectId -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.api import Anatomy +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.api import Anatomy from avalon.api import AvalonMongoDB -from pype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY +from openpype.modules.ftrack.lib.avalon_sync import CUST_ATTR_ID_KEY class StoreThumbnailsToAvalon(BaseAction): # Action identifier identifier = "store.thubmnail.to.avalon" # Action label - label = "Pype Admin" + label = "OpenPype Admin" # Action variant variant = "- Store Thumbnails to avalon" # Action description description = 'Test action' # roles that are allowed to register this action - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "store_thubmnail_to_avalon" thumbnail_key = "AVALON_THUMBNAIL_ROOT" @@ -274,7 +274,7 @@ class StoreThumbnailsToAvalon(BaseAction): thumbnail_entity = { "_id": thumbnail_id, "type": "thumbnail", - "schema": "pype:thumbnail-1.0", + "schema": "openpype:thumbnail-1.0", "data": { "template": thumbnail_template, "template_data": template_data diff --git a/pype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py similarity index 96% rename from pype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py rename to openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py index b86b469d1c..89fac7cf80 100644 --- a/pype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py +++ b/openpype/modules/ftrack/event_handlers_user/action_sync_to_avalon.py @@ -1,8 +1,8 @@ import time import traceback -from pype.modules.ftrack.lib import BaseAction, statics_icon -from pype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory +from openpype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory class SyncToAvalonLocal(BaseAction): @@ -33,7 +33,7 @@ class SyncToAvalonLocal(BaseAction): #: Action identifier. identifier = "sync.to.avalon.local" #: Action label. - label = "Pype Admin" + label = "OpenPype Admin" #: Action variant variant = "- Sync To Avalon (Local)" #: Action description. @@ -41,7 +41,7 @@ class SyncToAvalonLocal(BaseAction): #: priority priority = 200 #: roles that are allowed to register this action - icon = statics_icon("ftrack", "action_icons", "PypeAdmin.svg") + icon = statics_icon("ftrack", "action_icons", "OpenPypeAdmin.svg") settings_key = "sync_to_avalon_local" diff --git a/pype/modules/ftrack/event_handlers_user/action_test.py b/openpype/modules/ftrack/event_handlers_user/action_test.py similarity index 89% rename from pype/modules/ftrack/event_handlers_user/action_test.py rename to openpype/modules/ftrack/event_handlers_user/action_test.py index c12906e340..206c67de50 100644 --- a/pype/modules/ftrack/event_handlers_user/action_test.py +++ b/openpype/modules/ftrack/event_handlers_user/action_test.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class TestAction(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py b/openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py similarity index 96% rename from pype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py rename to openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py index b90dfa027c..a12f25b57d 100644 --- a/pype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py +++ b/openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_childern.py @@ -1,5 +1,5 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class ThumbToChildren(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py b/openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py similarity index 97% rename from pype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py rename to openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py index 5734ea6abc..284723bb0f 100644 --- a/pype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py +++ b/openpype/modules/ftrack/event_handlers_user/action_thumbnail_to_parent.py @@ -1,5 +1,5 @@ import json -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class ThumbToParent(BaseAction): diff --git a/pype/modules/ftrack/event_handlers_user/action_where_run_ask.py b/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py similarity index 87% rename from pype/modules/ftrack/event_handlers_user/action_where_run_ask.py rename to openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py index 64957208da..6950d45ecd 100644 --- a/pype/modules/ftrack/event_handlers_user/action_where_run_ask.py +++ b/openpype/modules/ftrack/event_handlers_user/action_where_run_ask.py @@ -1,4 +1,4 @@ -from pype.modules.ftrack.lib import BaseAction, statics_icon +from openpype.modules.ftrack.lib import BaseAction, statics_icon class ActionAskWhereIRun(BaseAction): @@ -8,7 +8,7 @@ class ActionAskWhereIRun(BaseAction): ignore_me = True identifier = 'ask.where.i.run' label = 'Ask where I run' - description = 'Triggers PC info where user have running Pype' + description = 'Triggers PC info where user have running OpenPype' icon = statics_icon("ftrack", "action_icons", "ActionAskWhereIRun.svg") def discover(self, session, entities, event): diff --git a/pype/modules/ftrack/event_handlers_user/action_where_run_show.py b/openpype/modules/ftrack/event_handlers_user/action_where_run_show.py similarity index 95% rename from pype/modules/ftrack/event_handlers_user/action_where_run_show.py rename to openpype/modules/ftrack/event_handlers_user/action_where_run_show.py index f872d17d27..4ce1a439a3 100644 --- a/pype/modules/ftrack/event_handlers_user/action_where_run_show.py +++ b/openpype/modules/ftrack/event_handlers_user/action_where_run_show.py @@ -1,7 +1,7 @@ import platform import socket import getpass -from pype.modules.ftrack.lib import BaseAction +from openpype.modules.ftrack.lib import BaseAction class ActionShowWhereIRun(BaseAction): @@ -15,7 +15,7 @@ class ActionShowWhereIRun(BaseAction): #: Action label. label = 'Show where I run' #: Action description. - description = 'Shows PC info where user have running Pype' + description = 'Shows PC info where user have running OpenPype' def discover(self, session, entities, event): """ Hide by default - Should be enabled only if you want to run. diff --git a/pype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py similarity index 98% rename from pype/modules/ftrack/ftrack_module.py rename to openpype/modules/ftrack/ftrack_module.py index 7b9d42f6df..8a40cac91a 100644 --- a/pype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -2,8 +2,8 @@ import os import collections from abc import ABCMeta, abstractmethod import six -import pype -from pype.modules import ( +import openpype +from openpype.modules import ( PypeModule, ITrayModule, IPluginPaths, @@ -140,7 +140,7 @@ class FtrackModule( return import ftrack_api - from pype.modules.ftrack.lib import avalon_sync + from openpype.modules.ftrack.lib import avalon_sync session = self.create_ftrack_session() project_entity = session.query( diff --git a/pype/modules/ftrack/ftrack_server/__init__.py b/openpype/modules/ftrack/ftrack_server/__init__.py similarity index 100% rename from pype/modules/ftrack/ftrack_server/__init__.py rename to openpype/modules/ftrack/ftrack_server/__init__.py diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py similarity index 98% rename from pype/modules/ftrack/ftrack_server/event_server_cli.py rename to openpype/modules/ftrack/ftrack_server/event_server_cli.py index c70a12aefb..b5cc1bef3e 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -14,21 +14,21 @@ import uuid import ftrack_api import pymongo -from pype.lib import ( +from openpype.lib import ( get_pype_execute_args, - PypeMongoConnection + OpenPypeMongoConnection ) -from pype.modules.ftrack import FTRACK_MODULE_DIR -from pype.modules.ftrack.lib import ( +from openpype.modules.ftrack import FTRACK_MODULE_DIR +from openpype.modules.ftrack.lib import ( credentials, get_ftrack_url_from_settings ) -from pype.modules.ftrack.ftrack_server.lib import ( +from openpype.modules.ftrack.ftrack_server.lib import ( check_ftrack_url, get_ftrack_event_mongo_info ) -from pype.modules.ftrack.ftrack_server import socket_thread +from openpype.modules.ftrack.ftrack_server import socket_thread class MongoPermissionsError(Exception): @@ -181,7 +181,7 @@ def main_loop(ftrack_url): os.environ["FTRACK_EVENT_SUB_ID"] = str(uuid.uuid1()) - mongo_uri = PypeMongoConnection.get_default_mongo_url() + mongo_uri = OpenPypeMongoConnection.get_default_mongo_url() # Current file scripts_dir = os.path.join(FTRACK_MODULE_DIR, "scripts") diff --git a/pype/modules/ftrack/ftrack_server/ftrack_server.py b/openpype/modules/ftrack/ftrack_server/ftrack_server.py similarity index 99% rename from pype/modules/ftrack/ftrack_server/ftrack_server.py rename to openpype/modules/ftrack/ftrack_server/ftrack_server.py index 1612a2f474..285ca29dc5 100644 --- a/pype/modules/ftrack/ftrack_server/ftrack_server.py +++ b/openpype/modules/ftrack/ftrack_server/ftrack_server.py @@ -5,7 +5,7 @@ import traceback import ftrack_api -from pype.lib import ( +from openpype.lib import ( PypeLogger, modules_from_path ) diff --git a/pype/modules/ftrack/ftrack_server/lib.py b/openpype/modules/ftrack/ftrack_server/lib.py similarity index 96% rename from pype/modules/ftrack/ftrack_server/lib.py rename to openpype/modules/ftrack/ftrack_server/lib.py index 0b92f6486a..91f3712136 100644 --- a/pype/modules/ftrack/ftrack_server/lib.py +++ b/openpype/modules/ftrack/ftrack_server/lib.py @@ -22,13 +22,13 @@ try: from weakref import WeakMethod except ImportError: from ftrack_api._weakref import WeakMethod -from pype.modules.ftrack.lib import get_ftrack_event_mongo_info +from openpype.modules.ftrack.lib import get_ftrack_event_mongo_info -from pype.lib import PypeMongoConnection -from pype.api import Logger +from openpype.lib import OpenPypeMongoConnection +from openpype.api import Logger -TOPIC_STATUS_SERVER = "pype.event.server.status" -TOPIC_STATUS_SERVER_RESULT = "pype.event.server.status.result" +TOPIC_STATUS_SERVER = "openpype.event.server.status" +TOPIC_STATUS_SERVER_RESULT = "openpype.event.server.status.result" def check_ftrack_url(url, log_errors=True): @@ -92,7 +92,7 @@ class StatusEventHub(SocketBaseEventHub): code_name = self._code_name_mapping[code] if code_name == "connect": event = ftrack_api.event.base.Event( - topic="pype.status.started", + topic="openpype.status.started", data={}, source={ "id": self.id, @@ -115,7 +115,7 @@ class StorerEventHub(SocketBaseEventHub): code_name = self._code_name_mapping[code] if code_name == "connect": event = ftrack_api.event.base.Event( - topic="pype.storer.started", + topic="openpype.storer.started", data={}, source={ "id": self.id, @@ -144,14 +144,14 @@ class ProcessEventHub(SocketBaseEventHub): def prepare_dbcon(self): try: database_name, collection_name = get_ftrack_event_mongo_info() - mongo_client = PypeMongoConnection.get_mongo_client() + mongo_client = OpenPypeMongoConnection.get_mongo_client() self.dbcon = mongo_client[database_name][collection_name] self.mongo_client = mongo_client except pymongo.errors.AutoReconnect: self.pypelog.error(( "Mongo server \"{}\" is not responding, exiting." - ).format(PypeMongoConnection.get_default_mongo_url())) + ).format(OpenPypeMongoConnection.get_default_mongo_url())) sys.exit(0) except pymongo.errors.OperationFailure: diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/openpype/modules/ftrack/ftrack_server/socket_thread.py similarity index 95% rename from pype/modules/ftrack/ftrack_server/socket_thread.py rename to openpype/modules/ftrack/ftrack_server/socket_thread.py index a895e0b900..fd407bb9f5 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/openpype/modules/ftrack/ftrack_server/socket_thread.py @@ -5,14 +5,14 @@ import socket import threading import traceback import subprocess -from pype.api import Logger -from pype.lib import get_pype_execute_args +from openpype.api import Logger +from openpype.lib import get_pype_execute_args class SocketThread(threading.Thread): """Thread that checks suprocess of storer of processor of events""" - MAX_TIMEOUT = int(os.environ.get("PYPE_FTRACK_SOCKET_TIMEOUT", 45)) + MAX_TIMEOUT = int(os.environ.get("OPENPYPE_FTRACK_SOCKET_TIMEOUT", 45)) def __init__(self, name, port, filepath, additional_args=[]): super(SocketThread, self).__init__() @@ -57,8 +57,8 @@ class SocketThread(threading.Thread): ) env = os.environ.copy() - env["PYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) - # Pype executable (with path to start script if not build) + env["OPENPYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) + # OpenPype executable (with path to start script if not build) args = get_pype_execute_args( # Add `run` command "run", diff --git a/pype/modules/ftrack/launch_hooks/post_ftrack_changes.py b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py similarity index 98% rename from pype/modules/ftrack/launch_hooks/post_ftrack_changes.py rename to openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py index ac6b0a2c5d..df16cde2b8 100644 --- a/pype/modules/ftrack/launch_hooks/post_ftrack_changes.py +++ b/openpype/modules/ftrack/launch_hooks/post_ftrack_changes.py @@ -1,8 +1,8 @@ import os import ftrack_api -from pype.api import get_project_settings -from pype.lib import PostLaunchHook +from openpype.api import get_project_settings +from openpype.lib import PostLaunchHook class PostFtrackHook(PostLaunchHook): diff --git a/pype/modules/ftrack/launch_hooks/pre_python2_vendor.py b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py similarity index 85% rename from pype/modules/ftrack/launch_hooks/pre_python2_vendor.py rename to openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py index 46b4009737..7826d833ac 100644 --- a/pype/modules/ftrack/launch_hooks/pre_python2_vendor.py +++ b/openpype/modules/ftrack/launch_hooks/pre_python2_vendor.py @@ -1,9 +1,9 @@ import os -from pype.lib import PreLaunchHook -from pype.modules.ftrack import FTRACK_MODULE_DIR +from openpype.lib import PreLaunchHook +from openpype.modules.ftrack import FTRACK_MODULE_DIR -class PrePyhton2Support(PreLaunchHook): +class PrePython2Support(PreLaunchHook): """Add python ftrack api module for Python 2 to PYTHONPATH. Path to vendor modules is added to the beggining of PYTHONPATH. @@ -23,7 +23,7 @@ class PrePyhton2Support(PreLaunchHook): os.path.join(python_2_vendor, "arrow"), # `builtins` from `python-future` # - `python-future` is strict Python 2 module that cause crashes - # of Python 3 scripts executed through pype (burnin script etc.) + # of Python 3 scripts executed through OpenPype (burnin script etc.) os.path.join(python_2_vendor, "builtins"), # `backports.functools_lru_cache` os.path.join( diff --git a/pype/modules/ftrack/lib/__init__.py b/openpype/modules/ftrack/lib/__init__.py similarity index 100% rename from pype/modules/ftrack/lib/__init__.py rename to openpype/modules/ftrack/lib/__init__.py diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py similarity index 99% rename from pype/modules/ftrack/lib/avalon_sync.py rename to openpype/modules/ftrack/lib/avalon_sync.py index 970a270290..7511c2627b 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -14,7 +14,7 @@ else: from avalon.api import AvalonMongoDB import avalon -from pype.api import ( +from openpype.api import ( Logger, Anatomy, get_anatomy_settings @@ -24,20 +24,20 @@ from bson.objectid import ObjectId from bson.errors import InvalidId from pymongo import UpdateOne import ftrack_api -from pype.lib import ApplicationManager +from openpype.lib import ApplicationManager log = Logger.get_logger(__name__) # Current schemas for avalon types EntitySchemas = { - "project": "pype:project-2.1", - "asset": "pype:asset-3.0", - "config": "pype:config-1.1" + "project": "openpype:project-3.0", + "asset": "openpype:asset-3.0", + "config": "openpype:config-2.0" } # Group name of custom attributes -CUST_ATTR_GROUP = "pype" +CUST_ATTR_GROUP = "openpype" # name of Custom attribute that stores mongo_id from avalon db CUST_ATTR_ID_KEY = "avalon_mongo_id" @@ -102,11 +102,12 @@ def get_pype_attr(session, split_hierarchical=True, query_keys=None): "is_hierarchical", "default" ] - # TODO remove deprecated "avalon" group from query + # TODO remove deprecated "pype" group from query cust_attrs_query = ( "select {}" " from CustomAttributeConfiguration" - " where group.name in (\"avalon\", \"{}\")" + # Kept `pype` for Backwards Compatiblity + " where group.name in (\"pype\", \"{}\")" ).format(", ".join(query_keys), CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: diff --git a/pype/modules/ftrack/lib/credentials.py b/openpype/modules/ftrack/lib/credentials.py similarity index 100% rename from pype/modules/ftrack/lib/credentials.py rename to openpype/modules/ftrack/lib/credentials.py diff --git a/pype/modules/ftrack/lib/custom_attributes.json b/openpype/modules/ftrack/lib/custom_attributes.json similarity index 100% rename from pype/modules/ftrack/lib/custom_attributes.json rename to openpype/modules/ftrack/lib/custom_attributes.json diff --git a/pype/modules/ftrack/lib/ftrack_action_handler.py b/openpype/modules/ftrack/lib/ftrack_action_handler.py similarity index 99% rename from pype/modules/ftrack/lib/ftrack_action_handler.py rename to openpype/modules/ftrack/lib/ftrack_action_handler.py index d95c81955e..2bff9d8cb3 100644 --- a/pype/modules/ftrack/lib/ftrack_action_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_action_handler.py @@ -3,7 +3,7 @@ from .ftrack_base_handler import BaseHandler def statics_icon(*icon_statics_file_parts): - statics_server = os.environ.get("PYPE_STATICS_SERVER") + statics_server = os.environ.get("OPENPYPE_STATICS_SERVER") if not statics_server: return None return "/".join((statics_server, *icon_statics_file_parts)) diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/openpype/modules/ftrack/lib/ftrack_base_handler.py similarity index 98% rename from pype/modules/ftrack/lib/ftrack_base_handler.py rename to openpype/modules/ftrack/lib/ftrack_base_handler.py index 74c31d1c6f..817841df4a 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/openpype/modules/ftrack/lib/ftrack_base_handler.py @@ -1,10 +1,10 @@ import functools import time -from pype.api import Logger -from pype.settings import get_project_settings +from openpype.api import Logger +from openpype.settings import get_project_settings import ftrack_api -from pype.modules.ftrack import ftrack_server +from openpype.modules.ftrack import ftrack_server class MissingPermision(Exception): @@ -557,7 +557,7 @@ class BaseHandler(object): ).one() def get_project_settings_from_event(self, event, project_name): - """Load or fill pype's project settings from event data. + """Load or fill OpenPype's project settings from event data. Project data are stored by ftrack id because in most cases it is easier to access project id than project name. diff --git a/pype/modules/ftrack/lib/ftrack_event_handler.py b/openpype/modules/ftrack/lib/ftrack_event_handler.py similarity index 100% rename from pype/modules/ftrack/lib/ftrack_event_handler.py rename to openpype/modules/ftrack/lib/ftrack_event_handler.py diff --git a/pype/modules/ftrack/lib/settings.py b/openpype/modules/ftrack/lib/settings.py similarity index 74% rename from pype/modules/ftrack/lib/settings.py rename to openpype/modules/ftrack/lib/settings.py index 46854e8184..f6967411db 100644 --- a/pype/modules/ftrack/lib/settings.py +++ b/openpype/modules/ftrack/lib/settings.py @@ -1,7 +1,5 @@ -from pype.api import get_system_settings - -PYPE_DATABASE_NAME = "pype" - +import os +from openpype.api import get_system_settings def get_ftrack_settings(): return get_system_settings()["modules"]["ftrack"] @@ -13,6 +11,6 @@ def get_ftrack_url_from_settings(): def get_ftrack_event_mongo_info(): ftrack_settings = get_ftrack_settings() - database_name = PYPE_DATABASE_NAME + database_name = os.environ["OPENPYPE_DATABASE_NAME"] collection_name = "ftrack_events" return database_name, collection_name diff --git a/pype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py b/openpype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py similarity index 100% rename from pype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py rename to openpype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py diff --git a/pype/modules/ftrack/plugins/publish/collect_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/collect_ftrack_api.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/collect_ftrack_api.py rename to openpype/modules/ftrack/plugins/publish/collect_ftrack_api.py diff --git a/pype/modules/ftrack/plugins/publish/integrate_ftrack_api.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/integrate_ftrack_api.py rename to openpype/modules/ftrack/plugins/publish/integrate_ftrack_api.py diff --git a/pype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py rename to openpype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py diff --git a/pype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py rename to openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py diff --git a/pype/modules/ftrack/plugins/publish/integrate_ftrack_note.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/integrate_ftrack_note.py rename to openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py diff --git a/pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py similarity index 96% rename from pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py rename to openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py index b4a2760c93..118a73a636 100644 --- a/pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py @@ -4,11 +4,13 @@ import six import pyblish.api from avalon import io -# Copy of constant `pype.modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` +# Copy of constant `openpype.modules.ftrack.lib.avalon_sync.CUST_ATTR_AUTO_SYNC` CUST_ATTR_AUTO_SYNC = "avalon_auto_sync" +CUST_ATTR_GROUP = "openpype" -# Copy of `get_pype_attr` from pype.modules.ftrack.lib +# Copy of `get_pype_attr` from openpype.modules.ftrack.lib +# TODO import from openpype's ftrack module when possible to not break Python 2 def get_pype_attr(session, split_hierarchical=True): custom_attributes = [] hier_custom_attributes = [] @@ -16,8 +18,9 @@ def get_pype_attr(session, split_hierarchical=True): cust_attrs_query = ( "select id, entity_type, object_type_id, is_hierarchical, default" " from CustomAttributeConfiguration" - " where group.name in (\"avalon\", \"pype\")" - ) + # Kept `pype` for Backwards Compatiblity + " where group.name in (\"pype\", \"{}\")" + ).format(CUST_ATTR_GROUP) all_avalon_attr = session.query(cust_attrs_query).all() for cust_attr in all_avalon_attr: if split_hierarchical and cust_attr["is_hierarchical"]: diff --git a/pype/modules/ftrack/plugins/publish/integrate_remove_components.py b/openpype/modules/ftrack/plugins/publish/integrate_remove_components.py similarity index 100% rename from pype/modules/ftrack/plugins/publish/integrate_remove_components.py rename to openpype/modules/ftrack/plugins/publish/integrate_remove_components.py diff --git a/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py b/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py similarity index 98% rename from pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py rename to openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py index 03aa8844fd..dc80bf4eb3 100644 --- a/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py +++ b/openpype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api class ValidateFtrackAttributes(pyblish.api.InstancePlugin): @@ -8,7 +8,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): Attributes to be validated are specified in: - `$PYPE_CONFIG/presets//ftrack_attributes.json` + `$OPENPYPE_CONFIG/presets//ftrack_attributes.json` This is array (list) of checks in format: [ @@ -34,7 +34,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): """ label = "Validate Custom Ftrack Attributes" - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder families = ["ftrack"] optional = True # Ignore standalone host, because it does not have an Ftrack entity diff --git a/openpype/modules/ftrack/python2_vendor/arrow b/openpype/modules/ftrack/python2_vendor/arrow new file mode 160000 index 0000000000..b746fedf72 --- /dev/null +++ b/openpype/modules/ftrack/python2_vendor/arrow @@ -0,0 +1 @@ +Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/__init__.py b/openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/__init__.py similarity index 100% rename from pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/__init__.py rename to openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/__init__.py diff --git a/pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/__init__.py b/openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/__init__.py similarity index 100% rename from pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/__init__.py rename to openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/__init__.py diff --git a/pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/helpers.py b/openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/helpers.py similarity index 100% rename from pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/helpers.py rename to openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/configparser/helpers.py diff --git a/pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/functools_lru_cache.py b/openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/functools_lru_cache.py similarity index 100% rename from pype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/functools_lru_cache.py rename to openpype/modules/ftrack/python2_vendor/backports.functools_lru_cache/backports/functools_lru_cache.py diff --git a/pype/modules/ftrack/python2_vendor/builtins/builtins/__init__.py b/openpype/modules/ftrack/python2_vendor/builtins/builtins/__init__.py similarity index 100% rename from pype/modules/ftrack/python2_vendor/builtins/builtins/__init__.py rename to openpype/modules/ftrack/python2_vendor/builtins/builtins/__init__.py diff --git a/openpype/modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/ftrack/python2_vendor/ftrack-python-api new file mode 160000 index 0000000000..d277f474ab --- /dev/null +++ b/openpype/modules/ftrack/python2_vendor/ftrack-python-api @@ -0,0 +1 @@ +Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e diff --git a/pype/modules/ftrack/scripts/sub_event_processor.py b/openpype/modules/ftrack/scripts/sub_event_processor.py similarity index 90% rename from pype/modules/ftrack/scripts/sub_event_processor.py rename to openpype/modules/ftrack/scripts/sub_event_processor.py index 51d796cea6..0d94fa7264 100644 --- a/pype/modules/ftrack/scripts/sub_event_processor.py +++ b/openpype/modules/ftrack/scripts/sub_event_processor.py @@ -4,15 +4,15 @@ import signal import socket import datetime -from pype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer -from pype.modules.ftrack.ftrack_server.lib import ( +from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer +from openpype.modules.ftrack.ftrack_server.lib import ( SocketSession, ProcessEventHub, TOPIC_STATUS_SERVER ) -from pype.modules import ModulesManager +from openpype.modules import ModulesManager -from pype.api import Logger +from openpype.api import Logger import ftrack_api @@ -46,7 +46,7 @@ def send_status(event): } new_event = ftrack_api.event.base.Event( - topic="pype.event.server.status.result", + topic="openpype.event.server.status.result", data=new_event_data ) diff --git a/pype/modules/ftrack/scripts/sub_event_status.py b/openpype/modules/ftrack/scripts/sub_event_status.py similarity index 97% rename from pype/modules/ftrack/scripts/sub_event_status.py rename to openpype/modules/ftrack/scripts/sub_event_status.py index bb72d9ac15..24b9bfb789 100644 --- a/pype/modules/ftrack/scripts/sub_event_status.py +++ b/openpype/modules/ftrack/scripts/sub_event_status.py @@ -7,14 +7,14 @@ import socket import datetime import ftrack_api -from pype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer -from pype.modules.ftrack.ftrack_server.lib import ( +from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer +from openpype.modules.ftrack.ftrack_server.lib import ( SocketSession, StatusEventHub, TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT ) -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger("Event storer") action_identifier = ( @@ -22,7 +22,7 @@ action_identifier = ( ) host_ip = socket.gethostbyname(socket.gethostname()) action_data = { - "label": "Pype Admin", + "label": "OpenPype Admin", "variant": "- Event server Status ({})".format(host_ip), "description": "Get Infromation about event server", "actionIdentifier": action_identifier @@ -322,7 +322,7 @@ def register(session): "topic=ftrack.action.discover", server_activity_discover ) - session.event_hub.subscribe("topic=pype.status.started", on_start) + session.event_hub.subscribe("topic=openpype.status.started", on_start) status_launch_subscription = ( "topic=ftrack.action.launch and data.actionIdentifier={}" diff --git a/pype/modules/ftrack/scripts/sub_event_storer.py b/openpype/modules/ftrack/scripts/sub_event_storer.py similarity index 91% rename from pype/modules/ftrack/scripts/sub_event_storer.py rename to openpype/modules/ftrack/scripts/sub_event_storer.py index d70ef8bd42..6e2990ef0b 100644 --- a/pype/modules/ftrack/scripts/sub_event_storer.py +++ b/openpype/modules/ftrack/scripts/sub_event_storer.py @@ -6,16 +6,16 @@ import socket import pymongo import ftrack_api -from pype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer -from pype.modules.ftrack.ftrack_server.lib import ( +from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer +from openpype.modules.ftrack.ftrack_server.lib import ( SocketSession, StorerEventHub, TOPIC_STATUS_SERVER, TOPIC_STATUS_SERVER_RESULT ) -from pype.modules.ftrack.lib import get_ftrack_event_mongo_info -from pype.lib import PypeMongoConnection -from pype.api import Logger +from openpype.modules.ftrack.lib import get_ftrack_event_mongo_info +from openpype.lib import OpenPypeMongoConnection +from openpype.api import Logger log = Logger.get_logger("Event storer") subprocess_started = datetime.datetime.now() @@ -35,11 +35,11 @@ ignore_topics = [] def install_db(): global dbcon try: - mongo_client = PypeMongoConnection.get_mongo_client() + mongo_client = OpenPypeMongoConnection.get_mongo_client() dbcon = mongo_client[database_name][collection_name] except pymongo.errors.AutoReconnect: log.error("Mongo server \"{}\" is not responding, exiting.".format( - PypeMongoConnection.get_default_mongo_url() + OpenPypeMongoConnection.get_default_mongo_url() )) sys.exit(0) @@ -170,7 +170,7 @@ def register(session): '''Registers the event, subscribing the discover and launch topics.''' install_db() session.event_hub.subscribe("topic=*", launch) - session.event_hub.subscribe("topic=pype.storer.started", trigger_sync) + session.event_hub.subscribe("topic=openpype.storer.started", trigger_sync) session.event_hub.subscribe( "topic={}".format(TOPIC_STATUS_SERVER), send_status ) diff --git a/pype/modules/ftrack/scripts/sub_legacy_server.py b/openpype/modules/ftrack/scripts/sub_legacy_server.py similarity index 94% rename from pype/modules/ftrack/scripts/sub_legacy_server.py rename to openpype/modules/ftrack/scripts/sub_legacy_server.py index 2e45b564b3..ae6aefa908 100644 --- a/pype/modules/ftrack/scripts/sub_legacy_server.py +++ b/openpype/modules/ftrack/scripts/sub_legacy_server.py @@ -5,9 +5,9 @@ import signal import threading import ftrack_api -from pype.api import Logger -from pype.modules import ModulesManager -from pype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer +from openpype.api import Logger +from openpype.modules import ModulesManager +from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer log = Logger().get_logger("Event Server Legacy") diff --git a/pype/modules/ftrack/scripts/sub_user_server.py b/openpype/modules/ftrack/scripts/sub_user_server.py similarity index 88% rename from pype/modules/ftrack/scripts/sub_user_server.py rename to openpype/modules/ftrack/scripts/sub_user_server.py index 79cd90a0d7..971a31b703 100644 --- a/pype/modules/ftrack/scripts/sub_user_server.py +++ b/openpype/modules/ftrack/scripts/sub_user_server.py @@ -2,14 +2,14 @@ import sys import signal import socket -from pype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer -from pype.modules.ftrack.ftrack_server.lib import ( +from openpype.modules.ftrack.ftrack_server.ftrack_server import FtrackServer +from openpype.modules.ftrack.ftrack_server.lib import ( SocketSession, SocketBaseEventHub ) -from pype.modules import ModulesManager +from openpype.modules import ModulesManager -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger("FtrackUserServer") diff --git a/pype/modules/ftrack/tray/__init__.py b/openpype/modules/ftrack/tray/__init__.py similarity index 100% rename from pype/modules/ftrack/tray/__init__.py rename to openpype/modules/ftrack/tray/__init__.py diff --git a/pype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py similarity index 99% rename from pype/modules/ftrack/tray/ftrack_tray.py rename to openpype/modules/ftrack/tray/ftrack_tray.py index 1009d93afe..9da5db835b 100644 --- a/pype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -11,7 +11,7 @@ from ..lib import credentials from ..ftrack_module import FTRACK_MODULE_DIR from . import login_dialog -from pype.api import Logger, resources +from openpype.api import Logger, resources log = Logger().get_logger("FtrackModule") diff --git a/pype/modules/ftrack/tray/login_dialog.py b/openpype/modules/ftrack/tray/login_dialog.py similarity index 98% rename from pype/modules/ftrack/tray/login_dialog.py rename to openpype/modules/ftrack/tray/login_dialog.py index a49010effc..ca409ebcaa 100644 --- a/pype/modules/ftrack/tray/login_dialog.py +++ b/openpype/modules/ftrack/tray/login_dialog.py @@ -1,9 +1,9 @@ import os import requests from avalon import style -from pype.modules.ftrack.lib import credentials +from openpype.modules.ftrack.lib import credentials from . import login_tools -from pype import resources +from openpype import resources from Qt import QtCore, QtGui, QtWidgets @@ -17,7 +17,7 @@ class CredentialsDialog(QtWidgets.QDialog): def __init__(self, parent=None): super(CredentialsDialog, self).__init__(parent) - self.setWindowTitle("Pype - Ftrack Login") + self.setWindowTitle("OpenPype - Ftrack Login") self._login_server_thread = None self._is_logged = False diff --git a/pype/modules/ftrack/tray/login_tools.py b/openpype/modules/ftrack/tray/login_tools.py similarity index 98% rename from pype/modules/ftrack/tray/login_tools.py rename to openpype/modules/ftrack/tray/login_tools.py index 328ce49f5c..c6cf5b5f7b 100644 --- a/pype/modules/ftrack/tray/login_tools.py +++ b/openpype/modules/ftrack/tray/login_tools.py @@ -3,7 +3,7 @@ from urllib import parse import webbrowser import functools import threading -from pype import resources +from openpype import resources class LoginServerHandler(BaseHTTPRequestHandler): diff --git a/pype/modules/idle_manager/__init__.py b/openpype/modules/idle_manager/__init__.py similarity index 100% rename from pype/modules/idle_manager/__init__.py rename to openpype/modules/idle_manager/__init__.py diff --git a/pype/modules/idle_manager/idle_module.py b/openpype/modules/idle_manager/idle_module.py similarity index 97% rename from pype/modules/idle_manager/idle_module.py rename to openpype/modules/idle_manager/idle_module.py index 979e1b92ea..ddccf07f6a 100644 --- a/pype/modules/idle_manager/idle_module.py +++ b/openpype/modules/idle_manager/idle_module.py @@ -3,7 +3,7 @@ from abc import ABCMeta, abstractmethod import six -from pype.modules import PypeModule, ITrayService +from openpype.modules import PypeModule, ITrayService @six.add_metaclass(ABCMeta) diff --git a/pype/modules/idle_manager/idle_threads.py b/openpype/modules/idle_manager/idle_threads.py similarity index 98% rename from pype/modules/idle_manager/idle_threads.py rename to openpype/modules/idle_manager/idle_threads.py index 7cedf986e6..f19feddb77 100644 --- a/pype/modules/idle_manager/idle_threads.py +++ b/openpype/modules/idle_manager/idle_threads.py @@ -3,7 +3,7 @@ import threading from pynput import mouse, keyboard -from pype.lib import PypeLogger +from openpype.lib import PypeLogger class MouseThread(mouse.Listener): diff --git a/pype/modules/launcher_action.py b/openpype/modules/launcher_action.py similarity index 90% rename from pype/modules/launcher_action.py rename to openpype/modules/launcher_action.py index 9c2120cf9a..da0468d495 100644 --- a/pype/modules/launcher_action.py +++ b/openpype/modules/launcher_action.py @@ -21,7 +21,7 @@ class LauncherAction(PypeModule, ITrayAction): def connect_with_modules(self, enabled_modules): # Register actions if self.tray_initialized: - from pype.tools.launcher import actions + from openpype.tools.launcher import actions # actions.register_default_actions() actions.register_config_actions() actions_paths = self.manager.collect_plugin_paths()["actions"] @@ -31,7 +31,7 @@ class LauncherAction(PypeModule, ITrayAction): def create_window(self): if self.window: return - from pype.tools.launcher import LauncherWindow + from openpype.tools.launcher import LauncherWindow self.window = LauncherWindow() def on_action_trigger(self): diff --git a/pype/modules/log_viewer/__init__.py b/openpype/modules/log_viewer/__init__.py similarity index 100% rename from pype/modules/log_viewer/__init__.py rename to openpype/modules/log_viewer/__init__.py diff --git a/pype/modules/log_viewer/log_view_module.py b/openpype/modules/log_viewer/log_view_module.py similarity index 97% rename from pype/modules/log_viewer/log_view_module.py rename to openpype/modules/log_viewer/log_view_module.py index 1252eaf888..dde482b04c 100644 --- a/pype/modules/log_viewer/log_view_module.py +++ b/openpype/modules/log_viewer/log_view_module.py @@ -1,4 +1,4 @@ -from pype.api import Logger +from openpype.api import Logger from .. import PypeModule, ITrayModule diff --git a/pype/modules/log_viewer/tray/__init__.py b/openpype/modules/log_viewer/tray/__init__.py similarity index 100% rename from pype/modules/log_viewer/tray/__init__.py rename to openpype/modules/log_viewer/tray/__init__.py diff --git a/pype/modules/log_viewer/tray/app.py b/openpype/modules/log_viewer/tray/app.py similarity index 100% rename from pype/modules/log_viewer/tray/app.py rename to openpype/modules/log_viewer/tray/app.py diff --git a/pype/modules/log_viewer/tray/models.py b/openpype/modules/log_viewer/tray/models.py similarity index 99% rename from pype/modules/log_viewer/tray/models.py rename to openpype/modules/log_viewer/tray/models.py index 6820d5bcb9..aea62c381b 100644 --- a/pype/modules/log_viewer/tray/models.py +++ b/openpype/modules/log_viewer/tray/models.py @@ -1,6 +1,6 @@ import collections from Qt import QtCore, QtGui -from pype.lib.log import PypeLogger +from openpype.lib.log import PypeLogger class LogModel(QtGui.QStandardItemModel): diff --git a/pype/modules/log_viewer/tray/widgets.py b/openpype/modules/log_viewer/tray/widgets.py similarity index 100% rename from pype/modules/log_viewer/tray/widgets.py rename to openpype/modules/log_viewer/tray/widgets.py diff --git a/pype/modules/muster/__init__.py b/openpype/modules/muster/__init__.py similarity index 100% rename from pype/modules/muster/__init__.py rename to openpype/modules/muster/__init__.py diff --git a/pype/modules/muster/muster.py b/openpype/modules/muster/muster.py similarity index 97% rename from pype/modules/muster/muster.py rename to openpype/modules/muster/muster.py index 5595ccff15..1a82926802 100644 --- a/pype/modules/muster/muster.py +++ b/openpype/modules/muster/muster.py @@ -153,5 +153,5 @@ class MusterModule(PypeModule, ITrayModule, IWebServerRoutes): of defense SSL is providing and it is not recommended. """ if 'verify' not in kwargs: - kwargs['verify'] = False if os.getenv("PYPE_DONT_VERIFY_SSL", True) else True # noqa + kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa return requests.post(*args, **kwargs) diff --git a/pype/modules/muster/rest_api.py b/openpype/modules/muster/rest_api.py similarity index 100% rename from pype/modules/muster/rest_api.py rename to openpype/modules/muster/rest_api.py diff --git a/pype/modules/muster/widget_login.py b/openpype/modules/muster/widget_login.py similarity index 99% rename from pype/modules/muster/widget_login.py rename to openpype/modules/muster/widget_login.py index 0fd1913d0c..d9af4cb99f 100644 --- a/pype/modules/muster/widget_login.py +++ b/openpype/modules/muster/widget_login.py @@ -1,7 +1,7 @@ import os from Qt import QtCore, QtGui, QtWidgets from avalon import style -from pype import resources +from openpype import resources class MusterLogin(QtWidgets.QWidget): diff --git a/pype/modules/settings_action.py b/openpype/modules/settings_action.py similarity index 97% rename from pype/modules/settings_action.py rename to openpype/modules/settings_action.py index 2fc59b11c2..371e190c12 100644 --- a/pype/modules/settings_action.py +++ b/openpype/modules/settings_action.py @@ -63,7 +63,7 @@ class SettingsAction(PypeModule, ITrayAction): """Initializa Settings Qt window.""" if self.settings_window: return - from pype.tools.settings import MainWidget + from openpype.tools.settings import MainWidget self.settings_window = MainWidget(self.user_role) def show_settings_window(self): @@ -118,7 +118,7 @@ class LocalSettingsAction(PypeModule, ITrayAction): """Initializa Settings Qt window.""" if self.settings_window: return - from pype.tools.settings import LocalSettingsWindow + from openpype.tools.settings import LocalSettingsWindow self.settings_window = LocalSettingsWindow() def show_settings_window(self): diff --git a/pype/modules/standalonepublish_action.py b/openpype/modules/standalonepublish_action.py similarity index 90% rename from pype/modules/standalonepublish_action.py rename to openpype/modules/standalonepublish_action.py index 4e9360a9e8..87f7446341 100644 --- a/pype/modules/standalonepublish_action.py +++ b/openpype/modules/standalonepublish_action.py @@ -1,7 +1,7 @@ import os import sys import subprocess -from pype.lib import get_pype_execute_args +from openpype.lib import get_pype_execute_args from . import PypeModule, ITrayAction @@ -10,11 +10,11 @@ class StandAlonePublishAction(PypeModule, ITrayAction): name = "standalonepublish_tool" def initialize(self, modules_settings): - import pype + import openpype self.enabled = modules_settings[self.name]["enabled"] self.publish_paths = [ os.path.join( - pype.PACKAGE_DIR, + openpype.PACKAGE_DIR, "hosts", "standalonepublisher", "plugins", diff --git a/pype/modules/sync_server/README.md b/openpype/modules/sync_server/README.md similarity index 100% rename from pype/modules/sync_server/README.md rename to openpype/modules/sync_server/README.md diff --git a/pype/modules/sync_server/__init__.py b/openpype/modules/sync_server/__init__.py similarity index 51% rename from pype/modules/sync_server/__init__.py rename to openpype/modules/sync_server/__init__.py index 7452b5be1a..7123536fcf 100644 --- a/pype/modules/sync_server/__init__.py +++ b/openpype/modules/sync_server/__init__.py @@ -1,4 +1,4 @@ -from pype.modules.sync_server.sync_server import SyncServer +from openpype.modules.sync_server.sync_server import SyncServer def tray_init(tray_widget, main_widget): diff --git a/pype/modules/sync_server/providers/abstract_provider.py b/openpype/modules/sync_server/providers/abstract_provider.py similarity index 100% rename from pype/modules/sync_server/providers/abstract_provider.py rename to openpype/modules/sync_server/providers/abstract_provider.py diff --git a/pype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py similarity index 99% rename from pype/modules/sync_server/providers/gdrive.py rename to openpype/modules/sync_server/providers/gdrive.py index cbd5c1b527..6c01bc4e6f 100644 --- a/pype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -5,8 +5,8 @@ import google.oauth2.service_account as service_account from googleapiclient import errors from .abstract_provider import AbstractProvider from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload -from pype.api import Logger -from pype.api import get_system_settings +from openpype.api import Logger +from openpype.api import get_system_settings from ..utils import time_function import time diff --git a/pype/modules/sync_server/providers/lib.py b/openpype/modules/sync_server/providers/lib.py similarity index 100% rename from pype/modules/sync_server/providers/lib.py rename to openpype/modules/sync_server/providers/lib.py diff --git a/pype/modules/sync_server/providers/local_drive.py b/openpype/modules/sync_server/providers/local_drive.py similarity index 99% rename from pype/modules/sync_server/providers/local_drive.py rename to openpype/modules/sync_server/providers/local_drive.py index 32a5017fe3..fa8dd4c183 100644 --- a/pype/modules/sync_server/providers/local_drive.py +++ b/openpype/modules/sync_server/providers/local_drive.py @@ -4,7 +4,7 @@ import shutil import threading import time -from pype.api import Logger +from openpype.api import Logger from .abstract_provider import AbstractProvider log = Logger().get_logger("SyncServer") diff --git a/openpype/modules/sync_server/providers/resources/folder.png b/openpype/modules/sync_server/providers/resources/folder.png new file mode 100644 index 0000000000..89b016563f Binary files /dev/null and b/openpype/modules/sync_server/providers/resources/folder.png differ diff --git a/openpype/modules/sync_server/providers/resources/gdrive.png b/openpype/modules/sync_server/providers/resources/gdrive.png new file mode 100644 index 0000000000..798f2e9b62 Binary files /dev/null and b/openpype/modules/sync_server/providers/resources/gdrive.png differ diff --git a/openpype/modules/sync_server/providers/resources/local_drive.png b/openpype/modules/sync_server/providers/resources/local_drive.png new file mode 100644 index 0000000000..0ff30ac615 Binary files /dev/null and b/openpype/modules/sync_server/providers/resources/local_drive.png differ diff --git a/openpype/modules/sync_server/providers/resources/studio.png b/openpype/modules/sync_server/providers/resources/studio.png new file mode 100644 index 0000000000..3af5a575bb Binary files /dev/null and b/openpype/modules/sync_server/providers/resources/studio.png differ diff --git a/openpype/modules/sync_server/resources/paused.png b/openpype/modules/sync_server/resources/paused.png new file mode 100644 index 0000000000..7dedcbb4b0 Binary files /dev/null and b/openpype/modules/sync_server/resources/paused.png differ diff --git a/openpype/modules/sync_server/resources/synced.png b/openpype/modules/sync_server/resources/synced.png new file mode 100644 index 0000000000..941e901350 Binary files /dev/null and b/openpype/modules/sync_server/resources/synced.png differ diff --git a/pype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py similarity index 99% rename from pype/modules/sync_server/sync_server.py rename to openpype/modules/sync_server/sync_server.py index e9a0b942e7..62a5dc675c 100644 --- a/pype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -1,4 +1,4 @@ -from pype.api import ( +from openpype.api import ( Anatomy, get_project_settings, get_local_site_id) @@ -18,7 +18,7 @@ from avalon.api import AvalonMongoDB from .utils import time_function import six -from pype.lib import PypeLogger +from openpype.lib import PypeLogger from .. import PypeModule, ITrayModule from .providers.local_drive import LocalDriveHandler diff --git a/pype/modules/sync_server/tray/app.py b/openpype/modules/sync_server/tray/app.py similarity index 99% rename from pype/modules/sync_server/tray/app.py rename to openpype/modules/sync_server/tray/app.py index 783f2def1c..476e9d16e8 100644 --- a/pype/modules/sync_server/tray/app.py +++ b/openpype/modules/sync_server/tray/app.py @@ -6,7 +6,7 @@ import os import sys import subprocess -from pype.tools.settings import ( +from openpype.tools.settings import ( ProjectListWidget, style ) @@ -14,8 +14,8 @@ from pype.tools.settings import ( from avalon.tools.delegates import PrettyTimeDelegate, pretty_timestamp from bson.objectid import ObjectId -from pype.lib import PypeLogger -from pype.api import get_local_site_id +from openpype.lib import PypeLogger +from openpype.api import get_local_site_id log = PypeLogger().get_logger("SyncServer") diff --git a/pype/modules/sync_server/utils.py b/openpype/modules/sync_server/utils.py similarity index 95% rename from pype/modules/sync_server/utils.py rename to openpype/modules/sync_server/utils.py index d5127213af..0762766783 100644 --- a/pype/modules/sync_server/utils.py +++ b/openpype/modules/sync_server/utils.py @@ -1,5 +1,5 @@ import time -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger("SyncServer") diff --git a/pype/modules/timers_manager/__init__.py b/openpype/modules/timers_manager/__init__.py similarity index 100% rename from pype/modules/timers_manager/__init__.py rename to openpype/modules/timers_manager/__init__.py diff --git a/pype/modules/timers_manager/rest_api.py b/openpype/modules/timers_manager/rest_api.py similarity index 97% rename from pype/modules/timers_manager/rest_api.py rename to openpype/modules/timers_manager/rest_api.py index 2247a6f769..975c1a91f9 100644 --- a/pype/modules/timers_manager/rest_api.py +++ b/openpype/modules/timers_manager/rest_api.py @@ -1,5 +1,5 @@ from aiohttp.web_response import Response -from pype.api import Logger +from openpype.api import Logger log = Logger().get_logger("Event processor") diff --git a/pype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py similarity index 99% rename from pype/modules/timers_manager/timers_manager.py rename to openpype/modules/timers_manager/timers_manager.py index b83f51f0ba..a8ea5799e6 100644 --- a/pype/modules/timers_manager/timers_manager.py +++ b/openpype/modules/timers_manager/timers_manager.py @@ -221,7 +221,7 @@ class TimersManager(PypeModule, ITrayService, IIdleManager, IWebServerRoutes): def change_timer_from_host(self, project_name, asset_name, task_name): """Prepared method for calling change timers on REST api""" - webserver_url = os.environ.get("PYPE_WEBSERVER_URL") + webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") if not webserver_url: self.log.warning("Couldn't find webserver url") return diff --git a/pype/modules/timers_manager/widget_user_idle.py b/openpype/modules/timers_manager/widget_user_idle.py similarity index 99% rename from pype/modules/timers_manager/widget_user_idle.py rename to openpype/modules/timers_manager/widget_user_idle.py index cbdb7fd30a..8b614f6a13 100644 --- a/pype/modules/timers_manager/widget_user_idle.py +++ b/openpype/modules/timers_manager/widget_user_idle.py @@ -1,6 +1,6 @@ from avalon import style from Qt import QtCore, QtGui, QtWidgets -from pype import resources +from openpype import resources class WidgetUserIdle(QtWidgets.QWidget): diff --git a/pype/modules/user/__init__.py b/openpype/modules/user/__init__.py similarity index 100% rename from pype/modules/user/__init__.py rename to openpype/modules/user/__init__.py diff --git a/pype/modules/user/rest_api.py b/openpype/modules/user/rest_api.py similarity index 100% rename from pype/modules/user/rest_api.py rename to openpype/modules/user/rest_api.py diff --git a/pype/modules/user/user_module.py b/openpype/modules/user/user_module.py similarity index 99% rename from pype/modules/user/user_module.py rename to openpype/modules/user/user_module.py index 71c5fd124e..7d257f1781 100644 --- a/pype/modules/user/user_module.py +++ b/openpype/modules/user/user_module.py @@ -29,7 +29,7 @@ class UserModule(PypeModule, ITrayModule, IWebServerRoutes): appdirs.user_data_dir('pype-app', 'pype') ) cred_filename = 'user_info.json' - env_name = "PYPE_USERNAME" + env_name = "OPENPYPE_USERNAME" name = "user" diff --git a/pype/modules/user/widget_user.py b/openpype/modules/user/widget_user.py similarity index 98% rename from pype/modules/user/widget_user.py rename to openpype/modules/user/widget_user.py index d12cd6175c..f8ecadf56b 100644 --- a/pype/modules/user/widget_user.py +++ b/openpype/modules/user/widget_user.py @@ -1,6 +1,6 @@ from Qt import QtCore, QtGui, QtWidgets from avalon import style -from pype import resources +from openpype import resources class UserWidget(QtWidgets.QWidget): diff --git a/pype/modules/webserver/__init__.py b/openpype/modules/webserver/__init__.py similarity index 100% rename from pype/modules/webserver/__init__.py rename to openpype/modules/webserver/__init__.py diff --git a/pype/modules/webserver/base_routes.py b/openpype/modules/webserver/base_routes.py similarity index 100% rename from pype/modules/webserver/base_routes.py rename to openpype/modules/webserver/base_routes.py diff --git a/pype/modules/webserver/server.py b/openpype/modules/webserver/server.py similarity index 99% rename from pype/modules/webserver/server.py rename to openpype/modules/webserver/server.py index e4b0ec236b..65c5795995 100644 --- a/pype/modules/webserver/server.py +++ b/openpype/modules/webserver/server.py @@ -3,7 +3,7 @@ import asyncio from aiohttp import web -from pype.lib import PypeLogger +from openpype.lib import PypeLogger log = PypeLogger.get_logger("WebServer") diff --git a/pype/modules/webserver/webserver_module.py b/openpype/modules/webserver/webserver_module.py similarity index 95% rename from pype/modules/webserver/webserver_module.py rename to openpype/modules/webserver/webserver_module.py index 3b3f0e7a79..59a0a08427 100644 --- a/pype/modules/webserver/webserver_module.py +++ b/openpype/modules/webserver/webserver_module.py @@ -4,7 +4,7 @@ from abc import ABCMeta, abstractmethod import six -from pype import resources +from openpype import resources from .. import PypeModule, ITrayService @@ -49,8 +49,8 @@ class WebServerModule(PypeModule, ITrayService): self.server_manager.add_static(static_prefix, resources.RESOURCES_DIR) webserver_url = "http://localhost:{}".format(self.port) - os.environ["PYPE_WEBSERVER_URL"] = webserver_url - os.environ["PYPE_STATICS_SERVER"] = "{}{}".format( + os.environ["OPENPYPE_WEBSERVER_URL"] = webserver_url + os.environ["OPENPYPE_STATICS_SERVER"] = "{}{}".format( webserver_url, static_prefix ) diff --git a/pype/plugin.py b/openpype/plugin.py similarity index 53% rename from pype/plugin.py rename to openpype/plugin.py index 855b3371d3..8ade0f3825 100644 --- a/pype/plugin.py +++ b/openpype/plugin.py @@ -2,8 +2,8 @@ import tempfile import os import pyblish.api import avalon.api -from pype.api import get_project_settings -from pype.lib import filter_profiles + +from openpype.lib import get_subset_name ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 @@ -11,83 +11,19 @@ ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 -class TaskNotSetError(KeyError): - def __init__(self, msg=None): - if not msg: - msg = "Creator's subset name template requires task name." - super(TaskNotSetError, self).__init__(msg) - - class PypeCreatorMixin: """Helper to override avalon's default class methods. Mixin class must be used as first in inheritance order to override methods. """ - default_tempate = "{family}{Variant}" @classmethod def get_subset_name( cls, variant, task_name, asset_id, project_name, host_name=None ): - if not cls.family: - return "" - - if not host_name: - host_name = os.environ["AVALON_APP"] - - # Use only last part of class family value split by dot (`.`) - family = cls.family.rsplit(".", 1)[-1] - - # Get settings - tools_settings = get_project_settings(project_name)["global"]["tools"] - profiles = tools_settings["creator"]["subset_name_profiles"] - filtering_criteria = { - "families": family, - "hosts": host_name, - "tasks": task_name - } - - matching_profile = filter_profiles(profiles, filtering_criteria) - template = None - if matching_profile: - template = matching_profile["template"] - - # Make sure template is set (matching may have empty string) - if not template: - template = cls.default_tempate - - # Simple check of task name existence for template with {task} in - # - missing task should be possible only in Standalone publisher - if not task_name and "{task" in template.lower(): - raise TaskNotSetError() - - fill_pairs = ( - ("variant", variant), - ("family", family), - ("task", task_name) + return get_subset_name( + cls.family, variant, task_name, asset_id, project_name, host_name ) - fill_data = {} - for key, value in fill_pairs: - # Handle cases when value is `None` (standalone publisher) - if value is None: - continue - # Keep value as it is - fill_data[key] = value - # Both key and value are with upper case - fill_data[key.upper()] = value.upper() - - # Capitalize only first char of value - # - conditions are because of possible index errors - capitalized = "" - if value: - # Upper first character - capitalized += value[0].upper() - # Append rest of string if there is any - if len(value) > 1: - capitalized += value[1:] - fill_data[key.capitalize()] = capitalized - - return template.format(**fill_data) class Creator(PypeCreatorMixin, avalon.api.Creator): diff --git a/pype/plugins/load/copy_file.py b/openpype/plugins/load/copy_file.py similarity index 100% rename from pype/plugins/load/copy_file.py rename to openpype/plugins/load/copy_file.py diff --git a/pype/plugins/load/copy_file_path.py b/openpype/plugins/load/copy_file_path.py similarity index 100% rename from pype/plugins/load/copy_file_path.py rename to openpype/plugins/load/copy_file_path.py diff --git a/pype/plugins/load/delete_old_versions.py b/openpype/plugins/load/delete_old_versions.py similarity index 99% rename from pype/plugins/load/delete_old_versions.py rename to openpype/plugins/load/delete_old_versions.py index b00d247425..e5132e0f8a 100644 --- a/pype/plugins/load/delete_old_versions.py +++ b/openpype/plugins/load/delete_old_versions.py @@ -11,7 +11,7 @@ from avalon.vendor.Qt import QtWidgets, QtCore from avalon.vendor import qargparse from avalon.api import AvalonMongoDB import avalon.pipeline -from pype.api import Anatomy +from openpype.api import Anatomy class DeleteOldVersions(api.Loader): diff --git a/pype/plugins/load/open_djv.py b/openpype/plugins/load/open_djv.py similarity index 100% rename from pype/plugins/load/open_djv.py rename to openpype/plugins/load/open_djv.py diff --git a/pype/plugins/load/open_file.py b/openpype/plugins/load/open_file.py similarity index 100% rename from pype/plugins/load/open_file.py rename to openpype/plugins/load/open_file.py diff --git a/pype/plugins/publish/cleanup.py b/openpype/plugins/publish/cleanup.py similarity index 100% rename from pype/plugins/publish/cleanup.py rename to openpype/plugins/publish/cleanup.py diff --git a/pype/plugins/publish/collect_anatomy_context_data.py b/openpype/plugins/publish/collect_anatomy_context_data.py similarity index 98% rename from pype/plugins/publish/collect_anatomy_context_data.py rename to openpype/plugins/publish/collect_anatomy_context_data.py index 07e58d8cb7..5b955a0592 100644 --- a/pype/plugins/publish/collect_anatomy_context_data.py +++ b/openpype/plugins/publish/collect_anatomy_context_data.py @@ -15,7 +15,7 @@ Provides: import os import json -from pype.lib import ApplicationManager +from openpype.lib import ApplicationManager from avalon import api, lib import pyblish.api diff --git a/pype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py similarity index 100% rename from pype/plugins/publish/collect_anatomy_instance_data.py rename to openpype/plugins/publish/collect_anatomy_instance_data.py diff --git a/pype/plugins/publish/collect_anatomy_object.py b/openpype/plugins/publish/collect_anatomy_object.py similarity index 96% rename from pype/plugins/publish/collect_anatomy_object.py rename to openpype/plugins/publish/collect_anatomy_object.py index 17f6e16962..2c87918728 100644 --- a/pype/plugins/publish/collect_anatomy_object.py +++ b/openpype/plugins/publish/collect_anatomy_object.py @@ -7,7 +7,7 @@ Provides: context -> anatomy (pype.api.Anatomy) """ import os -from pype.api import Anatomy +from openpype.api import Anatomy import pyblish.api diff --git a/pype/plugins/publish/collect_avalon_entities.py b/openpype/plugins/publish/collect_avalon_entities.py similarity index 100% rename from pype/plugins/publish/collect_avalon_entities.py rename to openpype/plugins/publish/collect_avalon_entities.py diff --git a/pype/plugins/publish/collect_comment.py b/openpype/plugins/publish/collect_comment.py similarity index 100% rename from pype/plugins/publish/collect_comment.py rename to openpype/plugins/publish/collect_comment.py diff --git a/pype/plugins/publish/collect_context_label.py b/openpype/plugins/publish/collect_context_label.py similarity index 100% rename from pype/plugins/publish/collect_context_label.py rename to openpype/plugins/publish/collect_context_label.py diff --git a/pype/plugins/publish/collect_current_pype_user.py b/openpype/plugins/publish/collect_current_pype_user.py similarity index 90% rename from pype/plugins/publish/collect_current_pype_user.py rename to openpype/plugins/publish/collect_current_pype_user.py index a8947dd8fb..de4e950d56 100644 --- a/pype/plugins/publish/collect_current_pype_user.py +++ b/openpype/plugins/publish/collect_current_pype_user.py @@ -11,7 +11,7 @@ class CollectCurrentUserPype(pyblish.api.ContextPlugin): label = "Collect Pype User" def process(self, context): - user = os.getenv("PYPE_USERNAME", "").strip() + user = os.getenv("OPENPYPE_USERNAME", "").strip() if not user: user = context.data.get("user", getpass.getuser()) diff --git a/pype/plugins/publish/collect_current_shell_file.py b/openpype/plugins/publish/collect_current_shell_file.py similarity index 100% rename from pype/plugins/publish/collect_current_shell_file.py rename to openpype/plugins/publish/collect_current_shell_file.py diff --git a/pype/plugins/publish/collect_datetime_data.py b/openpype/plugins/publish/collect_datetime_data.py similarity index 92% rename from pype/plugins/publish/collect_datetime_data.py rename to openpype/plugins/publish/collect_datetime_data.py index 1ad1f22a02..1675ae1a98 100644 --- a/pype/plugins/publish/collect_datetime_data.py +++ b/openpype/plugins/publish/collect_datetime_data.py @@ -5,7 +5,7 @@ Provides: """ import pyblish.api -from pype.api import config +from openpype.api import config class CollectDateTimeData(pyblish.api.ContextPlugin): diff --git a/pype/plugins/publish/collect_hierarchy.py b/openpype/plugins/publish/collect_hierarchy.py similarity index 100% rename from pype/plugins/publish/collect_hierarchy.py rename to openpype/plugins/publish/collect_hierarchy.py diff --git a/pype/plugins/publish/collect_machine_name.py b/openpype/plugins/publish/collect_machine_name.py similarity index 100% rename from pype/plugins/publish/collect_machine_name.py rename to openpype/plugins/publish/collect_machine_name.py diff --git a/pype/plugins/publish/collect_otio_frame_ranges.py b/openpype/plugins/publish/collect_otio_frame_ranges.py similarity index 91% rename from pype/plugins/publish/collect_otio_frame_ranges.py rename to openpype/plugins/publish/collect_otio_frame_ranges.py index 849a2c2475..53cc249033 100644 --- a/pype/plugins/publish/collect_otio_frame_ranges.py +++ b/openpype/plugins/publish/collect_otio_frame_ranges.py @@ -8,7 +8,7 @@ Requires: # import os import opentimelineio as otio import pyblish.api -import pype.lib +import openpype.lib from pprint import pformat @@ -31,9 +31,9 @@ class CollectOcioFrameRanges(pyblish.api.InstancePlugin): otio_tl_range = otio_clip.range_in_parent() otio_src_range = otio_clip.source_range otio_avalable_range = otio_clip.available_range() - otio_tl_range_handles = pype.lib.otio_range_with_handles( + otio_tl_range_handles = openpype.lib.otio_range_with_handles( otio_tl_range, instance) - otio_src_range_handles = pype.lib.otio_range_with_handles( + otio_src_range_handles = openpype.lib.otio_range_with_handles( otio_src_range, instance) # get source avalable start frame @@ -42,7 +42,7 @@ class CollectOcioFrameRanges(pyblish.api.InstancePlugin): otio_avalable_range.start_time.rate) # convert to frames - range_convert = pype.lib.otio_range_to_frame_range + range_convert = openpype.lib.otio_range_to_frame_range tl_start, tl_end = range_convert(otio_tl_range) tl_start_h, tl_end_h = range_convert(otio_tl_range_handles) src_start, src_end = range_convert(otio_src_range) diff --git a/pype/plugins/publish/collect_otio_review.py b/openpype/plugins/publish/collect_otio_review.py similarity index 100% rename from pype/plugins/publish/collect_otio_review.py rename to openpype/plugins/publish/collect_otio_review.py diff --git a/pype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py similarity index 92% rename from pype/plugins/publish/collect_otio_subset_resources.py rename to openpype/plugins/publish/collect_otio_subset_resources.py index d1fd47debd..a0c6b9339b 100644 --- a/pype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -10,7 +10,7 @@ import os import clique import opentimelineio as otio import pyblish.api -import pype +import openpype class CollectOcioSubsetResources(pyblish.api.InstancePlugin): @@ -34,21 +34,21 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): # generate range in parent otio_src_range = otio_clip.source_range otio_avalable_range = otio_clip.available_range() - trimmed_media_range = pype.lib.trim_media_range( + trimmed_media_range = openpype.lib.trim_media_range( otio_avalable_range, otio_src_range) # calculate wth handles - otio_src_range_handles = pype.lib.otio_range_with_handles( + otio_src_range_handles = openpype.lib.otio_range_with_handles( otio_src_range, instance) - trimmed_media_range_h = pype.lib.trim_media_range( + trimmed_media_range_h = openpype.lib.trim_media_range( otio_avalable_range, otio_src_range_handles) # frame start and end from media - s_frame_start, s_frame_end = pype.lib.otio_range_to_frame_range( + s_frame_start, s_frame_end = openpype.lib.otio_range_to_frame_range( trimmed_media_range) - a_frame_start, a_frame_end = pype.lib.otio_range_to_frame_range( + a_frame_start, a_frame_end = openpype.lib.otio_range_to_frame_range( otio_avalable_range) - a_frame_start_h, a_frame_end_h = pype.lib.otio_range_to_frame_range( + a_frame_start_h, a_frame_end_h = openpype.lib.otio_range_to_frame_range( trimmed_media_range_h) # fix frame_start and frame_end frame to be in range of media @@ -115,7 +115,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin): # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` path = media_ref.target_url - collection_data = pype.lib.make_sequence_collection( + collection_data = openpype.lib.make_sequence_collection( path, trimmed_media_range, metadata) self.staging_dir, collection = collection_data diff --git a/pype/plugins/publish/collect_rendered_files.py b/openpype/plugins/publish/collect_rendered_files.py similarity index 95% rename from pype/plugins/publish/collect_rendered_files.py rename to openpype/plugins/publish/collect_rendered_files.py index e0f3695fd5..edf9b50b92 100644 --- a/pype/plugins/publish/collect_rendered_files.py +++ b/openpype/plugins/publish/collect_rendered_files.py @@ -17,7 +17,7 @@ from avalon import api class CollectRenderedFiles(pyblish.api.ContextPlugin): """ This collector will try to find json files in provided - `PYPE_PUBLISH_DATA`. Those files _MUST_ share same context. + `OPENPYPE_PUBLISH_DATA`. Those files _MUST_ share same context. """ order = pyblish.api.CollectorOrder - 0.2 @@ -113,9 +113,9 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): def process(self, context): self._context = context - assert os.environ.get("PYPE_PUBLISH_DATA"), ( - "Missing `PYPE_PUBLISH_DATA`") - paths = os.environ["PYPE_PUBLISH_DATA"].split(os.pathsep) + assert os.environ.get("OPENPYPE_PUBLISH_DATA"), ( + "Missing `OPENPYPE_PUBLISH_DATA`") + paths = os.environ["OPENPYPE_PUBLISH_DATA"].split(os.pathsep) project_name = os.environ.get("AVALON_PROJECT") if project_name is None: diff --git a/pype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py similarity index 100% rename from pype/plugins/publish/collect_resources_path.py rename to openpype/plugins/publish/collect_resources_path.py diff --git a/pype/plugins/publish/collect_scene_version.py b/openpype/plugins/publish/collect_scene_version.py similarity index 97% rename from pype/plugins/publish/collect_scene_version.py rename to openpype/plugins/publish/collect_scene_version.py index de2d0d282e..f58bd0dd9d 100644 --- a/pype/plugins/publish/collect_scene_version.py +++ b/openpype/plugins/publish/collect_scene_version.py @@ -1,6 +1,6 @@ import os import pyblish.api -import pype.api as pype +import openpype.api as pype class CollectSceneVersion(pyblish.api.ContextPlugin): diff --git a/pype/plugins/publish/collect_settings.py b/openpype/plugins/publish/collect_settings.py similarity index 82% rename from pype/plugins/publish/collect_settings.py rename to openpype/plugins/publish/collect_settings.py index 8531e530ac..d56eabd1b5 100644 --- a/pype/plugins/publish/collect_settings.py +++ b/openpype/plugins/publish/collect_settings.py @@ -1,5 +1,5 @@ from pyblish import api -from pype.api import get_current_project_settings, get_system_settings +from openpype.api import get_current_project_settings, get_system_settings class CollectSettings(api.ContextPlugin): diff --git a/pype/plugins/publish/collect_shell_workspace.py b/openpype/plugins/publish/collect_shell_workspace.py similarity index 100% rename from pype/plugins/publish/collect_shell_workspace.py rename to openpype/plugins/publish/collect_shell_workspace.py diff --git a/pype/plugins/publish/collect_time.py b/openpype/plugins/publish/collect_time.py similarity index 100% rename from pype/plugins/publish/collect_time.py rename to openpype/plugins/publish/collect_time.py diff --git a/pype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py similarity index 99% rename from pype/plugins/publish/extract_burnin.py rename to openpype/plugins/publish/extract_burnin.py index f3276972e6..e266c39714 100644 --- a/pype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -4,10 +4,10 @@ import json import copy import tempfile -import pype -import pype.api +import openpype +import openpype.api import pyblish -from pype.lib import ( +from openpype.lib import ( get_pype_execute_args, should_decompress, get_decompress_dir, @@ -16,7 +16,7 @@ from pype.lib import ( import shutil -class ExtractBurnin(pype.api.Extractor): +class ExtractBurnin(openpype.api.Extractor): """ Extractor to create video with pre-defined burnins from existing extracted video representation. @@ -273,7 +273,7 @@ class ExtractBurnin(pype.api.Extractor): self.log.debug("Executing: {}".format(" ".join(args))) # Run burnin script - pype.api.run_subprocess( + openpype.api.run_subprocess( args, shell=True, logger=self.log, env=env ) @@ -822,7 +822,7 @@ class ExtractBurnin(pype.api.Extractor): """Return path to python script for burnin processing.""" scriptpath = os.path.normpath( os.path.join( - pype.PACKAGE_DIR, + openpype.PACKAGE_DIR, "scripts", "otio_burnin.py" ) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py new file mode 100644 index 0000000000..dd1f09bafa --- /dev/null +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -0,0 +1,196 @@ +import pyblish.api +from avalon import io +from copy import deepcopy + + +class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): + """Create entities in Avalon based on collected data.""" + + order = pyblish.api.ExtractorOrder - 0.01 + label = "Extract Hierarchy To Avalon" + families = ["clip", "shot"] + + def process(self, context): + # processing starts here + if "hierarchyContext" not in context.data: + self.log.info("skipping IntegrateHierarchyToAvalon") + return + hierarchy_context = deepcopy(context.data["hierarchyContext"]) + + if not io.Session: + io.install() + + active_assets = [] + # filter only the active publishing insatnces + for instance in context: + if instance.data.get("publish") is False: + continue + + if not instance.data.get("asset"): + continue + + active_assets.append(instance.data["asset"]) + + # remove duplicity in list + self.active_assets = list(set(active_assets)) + self.log.debug("__ self.active_assets: {}".format(self.active_assets)) + + hierarchy_context = self._get_assets(hierarchy_context) + + self.log.debug("__ hierarchy_context: {}".format(hierarchy_context)) + input_data = context.data["hierarchyContext"] = hierarchy_context + + self.project = None + self.import_to_avalon(input_data) + + def import_to_avalon(self, input_data, parent=None): + for name in input_data: + self.log.info("input_data[name]: {}".format(input_data[name])) + entity_data = input_data[name] + entity_type = entity_data["entity_type"] + + data = {} + data["entityType"] = entity_type + + # Custom attributes. + for k, val in entity_data.get("custom_attributes", {}).items(): + data[k] = val + + if entity_type.lower() != "project": + data["inputs"] = entity_data.get("inputs", []) + + # Tasks. + tasks = entity_data.get("tasks", {}) + if tasks is not None or len(tasks) > 0: + data["tasks"] = tasks + parents = [] + visualParent = None + # do not store project"s id as visualParent (silo asset) + if self.project is not None: + if self.project["_id"] != parent["_id"]: + visualParent = parent["_id"] + parents.extend( + parent.get("data", {}).get("parents", []) + ) + parents.append(parent["name"]) + data["visualParent"] = visualParent + data["parents"] = parents + + update_data = True + # Process project + if entity_type.lower() == "project": + entity = io.find_one({"type": "project"}) + # TODO: should be in validator? + assert (entity is not None), "Did not find project in DB" + + # get data from already existing project + cur_entity_data = entity.get("data") or {} + cur_entity_data.update(data) + data = cur_entity_data + + self.project = entity + # Raise error if project or parent are not set + elif self.project is None or parent is None: + raise AssertionError( + "Collected items are not in right order!" + ) + # Else process assset + else: + entity = io.find_one({"type": "asset", "name": name}) + if entity: + # Do not override data, only update + cur_entity_data = entity.get("data") or {} + new_tasks = data.pop("tasks", {}) + if "tasks" not in cur_entity_data and not new_tasks: + continue + for task_name in new_tasks: + if task_name in cur_entity_data["tasks"].keys(): + continue + cur_entity_data["tasks"][task_name] = new_tasks[task_name] + cur_entity_data.update(data) + data = cur_entity_data + else: + # Skip updating data + update_data = False + + archived_entities = io.find({ + "type": "archived_asset", + "name": name + }) + unarchive_entity = None + for archived_entity in archived_entities: + archived_parents = ( + archived_entity + .get("data", {}) + .get("parents") + ) + if data["parents"] == archived_parents: + unarchive_entity = archived_entity + break + + if unarchive_entity is None: + # Create entity if doesn"t exist + entity = self.create_avalon_asset(name, data) + else: + # Unarchive if entity was archived + entity = self.unarchive_entity(unarchive_entity, data) + + if update_data: + # Update entity data with input data + io.update_many( + {"_id": entity["_id"]}, + {"$set": {"data": data}} + ) + + if "childs" in entity_data: + self.import_to_avalon(entity_data["childs"], entity) + + def unarchive_entity(self, entity, data): + # Unarchived asset should not use same data + new_entity = { + "_id": entity["_id"], + "schema": "openpype:asset-3.0", + "name": entity["name"], + "parent": self.project["_id"], + "type": "asset", + "data": data + } + io.replace_one( + {"_id": entity["_id"]}, + new_entity + ) + return new_entity + + def create_avalon_asset(self, name, data): + item = { + "schema": "openpype:asset-3.0", + "name": name, + "parent": self.project["_id"], + "type": "asset", + "data": data + } + self.log.debug("Creating asset: {}".format(item)) + entity_id = io.insert_one(item).inserted_id + + return io.find_one({"_id": entity_id}) + + def _get_assets(self, input_dict): + """ Returns only asset dictionary. + Usually the last part of deep dictionary which + is not having any children + """ + input_dict_copy = deepcopy(input_dict) + + for key in input_dict.keys(): + self.log.debug("__ key: {}".format(key)) + # check if child key is available + if input_dict[key].get("childs"): + # loop deeper + input_dict_copy[key]["childs"] = self._get_assets( + input_dict[key]["childs"]) + else: + # filter out unwanted assets + if key not in self.active_assets: + input_dict_copy.pop(key, None) + + return input_dict_copy diff --git a/pype/plugins/publish/extract_jpeg.py b/openpype/plugins/publish/extract_jpeg.py similarity index 95% rename from pype/plugins/publish/extract_jpeg.py rename to openpype/plugins/publish/extract_jpeg.py index 1c921a90d4..b1289217e6 100644 --- a/pype/plugins/publish/extract_jpeg.py +++ b/openpype/plugins/publish/extract_jpeg.py @@ -1,9 +1,9 @@ import os import pyblish.api -import pype.api -import pype.lib -from pype.lib import should_decompress, \ +import openpype.api +import openpype.lib +from openpype.lib import should_decompress, \ get_decompress_dir, decompress import shutil @@ -85,7 +85,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): self.log.info("output {}".format(full_output_path)) - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") ffmpeg_args = self.ffmpeg_args or {} jpeg_items = [] @@ -111,7 +111,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): # run subprocess self.log.debug("{}".format(subprocess_jpeg)) try: # temporary until oiiotool is supported cross platform - pype.api.run_subprocess( + openpype.api.run_subprocess( subprocess_jpeg, shell=True, logger=self.log ) except RuntimeError as exp: diff --git a/pype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py similarity index 94% rename from pype/plugins/publish/extract_otio_file.py rename to openpype/plugins/publish/extract_otio_file.py index 84932f07a8..146f3b88ec 100644 --- a/pype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -1,10 +1,10 @@ import os import pyblish.api -import pype.api +import openpype.api import opentimelineio as otio -class ExtractOTIOFile(pype.api.Extractor): +class ExtractOTIOFile(openpype.api.Extractor): """ Extractor export OTIO file """ diff --git a/pype/plugins/publish/extract_otio_review.py b/openpype/plugins/publish/extract_otio_review.py similarity index 95% rename from pype/plugins/publish/extract_otio_review.py rename to openpype/plugins/publish/extract_otio_review.py index 0eb592a323..91a680ddb0 100644 --- a/pype/plugins/publish/extract_otio_review.py +++ b/openpype/plugins/publish/extract_otio_review.py @@ -18,10 +18,10 @@ import os import clique import opentimelineio as otio from pyblish import api -import pype +import openpype -class ExtractOTIOReview(pype.api.Extractor): +class ExtractOTIOReview(openpype.api.Extractor): """ Extract OTIO timeline into one concuted image sequence file. @@ -140,7 +140,7 @@ class ExtractOTIOReview(pype.api.Extractor): dirname = media_ref.target_url_base head = media_ref.name_prefix tail = media_ref.name_suffix - first, last = pype.lib.otio_range_to_frame_range( + first, last = openpype.lib.otio_range_to_frame_range( available_range) collection = clique.Collection( head=head, @@ -159,7 +159,7 @@ class ExtractOTIOReview(pype.api.Extractor): # in case it is file sequence but not new OTIO schema # `ImageSequenceReference` path = media_ref.target_url - collection_data = pype.lib.make_sequence_collection( + collection_data = openpype.lib.make_sequence_collection( path, available_range, metadata) dir_path, collection = collection_data @@ -282,8 +282,8 @@ class ExtractOTIOReview(pype.api.Extractor): duration = avl_durtation # return correct trimmed range - return pype.lib.trim_media_range( - avl_range, pype.lib.range_from_frames(start, duration, fps) + return openpype.lib.trim_media_range( + avl_range, openpype.lib.range_from_frames(start, duration, fps) ) def _render_seqment(self, sequence=None, @@ -304,7 +304,7 @@ class ExtractOTIOReview(pype.api.Extractor): otio.time.TimeRange: trimmed available range """ # get rendering app path - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") # create path and frame start to destination output_path, out_frame_start = self._get_ffmpeg_output() @@ -334,8 +334,8 @@ class ExtractOTIOReview(pype.api.Extractor): frame_start = otio_range.start_time.value input_fps = otio_range.start_time.rate frame_duration = otio_range.duration.value - sec_start = pype.lib.frames_to_secons(frame_start, input_fps) - sec_duration = pype.lib.frames_to_secons(frame_duration, input_fps) + sec_start = openpype.lib.frames_to_secons(frame_start, input_fps) + sec_duration = openpype.lib.frames_to_secons(frame_duration, input_fps) # form command for rendering gap files command.extend([ @@ -345,7 +345,7 @@ class ExtractOTIOReview(pype.api.Extractor): ]) elif gap: - sec_duration = pype.lib.frames_to_secons( + sec_duration = openpype.lib.frames_to_secons( gap, self.actual_fps) # form command for rendering gap files @@ -364,7 +364,7 @@ class ExtractOTIOReview(pype.api.Extractor): ]) # execute self.log.debug("Executing: {}".format(" ".join(command))) - output = pype.api.run_subprocess(" ".join(command), shell=True) + output = openpype.api.run_subprocess(" ".join(command), shell=True) self.log.debug("Output: {}".format(output)) def _generate_used_frames(self, duration, end_offset=None): diff --git a/pype/plugins/publish/extract_review.py b/openpype/plugins/publish/extract_review.py similarity index 99% rename from pype/plugins/publish/extract_review.py rename to openpype/plugins/publish/extract_review.py index f6530219a6..23c8ed2a8e 100644 --- a/pype/plugins/publish/extract_review.py +++ b/openpype/plugins/publish/extract_review.py @@ -4,9 +4,9 @@ import copy import json import pyblish.api import clique -import pype.api -import pype.lib -from pype.lib import should_decompress, \ +import openpype.api +import openpype.lib +from openpype.lib import should_decompress, \ get_decompress_dir, decompress @@ -43,7 +43,7 @@ class ExtractReview(pyblish.api.InstancePlugin): supported_exts = image_exts + video_exts # FFmpeg tools paths - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") # Preset attributes profiles = None @@ -207,7 +207,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - pype.api.run_subprocess( + openpype.api.run_subprocess( subprcs_cmd, shell=True, logger=self.log ) @@ -726,7 +726,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Skipped using instance's resolution full_input_path_single_file = temp_data["full_input_path_single_file"] - input_data = pype.lib.ffprobe_streams( + input_data = openpype.lib.ffprobe_streams( full_input_path_single_file, self.log )[0] input_width = int(input_data["width"]) @@ -1279,7 +1279,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # get representation and loop them representations = inst_data["representations"] - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") # filter out mov and img sequences representations_new = representations[:] @@ -1630,7 +1630,7 @@ class ExtractReview(pyblish.api.InstancePlugin): # run subprocess self.log.debug("Executing: {}".format(subprcs_cmd)) - pype.api.run_subprocess( + openpype.api.run_subprocess( subprcs_cmd, shell=True, logger=self.log ) diff --git a/pype/plugins/publish/extract_review_slate.py b/openpype/plugins/publish/extract_review_slate.py similarity index 96% rename from pype/plugins/publish/extract_review_slate.py rename to openpype/plugins/publish/extract_review_slate.py index 65930ea8fa..fb36a930fb 100644 --- a/pype/plugins/publish/extract_review_slate.py +++ b/openpype/plugins/publish/extract_review_slate.py @@ -1,10 +1,10 @@ import os -import pype.api -import pype.lib +import openpype.api +import openpype.lib import pyblish -class ExtractReviewSlate(pype.api.Extractor): +class ExtractReviewSlate(openpype.api.Extractor): """ Will add slate frame at the start of the video files """ @@ -24,9 +24,9 @@ class ExtractReviewSlate(pype.api.Extractor): suffix = "_slate" slate_path = inst_data.get("slateFrame") - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") - slate_stream = pype.lib.ffprobe_streams(slate_path, self.log)[0] + slate_stream = openpype.lib.ffprobe_streams(slate_path, self.log)[0] slate_width = slate_stream["width"] slate_height = slate_stream["height"] @@ -186,7 +186,7 @@ class ExtractReviewSlate(pype.api.Extractor): # run slate generation subprocess self.log.debug("Slate Executing: {}".format(slate_subprcs_cmd)) - pype.api.run_subprocess( + openpype.api.run_subprocess( slate_subprcs_cmd, shell=True, logger=self.log ) @@ -222,7 +222,7 @@ class ExtractReviewSlate(pype.api.Extractor): # ffmpeg concat subprocess self.log.debug("Executing concat: {}".format(concat_subprcs_cmd)) - pype.api.run_subprocess( + openpype.api.run_subprocess( concat_subprcs_cmd, shell=True, logger=self.log ) @@ -301,7 +301,7 @@ class ExtractReviewSlate(pype.api.Extractor): try: # Get information about input file via ffprobe tool - streams = pype.lib.ffprobe_streams(full_input_path, self.log) + streams = openpype.lib.ffprobe_streams(full_input_path, self.log) except Exception: self.log.warning( "Could not get codec data from input.", diff --git a/pype/plugins/publish/extract_scanline_exr.py b/openpype/plugins/publish/extract_scanline_exr.py similarity index 94% rename from pype/plugins/publish/extract_scanline_exr.py rename to openpype/plugins/publish/extract_scanline_exr.py index a801baa17c..404aa65ac2 100644 --- a/pype/plugins/publish/extract_scanline_exr.py +++ b/openpype/plugins/publish/extract_scanline_exr.py @@ -4,8 +4,8 @@ import os import shutil import pyblish.api -import pype.api -import pype.lib +import openpype.api +import openpype.lib class ExtractScanlineExr(pyblish.api.InstancePlugin): @@ -45,7 +45,7 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): stagingdir = os.path.normpath(repre.get("stagingDir")) - oiio_tool_path = os.getenv("PYPE_OIIO_PATH", "") + oiio_tool_path = os.getenv("OPENPYPE_OIIO_PATH", "") if not os.path.exists(oiio_tool_path): self.log.error( "OIIO tool not found in {}".format(oiio_tool_path)) @@ -65,7 +65,7 @@ class ExtractScanlineExr(pyblish.api.InstancePlugin): subprocess_exr = " ".join(oiio_cmd) self.log.info(f"running: {subprocess_exr}") - pype.api.run_subprocess(subprocess_exr, logger=self.log) + openpype.api.run_subprocess(subprocess_exr, logger=self.log) # raise error if there is no ouptput if not os.path.exists(os.path.join(stagingdir, original_name)): diff --git a/pype/plugins/publish/integrate_ftrack_component_overwrite.py b/openpype/plugins/publish/integrate_ftrack_component_overwrite.py similarity index 100% rename from pype/plugins/publish/integrate_ftrack_component_overwrite.py rename to openpype/plugins/publish/integrate_ftrack_component_overwrite.py diff --git a/pype/plugins/publish/integrate_master_version.py b/openpype/plugins/publish/integrate_master_version.py similarity index 99% rename from pype/plugins/publish/integrate_master_version.py rename to openpype/plugins/publish/integrate_master_version.py index 7709f089fe..ec836954e8 100644 --- a/pype/plugins/publish/integrate_master_version.py +++ b/openpype/plugins/publish/integrate_master_version.py @@ -168,7 +168,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): "version_id": src_version_entity["_id"], "parent": src_version_entity["parent"], "type": "hero_version", - "schema": "pype:hero_version-1.0" + "schema": "openpype:hero_version-1.0" } schema.validate(new_hero_version) diff --git a/pype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py similarity index 99% rename from pype/plugins/publish/integrate_new.py rename to openpype/plugins/publish/integrate_new.py index d4a094a975..0d36828ccf 100644 --- a/pype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -13,7 +13,7 @@ from pymongo import DeleteOne, InsertOne import pyblish.api from avalon import io from avalon.vendor import filelink -import pype.api +import openpype.api from datetime import datetime # from pype.modules import ModulesManager @@ -501,7 +501,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): data.update({'path': dst, 'template': template}) representation = { "_id": repre_id, - "schema": "pype:representation-2.0", + "schema": "openpype:representation-2.0", "type": "representation", "parent": version_id, "name": repre['name'], @@ -685,7 +685,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families.append(_family) _id = io.insert_one({ - "schema": "pype:subset-3.0", + "schema": "openpype:subset-3.0", "type": "subset", "name": subset_name, "data": { @@ -726,7 +726,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dict: collection of data to create a version """ - return {"schema": "pype:version-3.0", + return {"schema": "openpype:version-3.0", "type": "version", "parent": subset["_id"], "name": version_number, @@ -912,7 +912,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): for _src, dest in resources: path = self.get_rootless_path(anatomy, dest) dest = self.get_dest_temp_url(dest) - file_hash = pype.api.source_hash(dest) + file_hash = openpype.api.source_hash(dest) if self.TMP_FILE_EXT and \ ',{}'.format(self.TMP_FILE_EXT) in file_hash: file_hash = file_hash.replace(',{}'.format(self.TMP_FILE_EXT), @@ -968,7 +968,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): ["global"] ["sync_server"]) - local_site_id = pype.api.get_local_site_id() + local_site_id = openpype.api.get_local_site_id() if sync_server_presets["enabled"]: local_site = sync_server_presets["config"].\ get("active_site", "studio").strip() diff --git a/pype/plugins/publish/integrate_resources_path.py b/openpype/plugins/publish/integrate_resources_path.py similarity index 100% rename from pype/plugins/publish/integrate_resources_path.py rename to openpype/plugins/publish/integrate_resources_path.py diff --git a/pype/plugins/publish/integrate_thumbnail.py b/openpype/plugins/publish/integrate_thumbnail.py similarity index 99% rename from pype/plugins/publish/integrate_thumbnail.py rename to openpype/plugins/publish/integrate_thumbnail.py index 23d2da5f4b..28a93efb9a 100644 --- a/pype/plugins/publish/integrate_thumbnail.py +++ b/openpype/plugins/publish/integrate_thumbnail.py @@ -130,7 +130,7 @@ class IntegrateThumbnails(pyblish.api.InstancePlugin): thumbnail_entity = { "_id": thumbnail_id, "type": "thumbnail", - "schema": "pype:thumbnail-1.0", + "schema": "openpype:thumbnail-1.0", "data": { "template": thumbnail_template, "template_data": repre_context diff --git a/pype/plugins/publish/repair_unicode_strings.py b/openpype/plugins/publish/repair_unicode_strings.py similarity index 100% rename from pype/plugins/publish/repair_unicode_strings.py rename to openpype/plugins/publish/repair_unicode_strings.py diff --git a/pype/plugins/publish/validate_containers.py b/openpype/plugins/publish/validate_containers.py similarity index 91% rename from pype/plugins/publish/validate_containers.py rename to openpype/plugins/publish/validate_containers.py index 1bf4967ec2..52df493451 100644 --- a/pype/plugins/publish/validate_containers.py +++ b/openpype/plugins/publish/validate_containers.py @@ -1,6 +1,6 @@ import pyblish.api -import pype.lib +import openpype.lib from avalon.tools import cbsceneinventory @@ -24,5 +24,5 @@ class ValidateContainers(pyblish.api.ContextPlugin): actions = [ShowInventory] def process(self, context): - if pype.lib.any_outdated(): + if openpype.lib.any_outdated(): raise ValueError("There are outdated containers in the scene.") diff --git a/pype/plugins/publish/validate_ffmpeg_installed.py b/openpype/plugins/publish/validate_ffmpeg_installed.py similarity index 91% rename from pype/plugins/publish/validate_ffmpeg_installed.py rename to openpype/plugins/publish/validate_ffmpeg_installed.py index 2951759fa4..a5390a07b2 100644 --- a/pype/plugins/publish/validate_ffmpeg_installed.py +++ b/openpype/plugins/publish/validate_ffmpeg_installed.py @@ -1,7 +1,7 @@ import pyblish.api import os import subprocess -import pype.lib +import openpype.lib try: import os.errno as errno except ImportError: @@ -27,7 +27,7 @@ class ValidateFFmpegInstalled(pyblish.api.ContextPlugin): return True def process(self, context): - ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") + ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") self.log.info("ffmpeg path: `{}`".format(ffmpeg_path)) if self.is_tool("{}".format(ffmpeg_path)) is False: self.log.error("ffmpeg not found in PATH") diff --git a/pype/plugins/publish/validate_file_saved.py b/openpype/plugins/publish/validate_file_saved.py similarity index 100% rename from pype/plugins/publish/validate_file_saved.py rename to openpype/plugins/publish/validate_file_saved.py diff --git a/pype/plugins/publish/validate_filesequences.py b/openpype/plugins/publish/validate_filesequences.py similarity index 100% rename from pype/plugins/publish/validate_filesequences.py rename to openpype/plugins/publish/validate_filesequences.py diff --git a/pype/plugins/publish/validate_instance_in_context.py b/openpype/plugins/publish/validate_instance_in_context.py similarity index 98% rename from pype/plugins/publish/validate_instance_in_context.py rename to openpype/plugins/publish/validate_instance_in_context.py index a4fc555161..29f002f142 100644 --- a/pype/plugins/publish/validate_instance_in_context.py +++ b/openpype/plugins/publish/validate_instance_in_context.py @@ -3,7 +3,7 @@ from __future__ import absolute_import import pyblish.api -import pype.api +import openpype.api class SelectInvalidInstances(pyblish.api.Action): @@ -120,7 +120,7 @@ class ValidateInstanceInContext(pyblish.api.InstancePlugin): Action on this validator will select invalid instances in Outliner. """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Instance in same Context" optional = True hosts = ["maya", "nuke"] diff --git a/pype/plugins/publish/validate_intent.py b/openpype/plugins/publish/validate_intent.py similarity index 100% rename from pype/plugins/publish/validate_intent.py rename to openpype/plugins/publish/validate_intent.py diff --git a/pype/plugins/publish/validate_resources.py b/openpype/plugins/publish/validate_resources.py similarity index 91% rename from pype/plugins/publish/validate_resources.py rename to openpype/plugins/publish/validate_resources.py index bc10d3003c..644977ecd4 100644 --- a/pype/plugins/publish/validate_resources.py +++ b/openpype/plugins/publish/validate_resources.py @@ -1,5 +1,5 @@ import pyblish.api -import pype.api +import openpype.api import os @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ - order = pype.api.ValidateContentsOrder + order = openpype.api.ValidateContentsOrder label = "Resources" def process(self, instance): diff --git a/pype/plugins/publish/validate_sequence_frames.py b/openpype/plugins/publish/validate_sequence_frames.py similarity index 100% rename from pype/plugins/publish/validate_sequence_frames.py rename to openpype/plugins/publish/validate_sequence_frames.py diff --git a/pype/plugins/publish/validate_version.py b/openpype/plugins/publish/validate_version.py similarity index 100% rename from pype/plugins/publish/validate_version.py rename to openpype/plugins/publish/validate_version.py diff --git a/pype/pype_commands.py b/openpype/pype_commands.py similarity index 86% rename from pype/pype_commands.py rename to openpype/pype_commands.py index ea94a35e3a..a2d97429d3 100644 --- a/pype/pype_commands.py +++ b/openpype/pype_commands.py @@ -5,8 +5,8 @@ import sys import json from pathlib import Path -from pype.lib import PypeLogger -from pype.api import get_app_environments_for_context +from openpype.lib import PypeLogger +from openpype.api import get_app_environments_for_context class PypeCommands: @@ -18,13 +18,13 @@ class PypeCommands: def launch_tray(debug=False): PypeLogger.set_process_name("Tray") - from pype.tools import tray + from openpype.tools import tray tray.main() @staticmethod def launch_settings_gui(dev): - from pype.tools import settings + from openpype.tools import settings # TODO change argument options to allow enum of user roles user_role = "developer" @@ -32,14 +32,14 @@ class PypeCommands: @staticmethod def launch_eventservercli(*args): - from pype.modules.ftrack.ftrack_server.event_server_cli import ( + from openpype.modules.ftrack.ftrack_server.event_server_cli import ( run_event_server ) return run_event_server(*args) @staticmethod def launch_standalone_publisher(): - from pype.tools import standalonepublish + from openpype.tools import standalonepublish standalonepublish.main() @staticmethod @@ -57,8 +57,8 @@ class PypeCommands: if not any(paths): raise RuntimeError("No publish paths specified") - from pype import install, uninstall - from pype.api import Logger + from openpype import install, uninstall + from openpype.api import Logger # Register target and host import pyblish.api @@ -79,7 +79,7 @@ class PypeCommands: pyblish.api.register_target("filesequence") pyblish.api.register_host("shell") - os.environ["PYPE_PUBLISH_DATA"] = os.pathsep.join(paths) + os.environ["OPENPYPE_PUBLISH_DATA"] = os.pathsep.join(paths) log.info("Running publish ...") @@ -98,7 +98,7 @@ class PypeCommands: def extractenvironments(output_json_path, project, asset, task, app): env = os.environ.copy() if all((project, asset, task, app)): - from pype.api import get_app_environments_for_context + from openpype.api import get_app_environments_for_context env = get_app_environments_for_context( project, asset, task, app, env ) diff --git a/pype/resources/__init__.py b/openpype/resources/__init__.py similarity index 76% rename from pype/resources/__init__.py rename to openpype/resources/__init__.py index 9adce2afe4..ef4ed73974 100644 --- a/pype/resources/__init__.py +++ b/openpype/resources/__init__.py @@ -32,21 +32,21 @@ def get_liberation_font_path(bold=False, italic=False): def pype_icon_filepath(debug=None): if debug is None: - debug = bool(os.getenv("PYPE_DEV")) + debug = bool(os.getenv("OPENPYPE_DEV")) if debug: - icon_file_name = "pype_icon_dev.png" + icon_file_name = "openpype_icon_staging.png" else: - icon_file_name = "pype_icon.png" + icon_file_name = "openpype_icon.png" return get_resource("icons", icon_file_name) def pype_splash_filepath(debug=None): if debug is None: - debug = bool(os.getenv("PYPE_DEV")) + debug = bool(os.getenv("OPENPYPE_DEV")) if debug: - splash_file_name = "pype_splash_dev.png" + splash_file_name = "openpype_splash_staging.png" else: - splash_file_name = "pype_splash.png" + splash_file_name = "openpype_splash.png" return get_resource("icons", splash_file_name) diff --git a/pype/resources/app_icons/Aport.png b/openpype/resources/app_icons/Aport.png similarity index 100% rename from pype/resources/app_icons/Aport.png rename to openpype/resources/app_icons/Aport.png diff --git a/pype/resources/app_icons/aftereffects.png b/openpype/resources/app_icons/aftereffects.png similarity index 100% rename from pype/resources/app_icons/aftereffects.png rename to openpype/resources/app_icons/aftereffects.png diff --git a/pype/hosts/premiere/ppro/img/blender.png b/openpype/resources/app_icons/blender.png similarity index 100% rename from pype/hosts/premiere/ppro/img/blender.png rename to openpype/resources/app_icons/blender.png diff --git a/pype/resources/app_icons/celaction_local.png b/openpype/resources/app_icons/celaction_local.png similarity index 100% rename from pype/resources/app_icons/celaction_local.png rename to openpype/resources/app_icons/celaction_local.png diff --git a/pype/resources/app_icons/celaction_remotel.png b/openpype/resources/app_icons/celaction_remotel.png similarity index 100% rename from pype/resources/app_icons/celaction_remotel.png rename to openpype/resources/app_icons/celaction_remotel.png diff --git a/pype/resources/app_icons/clockify-white.png b/openpype/resources/app_icons/clockify-white.png similarity index 100% rename from pype/resources/app_icons/clockify-white.png rename to openpype/resources/app_icons/clockify-white.png diff --git a/pype/resources/app_icons/clockify.png b/openpype/resources/app_icons/clockify.png similarity index 100% rename from pype/resources/app_icons/clockify.png rename to openpype/resources/app_icons/clockify.png diff --git a/pype/resources/app_icons/djvView.png b/openpype/resources/app_icons/djvView.png similarity index 100% rename from pype/resources/app_icons/djvView.png rename to openpype/resources/app_icons/djvView.png diff --git a/pype/resources/app_icons/fusion.png b/openpype/resources/app_icons/fusion.png similarity index 100% rename from pype/resources/app_icons/fusion.png rename to openpype/resources/app_icons/fusion.png diff --git a/pype/resources/app_icons/harmony.png b/openpype/resources/app_icons/harmony.png similarity index 100% rename from pype/resources/app_icons/harmony.png rename to openpype/resources/app_icons/harmony.png diff --git a/pype/resources/app_icons/hiero.png b/openpype/resources/app_icons/hiero.png similarity index 100% rename from pype/resources/app_icons/hiero.png rename to openpype/resources/app_icons/hiero.png diff --git a/pype/resources/app_icons/houdini.png b/openpype/resources/app_icons/houdini.png similarity index 100% rename from pype/resources/app_icons/houdini.png rename to openpype/resources/app_icons/houdini.png diff --git a/pype/resources/app_icons/maya.png b/openpype/resources/app_icons/maya.png similarity index 100% rename from pype/resources/app_icons/maya.png rename to openpype/resources/app_icons/maya.png diff --git a/pype/resources/app_icons/nuke.png b/openpype/resources/app_icons/nuke.png similarity index 100% rename from pype/resources/app_icons/nuke.png rename to openpype/resources/app_icons/nuke.png diff --git a/pype/resources/app_icons/nukex.png b/openpype/resources/app_icons/nukex.png similarity index 100% rename from pype/resources/app_icons/nukex.png rename to openpype/resources/app_icons/nukex.png diff --git a/pype/resources/app_icons/photoshop.png b/openpype/resources/app_icons/photoshop.png similarity index 100% rename from pype/resources/app_icons/photoshop.png rename to openpype/resources/app_icons/photoshop.png diff --git a/pype/resources/app_icons/premiere.png b/openpype/resources/app_icons/premiere.png similarity index 100% rename from pype/resources/app_icons/premiere.png rename to openpype/resources/app_icons/premiere.png diff --git a/pype/resources/app_icons/python.png b/openpype/resources/app_icons/python.png similarity index 100% rename from pype/resources/app_icons/python.png rename to openpype/resources/app_icons/python.png diff --git a/pype/resources/app_icons/resolve.png b/openpype/resources/app_icons/resolve.png similarity index 100% rename from pype/resources/app_icons/resolve.png rename to openpype/resources/app_icons/resolve.png diff --git a/pype/resources/app_icons/storyboardpro.png b/openpype/resources/app_icons/storyboardpro.png similarity index 100% rename from pype/resources/app_icons/storyboardpro.png rename to openpype/resources/app_icons/storyboardpro.png diff --git a/pype/resources/app_icons/tvpaint.png b/openpype/resources/app_icons/tvpaint.png similarity index 100% rename from pype/resources/app_icons/tvpaint.png rename to openpype/resources/app_icons/tvpaint.png diff --git a/pype/resources/app_icons/ue4.png b/openpype/resources/app_icons/ue4.png similarity index 100% rename from pype/resources/app_icons/ue4.png rename to openpype/resources/app_icons/ue4.png diff --git a/pype/resources/fonts/LiberationSans/LiberationSans-Bold.ttf b/openpype/resources/fonts/LiberationSans/LiberationSans-Bold.ttf similarity index 100% rename from pype/resources/fonts/LiberationSans/LiberationSans-Bold.ttf rename to openpype/resources/fonts/LiberationSans/LiberationSans-Bold.ttf diff --git a/pype/resources/fonts/LiberationSans/LiberationSans-BoldItalic.ttf b/openpype/resources/fonts/LiberationSans/LiberationSans-BoldItalic.ttf similarity index 100% rename from pype/resources/fonts/LiberationSans/LiberationSans-BoldItalic.ttf rename to openpype/resources/fonts/LiberationSans/LiberationSans-BoldItalic.ttf diff --git a/pype/resources/fonts/LiberationSans/LiberationSans-Italic.ttf b/openpype/resources/fonts/LiberationSans/LiberationSans-Italic.ttf similarity index 100% rename from pype/resources/fonts/LiberationSans/LiberationSans-Italic.ttf rename to openpype/resources/fonts/LiberationSans/LiberationSans-Italic.ttf diff --git a/pype/resources/fonts/LiberationSans/LiberationSans-Regular.ttf b/openpype/resources/fonts/LiberationSans/LiberationSans-Regular.ttf similarity index 100% rename from pype/resources/fonts/LiberationSans/LiberationSans-Regular.ttf rename to openpype/resources/fonts/LiberationSans/LiberationSans-Regular.ttf diff --git a/pype/resources/fonts/LiberationSans/License.txt b/openpype/resources/fonts/LiberationSans/License.txt similarity index 100% rename from pype/resources/fonts/LiberationSans/License.txt rename to openpype/resources/fonts/LiberationSans/License.txt diff --git a/pype/resources/ftrack/action_icons/ActionAskWhereIRun.svg b/openpype/resources/ftrack/action_icons/ActionAskWhereIRun.svg similarity index 100% rename from pype/resources/ftrack/action_icons/ActionAskWhereIRun.svg rename to openpype/resources/ftrack/action_icons/ActionAskWhereIRun.svg diff --git a/pype/resources/ftrack/action_icons/AssetsRemover.svg b/openpype/resources/ftrack/action_icons/AssetsRemover.svg similarity index 100% rename from pype/resources/ftrack/action_icons/AssetsRemover.svg rename to openpype/resources/ftrack/action_icons/AssetsRemover.svg diff --git a/openpype/resources/ftrack/action_icons/BatchTasks.svg b/openpype/resources/ftrack/action_icons/BatchTasks.svg new file mode 100644 index 0000000000..5cf5d423dd --- /dev/null +++ b/openpype/resources/ftrack/action_icons/BatchTasks.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/ComponentOpen.svg b/openpype/resources/ftrack/action_icons/ComponentOpen.svg new file mode 100644 index 0000000000..f549e6142b --- /dev/null +++ b/openpype/resources/ftrack/action_icons/ComponentOpen.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/CreateFolders.svg b/openpype/resources/ftrack/action_icons/CreateFolders.svg new file mode 100644 index 0000000000..18efc273aa --- /dev/null +++ b/openpype/resources/ftrack/action_icons/CreateFolders.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/CreateProjectFolders.svg b/openpype/resources/ftrack/action_icons/CreateProjectFolders.svg new file mode 100644 index 0000000000..0e5821b0be --- /dev/null +++ b/openpype/resources/ftrack/action_icons/CreateProjectFolders.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/DeleteAsset.svg b/openpype/resources/ftrack/action_icons/DeleteAsset.svg new file mode 100644 index 0000000000..855bdae7c5 --- /dev/null +++ b/openpype/resources/ftrack/action_icons/DeleteAsset.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/Delivery.svg b/openpype/resources/ftrack/action_icons/Delivery.svg new file mode 100644 index 0000000000..a6333333ae --- /dev/null +++ b/openpype/resources/ftrack/action_icons/Delivery.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/MultipleNotes.svg b/openpype/resources/ftrack/action_icons/MultipleNotes.svg new file mode 100644 index 0000000000..40113fc709 --- /dev/null +++ b/openpype/resources/ftrack/action_icons/MultipleNotes.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/OpenPypeAdmin.svg b/openpype/resources/ftrack/action_icons/OpenPypeAdmin.svg new file mode 100644 index 0000000000..c2abc6146f --- /dev/null +++ b/openpype/resources/ftrack/action_icons/OpenPypeAdmin.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/PrepareProject.svg b/openpype/resources/ftrack/action_icons/PrepareProject.svg new file mode 100644 index 0000000000..644d83f84d --- /dev/null +++ b/openpype/resources/ftrack/action_icons/PrepareProject.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pype/resources/ftrack/action_icons/RV.png b/openpype/resources/ftrack/action_icons/RV.png similarity index 100% rename from pype/resources/ftrack/action_icons/RV.png rename to openpype/resources/ftrack/action_icons/RV.png diff --git a/openpype/resources/ftrack/action_icons/SeedProject.svg b/openpype/resources/ftrack/action_icons/SeedProject.svg new file mode 100644 index 0000000000..ff818b5ecb --- /dev/null +++ b/openpype/resources/ftrack/action_icons/SeedProject.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/SortReview.svg b/openpype/resources/ftrack/action_icons/SortReview.svg new file mode 100644 index 0000000000..13a7def648 --- /dev/null +++ b/openpype/resources/ftrack/action_icons/SortReview.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/TestAction.svg b/openpype/resources/ftrack/action_icons/TestAction.svg new file mode 100644 index 0000000000..917ef2d0c7 --- /dev/null +++ b/openpype/resources/ftrack/action_icons/TestAction.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openpype/resources/ftrack/action_icons/Thumbnail.svg b/openpype/resources/ftrack/action_icons/Thumbnail.svg new file mode 100644 index 0000000000..9af330e79a --- /dev/null +++ b/openpype/resources/ftrack/action_icons/Thumbnail.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pype/resources/ftrack/sign_in_message.html b/openpype/resources/ftrack/sign_in_message.html similarity index 100% rename from pype/resources/ftrack/sign_in_message.html rename to openpype/resources/ftrack/sign_in_message.html diff --git a/openpype/resources/icons/circle_green.png b/openpype/resources/icons/circle_green.png new file mode 100644 index 0000000000..a81cfbfb5a Binary files /dev/null and b/openpype/resources/icons/circle_green.png differ diff --git a/pype/resources/icons/circle_orange.png b/openpype/resources/icons/circle_orange.png similarity index 100% rename from pype/resources/icons/circle_orange.png rename to openpype/resources/icons/circle_orange.png diff --git a/openpype/resources/icons/circle_red.png b/openpype/resources/icons/circle_red.png new file mode 100644 index 0000000000..a473cda123 Binary files /dev/null and b/openpype/resources/icons/circle_red.png differ diff --git a/openpype/resources/icons/circle_yellow.png b/openpype/resources/icons/circle_yellow.png new file mode 100644 index 0000000000..3f6bbaa1bd Binary files /dev/null and b/openpype/resources/icons/circle_yellow.png differ diff --git a/pype/resources/icons/folder-favorite.png b/openpype/resources/icons/folder-favorite.png similarity index 100% rename from pype/resources/icons/folder-favorite.png rename to openpype/resources/icons/folder-favorite.png diff --git a/pype/resources/icons/folder-favorite2.png b/openpype/resources/icons/folder-favorite2.png similarity index 100% rename from pype/resources/icons/folder-favorite2.png rename to openpype/resources/icons/folder-favorite2.png diff --git a/pype/resources/icons/folder-favorite3.png b/openpype/resources/icons/folder-favorite3.png similarity index 100% rename from pype/resources/icons/folder-favorite3.png rename to openpype/resources/icons/folder-favorite3.png diff --git a/openpype/resources/icons/inventory.png b/openpype/resources/icons/inventory.png new file mode 100644 index 0000000000..06c07c783e Binary files /dev/null and b/openpype/resources/icons/inventory.png differ diff --git a/openpype/resources/icons/loader.png b/openpype/resources/icons/loader.png new file mode 100644 index 0000000000..9787fef570 Binary files /dev/null and b/openpype/resources/icons/loader.png differ diff --git a/openpype/resources/icons/lookmanager.png b/openpype/resources/icons/lookmanager.png new file mode 100644 index 0000000000..2dde025430 Binary files /dev/null and b/openpype/resources/icons/lookmanager.png differ diff --git a/openpype/resources/icons/openpype_icon.png b/openpype/resources/icons/openpype_icon.png new file mode 100644 index 0000000000..41469a7399 Binary files /dev/null and b/openpype/resources/icons/openpype_icon.png differ diff --git a/openpype/resources/icons/openpype_icon_staging.png b/openpype/resources/icons/openpype_icon_staging.png new file mode 100644 index 0000000000..f1da43fad2 Binary files /dev/null and b/openpype/resources/icons/openpype_icon_staging.png differ diff --git a/openpype/resources/icons/openpype_rim.png b/openpype/resources/icons/openpype_rim.png new file mode 100644 index 0000000000..26e93e67d5 Binary files /dev/null and b/openpype/resources/icons/openpype_rim.png differ diff --git a/openpype/resources/icons/openpype_splash.png b/openpype/resources/icons/openpype_splash.png new file mode 100644 index 0000000000..191c70c88d Binary files /dev/null and b/openpype/resources/icons/openpype_splash.png differ diff --git a/openpype/resources/icons/openpype_splash_staging.png b/openpype/resources/icons/openpype_splash_staging.png new file mode 100644 index 0000000000..5a2db47385 Binary files /dev/null and b/openpype/resources/icons/openpype_splash_staging.png differ diff --git a/openpype/resources/icons/workfiles.png b/openpype/resources/icons/workfiles.png new file mode 100644 index 0000000000..179f5fae0f Binary files /dev/null and b/openpype/resources/icons/workfiles.png differ diff --git a/pype/scripts/__init__.py b/openpype/scripts/__init__.py similarity index 100% rename from pype/scripts/__init__.py rename to openpype/scripts/__init__.py diff --git a/pype/scripts/export_maya_ass_job.py b/openpype/scripts/export_maya_ass_job.py similarity index 88% rename from pype/scripts/export_maya_ass_job.py rename to openpype/scripts/export_maya_ass_job.py index d343eec131..6e5eff6663 100644 --- a/pype/scripts/export_maya_ass_job.py +++ b/openpype/scripts/export_maya_ass_job.py @@ -1,4 +1,8 @@ -"""This module is used for command line exporting of ASS files.""" +"""This module is used for command line exporting of ASS files. + +WARNING: +This need to be rewriten to be able use it in Pype 3! +""" import os import argparse @@ -45,10 +49,10 @@ def __main__(): auto_pype_root = os.path.dirname(os.path.abspath(__file__)) auto_pype_root = os.path.abspath(auto_pype_root + "../../../../..") - auto_pype_root = os.environ.get('PYPE_SETUP_PATH') or auto_pype_root - if os.environ.get('PYPE_SETUP_PATH'): + auto_pype_root = os.environ.get('OPENPYPE_SETUP_PATH') or auto_pype_root + if os.environ.get('OPENPYPE_SETUP_PATH'): print("Got Pype location from environment: {}".format( - os.environ.get('PYPE_SETUP_PATH'))) + os.environ.get('OPENPYPE_SETUP_PATH'))) pype_command = "pype.ps1" if platform.system().lower() == "linux": @@ -74,7 +78,7 @@ def __main__(): print("Set pype root to: {}".format(pype_root)) print("Paths: {}".format(kwargs.paths or [os.getcwd()])) - # paths = kwargs.paths or [os.environ.get("PYPE_METADATA_FILE")] or [os.getcwd()] # noqa + # paths = kwargs.paths or [os.environ.get("OPENPYPE_METADATA_FILE")] or [os.getcwd()] # noqa mayabatch = os.environ.get("AVALON_APP_NAME").replace("maya", "mayabatch") args = [ diff --git a/pype/scripts/export_maya_ass_sequence.mel b/openpype/scripts/export_maya_ass_sequence.mel similarity index 81% rename from pype/scripts/export_maya_ass_sequence.mel rename to openpype/scripts/export_maya_ass_sequence.mel index 83d1d010ac..b3b9a8543e 100644 --- a/pype/scripts/export_maya_ass_sequence.mel +++ b/openpype/scripts/export_maya_ass_sequence.mel @@ -12,12 +12,12 @@ Attributes: */ -$scene_file=`getenv "PYPE_ASS_EXPORT_SCENE_FILE"`; -$step=`getenv "PYPE_ASS_EXPORT_STEP"`; -$start=`getenv "PYPE_ASS_EXPORT_START"`; -$end=`getenv "PYPE_ASS_EXPORT_END"`; -$file_path=`getenv "PYPE_ASS_EXPORT_OUTPUT"`; -$render_layer = `getenv "PYPE_ASS_EXPORT_RENDER_LAYER"`; +$scene_file=`getenv "OPENPYPE_ASS_EXPORT_SCENE_FILE"`; +$step=`getenv "OPENPYPE_ASS_EXPORT_STEP"`; +$start=`getenv "OPENPYPE_ASS_EXPORT_START"`; +$end=`getenv "OPENPYPE_ASS_EXPORT_END"`; +$file_path=`getenv "OPENPYPE_ASS_EXPORT_OUTPUT"`; +$render_layer = `getenv "OPENPYPE_ASS_EXPORT_RENDER_LAYER"`; print("*** ASS Export Plugin\n"); diff --git a/pype/scripts/fusion_switch_shot.py b/openpype/scripts/fusion_switch_shot.py similarity index 98% rename from pype/scripts/fusion_switch_shot.py rename to openpype/scripts/fusion_switch_shot.py index 5791220acd..26f5356336 100644 --- a/pype/scripts/fusion_switch_shot.py +++ b/openpype/scripts/fusion_switch_shot.py @@ -8,8 +8,8 @@ from avalon import api, io, pipeline import avalon.fusion # Config imports -import pype.lib as pype -import pype.hosts.fusion.lib as fusion_lib +import openpype.lib as pype +import openpype.hosts.fusion.lib as fusion_lib log = logging.getLogger("Update Slap Comp") diff --git a/pype/scripts/non_python_host_launch.py b/openpype/scripts/non_python_host_launch.py similarity index 100% rename from pype/scripts/non_python_host_launch.py rename to openpype/scripts/non_python_host_launch.py diff --git a/pype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py similarity index 99% rename from pype/scripts/otio_burnin.py rename to openpype/scripts/otio_burnin.py index dec1ad1dbd..5e2a22f1b5 100644 --- a/pype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -5,12 +5,12 @@ import subprocess import platform import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins -from pype.api import resources -import pype.lib +from openpype.api import resources +import openpype.lib -ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg") -ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") +ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg") +ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe") FFMPEG = ( diff --git a/pype/scripts/slates/__init__.py b/openpype/scripts/slates/__init__.py similarity index 100% rename from pype/scripts/slates/__init__.py rename to openpype/scripts/slates/__init__.py diff --git a/pype/scripts/slates/__main__.py b/openpype/scripts/slates/__main__.py similarity index 100% rename from pype/scripts/slates/__main__.py rename to openpype/scripts/slates/__main__.py diff --git a/pype/scripts/slates/slate_base/__init__.py b/openpype/scripts/slates/slate_base/__init__.py similarity index 100% rename from pype/scripts/slates/slate_base/__init__.py rename to openpype/scripts/slates/slate_base/__init__.py diff --git a/pype/scripts/slates/slate_base/api.py b/openpype/scripts/slates/slate_base/api.py similarity index 100% rename from pype/scripts/slates/slate_base/api.py rename to openpype/scripts/slates/slate_base/api.py diff --git a/pype/scripts/slates/slate_base/base.py b/openpype/scripts/slates/slate_base/base.py similarity index 100% rename from pype/scripts/slates/slate_base/base.py rename to openpype/scripts/slates/slate_base/base.py diff --git a/pype/scripts/slates/slate_base/default_style.json b/openpype/scripts/slates/slate_base/default_style.json similarity index 100% rename from pype/scripts/slates/slate_base/default_style.json rename to openpype/scripts/slates/slate_base/default_style.json diff --git a/pype/scripts/slates/slate_base/example.py b/openpype/scripts/slates/slate_base/example.py similarity index 100% rename from pype/scripts/slates/slate_base/example.py rename to openpype/scripts/slates/slate_base/example.py diff --git a/pype/scripts/slates/slate_base/font_factory.py b/openpype/scripts/slates/slate_base/font_factory.py similarity index 100% rename from pype/scripts/slates/slate_base/font_factory.py rename to openpype/scripts/slates/slate_base/font_factory.py diff --git a/pype/scripts/slates/slate_base/items.py b/openpype/scripts/slates/slate_base/items.py similarity index 100% rename from pype/scripts/slates/slate_base/items.py rename to openpype/scripts/slates/slate_base/items.py diff --git a/pype/scripts/slates/slate_base/layer.py b/openpype/scripts/slates/slate_base/layer.py similarity index 100% rename from pype/scripts/slates/slate_base/layer.py rename to openpype/scripts/slates/slate_base/layer.py diff --git a/pype/scripts/slates/slate_base/lib.py b/openpype/scripts/slates/slate_base/lib.py similarity index 100% rename from pype/scripts/slates/slate_base/lib.py rename to openpype/scripts/slates/slate_base/lib.py diff --git a/pype/scripts/slates/slate_base/main_frame.py b/openpype/scripts/slates/slate_base/main_frame.py similarity index 100% rename from pype/scripts/slates/slate_base/main_frame.py rename to openpype/scripts/slates/slate_base/main_frame.py diff --git a/pype/settings/__init__.py b/openpype/settings/__init__.py similarity index 100% rename from pype/settings/__init__.py rename to openpype/settings/__init__.py diff --git a/pype/settings/constants.py b/openpype/settings/constants.py similarity index 96% rename from pype/settings/constants.py rename to openpype/settings/constants.py index f6077e826e..a53e88a91e 100644 --- a/pype/settings/constants.py +++ b/openpype/settings/constants.py @@ -15,6 +15,7 @@ METADATA_KEYS = ( ) # File where studio's system overrides are stored +GLOBAL_SETTINGS_KEY = "global_settings" SYSTEM_SETTINGS_KEY = "system_settings" PROJECT_SETTINGS_KEY = "project_settings" PROJECT_ANATOMY_KEY = "project_anatomy" diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json new file mode 100644 index 0000000000..987021f25b --- /dev/null +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -0,0 +1,26 @@ +{ + "fps": 25.0, + "frameStart": 1001, + "frameEnd": 1001, + "clipIn": 1, + "clipOut": 1, + "handleStart": 0, + "handleEnd": 0, + "resolutionWidth": 1920, + "resolutionHeight": 1080, + "pixelAspect": 1.0, + "applications": [ + "maya_2020", + "nuke_12-2", + "nukex_12-2", + "hiero_12-2", + "resolve_16", + "houdini_18-5", + "blender_2-90", + "harmony_20", + "photoshop_2021", + "aftereffects_2021", + "unreal_4-24" + ], + "tools_env": [] +} \ No newline at end of file diff --git a/pype/settings/defaults/project_anatomy/imageio.json b/openpype/settings/defaults/project_anatomy/imageio.json similarity index 100% rename from pype/settings/defaults/project_anatomy/imageio.json rename to openpype/settings/defaults/project_anatomy/imageio.json diff --git a/pype/settings/defaults/project_anatomy/roots.json b/openpype/settings/defaults/project_anatomy/roots.json similarity index 100% rename from pype/settings/defaults/project_anatomy/roots.json rename to openpype/settings/defaults/project_anatomy/roots.json diff --git a/pype/settings/defaults/project_anatomy/tasks.json b/openpype/settings/defaults/project_anatomy/tasks.json similarity index 100% rename from pype/settings/defaults/project_anatomy/tasks.json rename to openpype/settings/defaults/project_anatomy/tasks.json diff --git a/pype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json similarity index 100% rename from pype/settings/defaults/project_anatomy/templates.json rename to openpype/settings/defaults/project_anatomy/templates.json diff --git a/pype/settings/defaults/project_settings/celaction.json b/openpype/settings/defaults/project_settings/celaction.json similarity index 100% rename from pype/settings/defaults/project_settings/celaction.json rename to openpype/settings/defaults/project_settings/celaction.json diff --git a/pype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json similarity index 95% rename from pype/settings/defaults/project_settings/deadline.json rename to openpype/settings/defaults/project_settings/deadline.json index 6d36f38423..9ff551491c 100644 --- a/pype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -20,7 +20,8 @@ "primary_pool": "", "secondary_pool": "", "group": "", - "department": "" + "department": "", + "limit_groups": {} }, "HarmonySubmitDeadline": { "enabled": true, diff --git a/pype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json similarity index 100% rename from pype/settings/defaults/project_settings/ftrack.json rename to openpype/settings/defaults/project_settings/ftrack.json diff --git a/pype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json similarity index 99% rename from pype/settings/defaults/project_settings/global.json rename to openpype/settings/defaults/project_settings/global.json index c8eea42134..8081f92ef7 100644 --- a/pype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -1,10 +1,4 @@ { - "project_plugins": { - "windows": [], - "darwin": [], - "linux": [] - }, - "project_environments": {}, "publish": { "IntegrateHeroVersion": { "enabled": true @@ -220,5 +214,11 @@ } } } - } + }, + "project_plugins": { + "windows": [], + "darwin": [], + "linux": [] + }, + "project_environments": {} } \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/harmony.json b/openpype/settings/defaults/project_settings/harmony.json similarity index 100% rename from pype/settings/defaults/project_settings/harmony.json rename to openpype/settings/defaults/project_settings/harmony.json diff --git a/pype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json similarity index 100% rename from pype/settings/defaults/project_settings/hiero.json rename to openpype/settings/defaults/project_settings/hiero.json diff --git a/pype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json similarity index 95% rename from pype/settings/defaults/project_settings/maya.json rename to openpype/settings/defaults/project_settings/maya.json index 6945bb0581..feddd2860a 100644 --- a/pype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -459,39 +459,6 @@ 0.8, 0.5 ] - }, - "ReferenceLoader": { - "enabled": true, - "representations": [ - "ma", - "mb", - "abc", - "fbx" - ] - }, - "AudioLoader": { - "enabled": true, - "representations": [ - "wav" - ] - }, - "GpuCacheLoader": { - "enabled": true, - "representations": [ - "abc" - ] - }, - "ImagePlaneLoader": { - "enabled": true, - "representations": [ - "jpg", - "png", - "mov" - ] - }, - "MatchmoveLoader": { - "enabled": true, - "representations": [] } }, "workfile_build": { diff --git a/pype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json similarity index 74% rename from pype/settings/defaults/project_settings/nuke.json rename to openpype/settings/defaults/project_settings/nuke.json index d727a6ba1e..0173eb0a82 100644 --- a/pype/settings/defaults/project_settings/nuke.json +++ b/openpype/settings/defaults/project_settings/nuke.json @@ -1,6 +1,6 @@ { - "menu": { - "Pype": { + "general": { + "menu": { "create": "ctrl+shift+alt+c", "publish": "ctrl+alt+p", "load": "ctrl+alt+l", @@ -95,20 +95,60 @@ "load": { "LoadImage": { "enabled": true, - "representations": [] + "families": [ + "render2d", + "source", + "plate", + "render", + "prerender", + "review", + "image" + ], + "representations": [ + "exr", + "dpx", + "jpg", + "jpeg", + "png", + "psd" + ], + "node_name_template": "{class_name}_{ext}" }, "LoadMov": { "enabled": true, - "representations": [] + "families": [ + "source", + "plate", + "render", + "prerender", + "review" + ], + "representations": [ + "mov", + "review", + "mp4", + "h264" + ], + "node_name_template": "{class_name}_{ext}" }, "LoadSequence": { "enabled": true, + "families": [ + "render2d", + "source", + "plate", + "render", + "prerender", + "review" + ], "representations": [ - "png", - "jpg", "exr", - "" - ] + "dpx", + "jpg", + "jpeg", + "png" + ], + "node_name_template": "{class_name}_{ext}" } }, "workfile_build": { diff --git a/pype/settings/defaults/project_settings/resolve.json b/openpype/settings/defaults/project_settings/resolve.json similarity index 100% rename from pype/settings/defaults/project_settings/resolve.json rename to openpype/settings/defaults/project_settings/resolve.json diff --git a/pype/settings/defaults/project_settings/standalonepublisher.json b/openpype/settings/defaults/project_settings/standalonepublisher.json similarity index 100% rename from pype/settings/defaults/project_settings/standalonepublisher.json rename to openpype/settings/defaults/project_settings/standalonepublisher.json diff --git a/pype/settings/defaults/project_settings/unreal.json b/openpype/settings/defaults/project_settings/unreal.json similarity index 100% rename from pype/settings/defaults/project_settings/unreal.json rename to openpype/settings/defaults/project_settings/unreal.json diff --git a/pype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json similarity index 66% rename from pype/settings/defaults/system_settings/applications.json rename to openpype/settings/defaults/system_settings/applications.json index ea910e125d..e7c505f184 100644 --- a/pype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -6,9 +6,9 @@ "host_name": "maya", "environment": { "PYTHONPATH": [ - "{PYPE_ROOT}/pype/hosts/maya/startup", - "{PYPE_REPOS_ROOT}/avalon-core/setup/maya", - "{PYPE_REPOS_ROOT}/maya-look-assigner", + "{OPENPYPE_ROOT}/pype/hosts/maya/startup", + "{OPENPYPE_ROOT}/repos/avalon-core/setup/maya", + "{OPENPYPE_ROOT}/repos/maya-look-assigner", "{PYTHONPATH}" ], "MAYA_DISABLE_CLIC_IPM": "Yes", @@ -16,25 +16,12 @@ "MAYA_DISABLE_CER": "Yes", "PYMEL_SKIP_MEL_INIT": "Yes", "LC_ALL": "C", - "PYPE_LOG_NO_COLORS": "Yes", - "__environment_keys__": { - "maya": [ - "PYTHONPATH", - "MAYA_DISABLE_CLIC_IPM", - "MAYA_DISABLE_CIP", - "MAYA_DISABLE_CER", - "PYMEL_SKIP_MEL_INIT", - "LC_ALL", - "PYPE_LOG_NO_COLORS" - ] - } + "OPENPYPE_LOG_NO_COLORS": "Yes" }, "variants": { "maya_2020": { "enabled": true, - "label": "", "variant_label": "2020", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2020\\bin\\maya.exe" @@ -50,19 +37,12 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2020", - "__environment_keys__": { - "maya_2020": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2020" } }, "maya_2019": { "enabled": true, - "label": "", "variant_label": "2019", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2019\\bin\\maya.exe" @@ -78,19 +58,12 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2019", - "__environment_keys__": { - "maya_2019": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2019" } }, "maya_2018": { "enabled": true, - "label": "", "variant_label": "2018", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2018\\bin\\maya.exe" @@ -106,12 +79,7 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2018", - "__environment_keys__": { - "maya_2018": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2018" } } } @@ -123,8 +91,8 @@ "host_name": "maya", "environment": { "PYTHONPATH": [ - "{PYPE_REPOS_ROOT}/avalon-core/setup/maya", - "{PYPE_REPOS_ROOT}/maya-look-assigner", + "{OPENPYPE_ROOT}/avalon-core/setup/maya", + "{OPENPYPE_ROOT}/maya-look-assigner", "{PYTHON_ENV}/python2/Lib/site-packages", "{PYTHONPATH}" ], @@ -133,27 +101,13 @@ "MAYA_DISABLE_CER": "Yes", "PYMEL_SKIP_MEL_INIT": "Yes", "LC_ALL": "C", - "PYPE_LOG_NO_COLORS": "Yes", - "MAYA_TEST": "{MAYA_VERSION}", - "__environment_keys__": { - "mayabatch": [ - "PYTHONPATH", - "MAYA_DISABLE_CLIC_IPM", - "MAYA_DISABLE_CIP", - "MAYA_DISABLE_CER", - "PYMEL_SKIP_MEL_INIT", - "LC_ALL", - "PYPE_LOG_NO_COLORS", - "MAYA_TEST" - ] - } + "OPENPYPE_LOG_NO_COLORS": "Yes", + "MAYA_TEST": "{MAYA_VERSION}" }, "variants": { "mayabatch_2020": { "enabled": true, - "label": "", "variant_label": "2020", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2020\\bin\\mayabatch.exe" @@ -167,19 +121,12 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2020", - "__environment_keys__": { - "mayabatch_2020": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2020" } }, "mayabatch_2019": { "enabled": true, - "label": "", "variant_label": "2019", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2019\\bin\\mayabatch.exe" @@ -193,19 +140,12 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2019", - "__environment_keys__": { - "mayabatch_2019": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2019" } }, "mayabatch_2018": { "enabled": true, - "label": "", "variant_label": "2018", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Autodesk\\Maya2018\\bin\\mayabatch.exe" @@ -219,12 +159,7 @@ "linux": [] }, "environment": { - "MAYA_VERSION": "2018", - "__environment_keys__": { - "mayabatch_2018": [ - "MAYA_VERSION" - ] - } + "MAYA_VERSION": "2018" } } } @@ -236,28 +171,19 @@ "host_name": "nuke", "environment": { "NUKE_PATH": [ - "{PYPE_REPOS_ROOT}/avalon-core/setup/nuke/nuke_path", - "{PYPE_ROOT}/pype/hosts/nuke/startup", - "{PYPE_STUDIO_PLUGINS}/nuke" + "{OPENPYPE_ROOT}/repos/avalon-core/setup/nuke/nuke_path", + "{OPENPYPE_ROOT}/openpype/hosts/nuke/startup", + "{OPENPYPE_STUDIO_PLUGINS}/nuke" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" }, - "LOGLEVEL": "DEBUG", - "__environment_keys__": { - "nuke": [ - "NUKE_PATH", - "PATH", - "LOGLEVEL" - ] - } + "LOGLEVEL": "DEBUG" }, "variants": { "nuke_12-2": { "enabled": true, - "label": "", "variant_label": "12.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" @@ -272,17 +198,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "nuke_12-2": [] - } - } + "environment": {} }, "nuke_12-0": { "enabled": true, - "label": "", "variant_label": "12.0", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" @@ -297,17 +217,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "nuke_12-0": [] - } - } + "environment": {} }, "nuke_11-3": { "enabled": true, - "label": "", "variant_label": "11.3", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" @@ -322,17 +236,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "nuke_11-3": [] - } - } + "environment": {} }, "nuke_11-2": { "enabled": true, - "label": "", "variant_label": "11.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" @@ -345,11 +253,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "nuke_11-2": [] - } - } + "environment": {} } } }, @@ -360,28 +264,19 @@ "host_name": "nuke", "environment": { "NUKE_PATH": [ - "{PYPE_REPOS_ROOT}/avalon-core/setup/nuke/nuke_path", - "{PYPE_ROOT}/pype/hosts/nuke/startup", - "{PYPE_STUDIO_PLUGINS}/nuke" + "{OPENPYPE_ROOT}/repos/avalon-core/setup/nuke/nuke_path", + "{OPENPYPE_ROOT}/openpype/hosts/nuke/startup", + "{OPENPYPE_STUDIO_PLUGINS}/nuke" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" }, - "LOGLEVEL": "DEBUG", - "__environment_keys__": { - "nukex": [ - "NUKE_PATH", - "PATH", - "LOGLEVEL" - ] - } + "LOGLEVEL": "DEBUG" }, "variants": { "nukex_12-2": { "enabled": true, - "label": "", "variant_label": "12.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" @@ -402,17 +297,11 @@ "--nukex" ] }, - "environment": { - "__environment_keys__": { - "nukex_12-2": [] - } - } + "environment": {} }, "nukex_12-0": { "enabled": true, - "label": "", "variant_label": "12.0", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" @@ -433,17 +322,11 @@ "--nukex" ] }, - "environment": { - "__environment_keys__": { - "nukex_12-0": [] - } - } + "environment": {} }, "nukex_11-3": { "enabled": true, - "label": "", "variant_label": "11.3", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" @@ -464,17 +347,11 @@ "--nukex" ] }, - "environment": { - "__environment_keys__": { - "nukex_11-3": [] - } - } + "environment": {} }, "nukex_11-2": { "enabled": true, - "label": "", "variant_label": "11.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" @@ -493,11 +370,7 @@ "--nukex" ] }, - "environment": { - "__environment_keys__": { - "nukex_11-2": [] - } - } + "environment": {} } } }, @@ -508,30 +381,19 @@ "host_name": "hiero", "environment": { "HIERO_PLUGIN_PATH": [ - "{PYPE_ROOT}/pype/hosts/hiero/startup" + "{OPENPYPE_ROOT}/openpype/hosts/hiero/startup" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" }, "WORKFILES_STARTUP": "0", "TAG_ASSETBUILD_STARTUP": "0", - "LOGLEVEL": "DEBUG", - "__environment_keys__": { - "nukestudio": [ - "HIERO_PLUGIN_PATH", - "PATH", - "WORKFILES_STARTUP", - "TAG_ASSETBUILD_STARTUP", - "LOGLEVEL" - ] - } + "LOGLEVEL": "DEBUG" }, "variants": { "nukestudio_12-2": { "enabled": true, - "label": "", "variant_label": "12.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" @@ -552,17 +414,11 @@ "--studio" ] }, - "environment": { - "__environment_keys__": { - "nukestudio_12-2": [] - } - } + "environment": {} }, "nukestudio_12-0": { "enabled": true, - "label": "", "variant_label": "12.0", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" @@ -583,17 +439,11 @@ "--studio" ] }, - "environment": { - "__environment_keys__": { - "nukestudio_12-0": [] - } - } + "environment": {} }, "nukestudio_11-3": { "enabled": true, - "label": "", "variant_label": "11.3", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" @@ -614,17 +464,11 @@ "--studio" ] }, - "environment": { - "__environment_keys__": { - "nukestudio_11-3": [] - } - } + "environment": {} }, "nukestudio_11-2": { "enabled": true, - "label": "", "variant_label": "11.2", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -641,11 +485,7 @@ "--studio" ] }, - "environment": { - "__environment_keys__": { - "nukestudio_11-2": [] - } - } + "environment": {} } } }, @@ -656,30 +496,19 @@ "host_name": "hiero", "environment": { "HIERO_PLUGIN_PATH": [ - "{PYPE_ROOT}/pype/hosts/hiero/startup" + "{OPENPYPE_ROOT}/openpype/hosts/hiero/startup" ], "PATH": { "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" }, "WORKFILES_STARTUP": "0", "TAG_ASSETBUILD_STARTUP": "0", - "LOGLEVEL": "DEBUG", - "__environment_keys__": { - "hiero": [ - "HIERO_PLUGIN_PATH", - "PATH", - "WORKFILES_STARTUP", - "TAG_ASSETBUILD_STARTUP", - "LOGLEVEL" - ] - } + "LOGLEVEL": "DEBUG" }, "variants": { "hiero_12-2": { "enabled": true, - "label": "", "variant_label": "12.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.2v3\\Nuke12.2.exe" @@ -700,17 +529,11 @@ "--hiero" ] }, - "environment": { - "__environment_keys__": { - "hiero_12-2": [] - } - } + "environment": {} }, "hiero_12-0": { "enabled": true, - "label": "", "variant_label": "12.0", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke12.0v1\\Nuke12.0.exe" @@ -731,17 +554,11 @@ "--hiero" ] }, - "environment": { - "__environment_keys__": { - "hiero_12-0": [] - } - } + "environment": {} }, "hiero_11-3": { "enabled": true, - "label": "", "variant_label": "11.3", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.3v1\\Nuke11.3.exe" @@ -762,17 +579,11 @@ "--hiero" ] }, - "environment": { - "__environment_keys__": { - "hiero_11-3": [] - } - } + "environment": {} }, "hiero_11-2": { "enabled": true, - "label": "", "variant_label": "11.2", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Nuke11.2v2\\Nuke11.2.exe" @@ -791,11 +602,7 @@ "--hiero" ] }, - "environment": { - "__environment_keys__": { - "hiero_11-2": [] - } - } + "environment": {} } } }, @@ -826,24 +633,12 @@ "{PYTHON36}/Scripts", "{PATH}" ], - "PYPE_LOG_NO_COLORS": "Yes", - "__environment_keys__": { - "fusion": [ - "FUSION_UTILITY_SCRIPTS_SOURCE_DIR", - "FUSION_UTILITY_SCRIPTS_DIR", - "PYTHON36", - "PYTHONPATH", - "PATH", - "PYPE_LOG_NO_COLORS" - ] - } + "OPENPYPE_LOG_NO_COLORS": "Yes" }, "variants": { "fusion_16": { "enabled": true, - "label": "", "variant_label": "16", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -854,17 +649,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "fusion_16": [] - } - } + "environment": {} }, "fusion_9": { "enabled": true, - "label": "", "variant_label": "9", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Blackmagic Design\\Fusion 9\\Fusion.exe" @@ -877,11 +666,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "fusion_9": [] - } - } + "environment": {} } } }, @@ -924,30 +709,14 @@ "{PYTHON36_RESOLVE}/Scripts", "{PATH}" ], - "PRE_PYTHON_SCRIPT": "{PYPE_ROOT}/pype/resolve/preload_console.py", - "PYPE_LOG_NO_COLORS": "True", - "RESOLVE_DEV": "True", - "__environment_keys__": { - "resolve": [ - "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR", - "RESOLVE_SCRIPT_API", - "RESOLVE_SCRIPT_LIB", - "RESOLVE_UTILITY_SCRIPTS_DIR", - "PYTHON36_RESOLVE", - "PYTHONPATH", - "PATH", - "PRE_PYTHON_SCRIPT", - "PYPE_LOG_NO_COLORS", - "RESOLVE_DEV" - ] - } + "PRE_PYTHON_SCRIPT": "{OPENPYPE_ROOT}/openpype/resolve/preload_console.py", + "OPENPYPE_LOG_NO_COLORS": "True", + "RESOLVE_DEV": "True" }, "variants": { "resolve_16": { "enabled": true, - "label": "", "variant_label": "16", - "icon": "", "executables": { "windows": [ "C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" @@ -960,11 +729,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "resolve_16": [] - } - } + "environment": {} } } }, @@ -975,28 +740,20 @@ "host_name": "houdini", "environment": { "HOUDINI_PATH": { - "darwin": "{PYPE_ROOT}/pype/hosts/houdini/startup:&", - "linux": "{PYPE_ROOT}/pype/hosts/houdini/startup:&", - "windows": "{PYPE_ROOT}/pype/hosts/houdini/startup;&" + "darwin": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup:&", + "linux": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup:&", + "windows": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup;&" }, "HOUDINI_MENU_PATH": { - "darwin": "{PYPE_ROOT}/pype/hosts/houdini/startup:&", - "linux": "{PYPE_ROOT}/pype/hosts/houdini/startup:&", - "windows": "{PYPE_ROOT}/pype/hosts/houdini/startup;&" - }, - "__environment_keys__": { - "houdini": [ - "HOUDINI_PATH", - "HOUDINI_MENU_PATH" - ] + "darwin": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup:&", + "linux": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup:&", + "windows": "{OPENPYPE_ROOT}/openpype/hosts/houdini/startup;&" } }, "variants": { "houdini_18-5": { "enabled": true, - "label": "", "variant_label": "18.5", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Side Effects Software\\Houdini 18.5.499\\bin\\houdini.exe" @@ -1009,17 +766,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "houdini_18-5": [] - } - } + "environment": {} }, "houdini_18": { "enabled": true, - "label": "", "variant_label": "18", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1030,17 +781,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "houdini_18": [] - } - } + "environment": {} }, "houdini_17": { "enabled": true, - "label": "", "variant_label": "17", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1051,11 +796,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "houdini_17": [] - } - } + "environment": {} } } }, @@ -1065,26 +806,17 @@ "icon": "{}/app_icons/blender.png", "host_name": "blender", "environment": { - "BLENDER_USER_SCRIPTS": "{PYPE_REPOS_ROOT}/avalon-core/setup/blender", + "BLENDER_USER_SCRIPTS": "{OPENPYPE_ROOT}/repos/avalon-core/setup/blender", "PYTHONPATH": [ - "{PYPE_REPOS_ROOT}/avalon-core/setup/blender", + "{OPENPYPE_ROOT}/repos/avalon-core/setup/blender", "{PYTHONPATH}" ], - "CREATE_NEW_CONSOLE": "yes", - "__environment_keys__": { - "blender": [ - "BLENDER_USER_SCRIPTS", - "PYTHONPATH", - "CREATE_NEW_CONSOLE" - ] - } + "CREATE_NEW_CONSOLE": "yes" }, "variants": { "blender_2-83": { "enabled": true, - "label": "", "variant_label": "2.83", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Blender Foundation\\Blender 2.83\\blender.exe" @@ -1103,17 +835,11 @@ "--python-use-system-env" ] }, - "environment": { - "__environment_keys__": { - "blender_2-83": [] - } - } + "environment": {} }, "blender_2-90": { "enabled": true, - "label": "", "variant_label": "2.90", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Blender Foundation\\Blender 2.90\\blender.exe" @@ -1132,11 +858,7 @@ "--python-use-system-env" ] }, - "environment": { - "__environment_keys__": { - "blender_2-90": [] - } - } + "environment": {} } } }, @@ -1147,20 +869,12 @@ "host_name": "harmony", "environment": { "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1", - "LIB_OPENHARMONY_PATH": "{PYPE_ROOT}/pype/vendor/OpenHarmony", - "__environment_keys__": { - "harmony": [ - "AVALON_HARMONY_WORKFILES_ON_LAUNCH", - "LIB_OPENHARMONY_PATH" - ] - } + "LIB_OPENHARMONY_PATH": "{OPENPYPE_ROOT}/pype/vendor/OpenHarmony" }, "variants": { "harmony_20": { "enabled": true, - "label": "", "variant_label": "20", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1171,17 +885,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "harmony_20": [] - } - } + "environment": {} }, "harmony_17": { "enabled": true, - "label": "", "variant_label": "17", - "icon": "", "executables": { "windows": [], "darwin": [ @@ -1194,11 +902,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "harmony_17": [] - } - } + "environment": {} } } }, @@ -1208,19 +912,12 @@ "icon": "{}/app_icons/tvpaint.png", "host_name": "tvpaint", "environment": { - "PYPE_LOG_NO_COLORS": "True", - "__environment_keys__": { - "tvpaint": [ - "PYPE_LOG_NO_COLORS" - ] - } + "OPENPYPE_LOG_NO_COLORS": "True" }, "variants": { "tvpaint_animation_11-64bits": { "enabled": true, - "label": "", "variant_label": "11 (64bits)", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\TVPaint Developpement\\TVPaint Animation 11 (64bits)\\TVPaint Animation 11 (64bits).exe" @@ -1233,17 +930,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "tvpaint_animation_11-64bits": [] - } - } + "environment": {} }, "tvpaint_animation_11-32bits": { "enabled": true, - "label": "", "variant_label": "11 (32bits)", - "icon": "", "executables": { "windows": [ "C:\\Program Files (x86)\\TVPaint Developpement\\TVPaint Animation 11 (32bits)\\TVPaint Animation 11 (32bits).exe" @@ -1256,11 +947,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "tvpaint_animation_11-32bits": [] - } - } + "environment": {} } } }, @@ -1271,24 +958,14 @@ "host_name": "photoshop", "environment": { "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", - "PYPE_LOG_NO_COLORS": "Yes", + "OPENPYPE_LOG_NO_COLORS": "Yes", "WEBSOCKET_URL": "ws://localhost:8099/ws/", - "WORKFILES_SAVE_AS": "Yes", - "__environment_keys__": { - "photoshop": [ - "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", - "PYPE_LOG_NO_COLORS", - "WEBSOCKET_URL", - "WORKFILES_SAVE_AS" - ] - } + "WORKFILES_SAVE_AS": "Yes" }, "variants": { "photoshop_2020": { "enabled": true, - "label": "", "variant_label": "2020", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2020\\Photoshop.exe" @@ -1301,17 +978,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "photoshop_2020": [] - } - } + "environment": {} }, "photoshop_2021": { "enabled": true, - "label": "", "variant_label": "2021", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe Photoshop 2021\\Photoshop.exe" @@ -1324,11 +995,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "photoshop_2021": [] - } - } + "environment": {} } } }, @@ -1339,24 +1006,14 @@ "host_name": "aftereffects", "environment": { "AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH": "1", - "PYPE_LOG_NO_COLORS": "Yes", + "OPENPYPE_LOG_NO_COLORS": "Yes", "WEBSOCKET_URL": "ws://localhost:8097/ws/", - "WORKFILES_SAVE_AS": "Yes", - "__environment_keys__": { - "aftereffects": [ - "AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH", - "PYPE_LOG_NO_COLORS", - "WEBSOCKET_URL", - "WORKFILES_SAVE_AS" - ] - } + "WORKFILES_SAVE_AS": "Yes" }, "variants": { "aftereffects_2020": { "enabled": true, - "label": "", "variant_label": "2020", - "icon": "", "executables": { "windows": [ "" @@ -1369,17 +1026,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "aftereffects_2020": [] - } - } + "environment": {} }, "aftereffects_2021": { "enabled": true, - "label": "", "variant_label": "2021", - "icon": "", "executables": { "windows": [ "C:\\Program Files\\Adobe\\Adobe After Effects 2021\\Support Files\\AfterFX.exe" @@ -1392,11 +1043,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "aftereffects_2021": [] - } - } + "environment": {} } } }, @@ -1406,30 +1053,19 @@ "icon": "app_icons/celaction.png", "host_name": "celaction", "environment": { - "CELACTION_TEMPLATE": "{PYPE_ROOT}/pype/hosts/celaction/celaction_template_scene.scn", - "__environment_keys__": { - "celaction": [ - "CELACTION_TEMPLATE" - ] - } + "CELACTION_TEMPLATE": "{OPENPYPE_ROOT}/openpype/hosts/celaction/celaction_template_scene.scn" }, "variants": { "celation_Local": { "enabled": true, - "label": "", "variant_label": "Local", - "icon": "{}/app_icons/celaction_local.png", "executables": "", "arguments": { "windows": [], "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "celation_Local": [] - } - } + "environment": {} } } }, @@ -1439,23 +1075,14 @@ "icon": "{}/app_icons/ue4.png'", "host_name": "unreal", "environment": { - "AVALON_UNREAL_PLUGIN": "{PYPE_REPOS_ROOT}/avalon-unreal-integration", - "PYPE_LOG_NO_COLORS": "True", - "QT_PREFERRED_BINDING": "PySide", - "__environment_keys__": { - "unreal": [ - "AVALON_UNREAL_PLUGIN", - "PYPE_LOG_NO_COLORS", - "QT_PREFERRED_BINDING" - ] - } + "AVALON_UNREAL_PLUGIN": "{OPENPYPE_ROOT}/repos/avalon-unreal-integration", + "OPENPYPE_LOG_NO_COLORS": "True", + "QT_PREFERRED_BINDING": "PySide" }, "variants": { "unreal_4-24": { "enabled": true, - "label": "", "variant_label": "4.24", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1466,27 +1093,17 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "unreal_4-24": [] - } - } + "environment": {} } } }, "shell": { "enabled": true, - "environment": { - "__environment_keys__": { - "shell": [] - } - }, + "environment": {}, "variants": { "python_python_3-7": { "enabled": true, - "label": "Python", "variant_label": "3.7", - "icon": "{}/app_icons/python.png", "executables": { "windows": [], "darwin": [], @@ -1497,17 +1114,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "python_python_3-7": [] - } - } + "environment": {} }, "python_python_2-7": { "enabled": true, - "label": "Python", "variant_label": "2.7", - "icon": "{}/app_icons/python.png", "executables": { "windows": [], "darwin": [], @@ -1518,17 +1129,11 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "python_python_2-7": [] - } - } + "environment": {} }, "terminal_terminal": { "enabled": true, - "label": "Terminal", "variant_label": "", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1539,11 +1144,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "terminal_terminal": [] - } - } + "environment": {} } } }, @@ -1552,17 +1153,11 @@ "label": "DJV View", "icon": "{}/app_icons/djvView.png", "host_name": "", - "environment": { - "__environment_keys__": { - "djvview": [] - } - }, + "environment": {}, "variants": { "djvview_1-1": { "enabled": true, - "label": "", "variant_label": "1.1", - "icon": "", "executables": { "windows": [], "darwin": [], @@ -1573,11 +1168,7 @@ "darwin": [], "linux": [] }, - "environment": { - "__environment_keys__": { - "djvview_1-1": [] - } - } + "environment": {} } } } diff --git a/openpype/settings/defaults/system_settings/general.json b/openpype/settings/defaults/system_settings/general.json new file mode 100644 index 0000000000..d93d2a0c3a --- /dev/null +++ b/openpype/settings/defaults/system_settings/general.json @@ -0,0 +1,23 @@ +{ + "studio_name": "Studio name", + "studio_code": "stu", + "environment": { + "FFMPEG_PATH": { + "windows": "{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/windows/bin", + "darwin": "{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/darwin/bin", + "linux": ":{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/linux" + }, + "OPENPYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs", + "__environment_keys__": { + "global": [ + "FFMPEG_PATH", + "OPENPYPE_OCIO_CONFIG" + ] + } + }, + "openpype_path": { + "windows": [], + "darwin": [], + "linux": [] + } +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json similarity index 98% rename from pype/settings/defaults/system_settings/modules.json rename to openpype/settings/defaults/system_settings/modules.json index e285fce854..00e98aa8de 100644 --- a/pype/settings/defaults/system_settings/modules.json +++ b/openpype/settings/defaults/system_settings/modules.json @@ -9,7 +9,7 @@ }, "ftrack": { "enabled": true, - "ftrack_server": "https://pype.ftrackapp.com", + "ftrack_server": "", "ftrack_actions_path": [], "ftrack_events_path": [], "intent": { @@ -131,7 +131,7 @@ }, "clockify": { "enabled": false, - "workspace_name": "studio name" + "workspace_name": "" }, "sync_server": { "enabled": false diff --git a/pype/settings/defaults/system_settings/tools.json b/openpype/settings/defaults/system_settings/tools.json similarity index 94% rename from pype/settings/defaults/system_settings/tools.json rename to openpype/settings/defaults/system_settings/tools.json index 214bfc95e5..b0adccc65e 100644 --- a/pype/settings/defaults/system_settings/tools.json +++ b/openpype/settings/defaults/system_settings/tools.json @@ -2,7 +2,7 @@ "tool_groups": { "mtoa": { "environment": { - "MTOA": "{PYPE_STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", + "MTOA": "{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", "MAYA_RENDER_DESC_PATH": "{MTOA}", "MAYA_MODULE_PATH": "{MTOA}", "ARNOLD_PLUGIN_PATH": "{MTOA}/shaders", diff --git a/pype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py similarity index 94% rename from pype/settings/entities/__init__.py rename to openpype/settings/entities/__init__.py index 2ff911e7d4..f76a915225 100644 --- a/pype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -1,9 +1,9 @@ -"""Pype Settings +"""OpenPype Settings -Settings define how pype and it's modules behave. They became main component of -dynamism. +Settings define how openpype and it's modules behave. They became main +component of dynamism. -Pype settings (ATM) have 3 layers: +OpenPype settings (ATM) have 3 layers: 1.) Defaults - defined in code 2.) Studio overrides - values that are applied on default that may modify only some values or None, result can be called "studio settings" @@ -24,7 +24,7 @@ visuallise how values are applied. With help of setting entities it is possible to modify settings from code. -Pype has (ATM) 2 types of settings: +OpenPype has (ATM) 2 types of settings: 1.) System settings - global system settings, don't have project overrides 2.) Project settings - project specific settings diff --git a/pype/settings/entities/anatomy_entities.py b/openpype/settings/entities/anatomy_entities.py similarity index 100% rename from pype/settings/entities/anatomy_entities.py rename to openpype/settings/entities/anatomy_entities.py diff --git a/pype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py similarity index 98% rename from pype/settings/entities/base_entity.py rename to openpype/settings/entities/base_entity.py index c01fca6a4e..b5c42e1da0 100644 --- a/pype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -14,7 +14,7 @@ from .exceptions import ( EntitySchemaError ) -from pype.lib import PypeLogger +from openpype.lib import PypeLogger @six.add_metaclass(ABCMeta) @@ -611,8 +611,8 @@ class BaseItemEntity(BaseEntity): def remove_from_studio_default(self, on_change_trigger=None): """Remove studio overrides from entity and it's children. - Reset values to pype's default and mark entity to not store values as - studio overrides if entity is not under group. + Reset values to openpype's default and mark entity to not store values + as studio overrides if entity is not under group. This is wrapper method that handles on_change callbacks only when all `_remove_from_studio_default` on all children happened. That is @@ -672,8 +672,8 @@ class BaseItemEntity(BaseEntity): def remove_from_project_override(self, on_change_trigger=None): """Remove project overrides from entity and it's children. - Reset values to studio overrides or pype's default and mark entity to - not store values as project overrides if entity is not under group. + Reset values to studio overrides or openpype's default and mark entity + to not store values as project overrides if entity is not under group. This is wrapper method that handles on_change callbacks only when all `_remove_from_project_override` on all children happened. That is @@ -837,9 +837,9 @@ class ItemEntity(BaseItemEntity): def update_default_value(self, parent_values): """Fill default values on startup or on refresh. - Default values stored in `pype` repository should update all items in - schema. Each item should take values for his key and set it's value or - pass values down to children items. + Default values stored in `openpype` repository should update all items + in schema. Each item should take values for his key and set it's value + or pass values down to children items. Args: parent_values (dict): Values of parent's item. But in case item is diff --git a/pype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py similarity index 99% rename from pype/settings/entities/dict_immutable_keys_entity.py rename to openpype/settings/entities/dict_immutable_keys_entity.py index 270e635736..d5563f80d6 100644 --- a/pype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -5,7 +5,7 @@ from .lib import ( OverrideState, NOT_SET ) -from pype.settings.constants import ( +from openpype.settings.constants import ( METADATA_KEYS, M_OVERRIDEN_KEY, KEY_REGEX diff --git a/pype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py similarity index 99% rename from pype/settings/entities/dict_mutable_keys_entity.py rename to openpype/settings/entities/dict_mutable_keys_entity.py index b465171734..cbc80b6409 100644 --- a/pype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -13,7 +13,7 @@ from .exceptions import ( RequiredKeyModified, EntitySchemaError ) -from pype.settings.constants import ( +from openpype.settings.constants import ( METADATA_KEYS, M_DYNAMIC_KEY_LABEL, M_ENVIRONMENT_KEY, @@ -44,6 +44,8 @@ class DictMutableKeysEntity(EndpointEntity): _miss_arg = object() def __getitem__(self, key): + if key not in self.children_by_key: + self.add_key(key) return self.children_by_key[key] def __setitem__(self, key, value): diff --git a/pype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py similarity index 96% rename from pype/settings/entities/enum_entity.py rename to openpype/settings/entities/enum_entity.py index c486de397e..e28fb7478f 100644 --- a/pype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -133,12 +133,9 @@ class AppsEnumEntity(BaseEnumEntity): if enabled_entity and not enabled_entity.value: continue - _group_label = variant_entity["label"].value - if not _group_label: - _group_label = group_label variant_label = variant_entity["variant_label"].value - full_label = "{} {}".format(_group_label, variant_label) + full_label = "{} {}".format(group_label, variant_label) enum_items.append({variant_name: full_label}) valid_keys.add(variant_name) return enum_items, valid_keys diff --git a/pype/settings/entities/exceptions.py b/openpype/settings/entities/exceptions.py similarity index 98% rename from pype/settings/entities/exceptions.py rename to openpype/settings/entities/exceptions.py index f86d08ab5f..3649e63ab7 100644 --- a/pype/settings/entities/exceptions.py +++ b/openpype/settings/entities/exceptions.py @@ -1,4 +1,4 @@ -from pype.settings.constants import KEY_ALLOWED_SYMBOLS +from openpype.settings.constants import KEY_ALLOWED_SYMBOLS class DefaultsNotDefined(Exception): diff --git a/pype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py similarity index 99% rename from pype/settings/entities/input_entities.py rename to openpype/settings/entities/input_entities.py index f668cfde2d..e406c7797a 100644 --- a/pype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -14,7 +14,7 @@ from .exceptions import ( EntitySchemaError ) -from pype.settings.constants import ( +from openpype.settings.constants import ( METADATA_KEYS, M_ENVIRONMENT_KEY ) diff --git a/pype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py similarity index 100% rename from pype/settings/entities/item_entities.py rename to openpype/settings/entities/item_entities.py diff --git a/pype/settings/entities/lib.py b/openpype/settings/entities/lib.py similarity index 100% rename from pype/settings/entities/lib.py rename to openpype/settings/entities/lib.py diff --git a/pype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py similarity index 100% rename from pype/settings/entities/list_entity.py rename to openpype/settings/entities/list_entity.py diff --git a/pype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py similarity index 99% rename from pype/settings/entities/root_entities.py rename to openpype/settings/entities/root_entities.py index e7cb098c67..eed3d47f46 100644 --- a/pype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -17,14 +17,14 @@ from .exceptions import ( SchemaError, InvalidKeySymbols ) -from pype.settings.constants import ( +from openpype.settings.constants import ( SYSTEM_SETTINGS_KEY, PROJECT_SETTINGS_KEY, PROJECT_ANATOMY_KEY, KEY_REGEX ) -from pype.settings.lib import ( +from openpype.settings.lib import ( DEFAULTS_DIR, get_default_settings, @@ -175,7 +175,7 @@ class RootEntity(BaseItemEntity): """ if self._loaded_types is None: # Load available entities - from pype.settings import entities + from openpype.settings import entities # Define known abstract classes known_abstract_classes = ( diff --git a/pype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md similarity index 99% rename from pype/settings/entities/schemas/README.md rename to openpype/settings/entities/schemas/README.md index e92ba8918f..18312a8364 100644 --- a/pype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -7,9 +7,9 @@ - each input may have "input modifiers" (keys in dictionary) that are required or optional - only required modifier for all input items is key `"type"` which says what type of item it is - there are special keys across all inputs - - `"is_file"` - this key is for storing pype defaults in `pype` repo + - `"is_file"` - this key is for storing openpype defaults in `openpype` repo - reasons of existence: developing new schemas does not require to create defaults manually - - key is validated, must be once in hierarchy else it won't be possible to store pype defaults + - key is validated, must be once in hierarchy else it won't be possible to store openpype defaults - `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides - this keys is not allowed for all inputs as they may have not reason for that - key is validated, can be only once in hierarchy but is not required diff --git a/pype/settings/entities/schemas/projects_schema/schema_main.json b/openpype/settings/entities/schemas/projects_schema/schema_main.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_main.json rename to openpype/settings/entities/schemas/projects_schema/schema_main.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_celaction.json b/openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_celaction.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_celaction.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json similarity index 96% rename from pype/settings/entities/schemas/projects_schema/schema_project_deadline.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index 2070e4c8f5..f46221ba63 100644 --- a/pype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -127,6 +127,15 @@ "type": "text", "key": "department", "label": "Department" + }, + { + "type": "dict-modifiable", + "key": "limit_groups", + "label": "Limit Groups", + "object_type": { + "type": "list", + "object_type": "text" + } } ] }, diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_ftrack.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_global.json b/openpype/settings/entities/schemas/projects_schema/schema_project_global.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_global.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_global.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_harmony.json b/openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_harmony.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_harmony.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_hiero.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_maya.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_maya.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json similarity index 93% rename from pype/settings/entities/schemas/projects_schema/schema_project_nuke.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json index 3fe01cad09..75ca5411a1 100644 --- a/pype/settings/entities/schemas/projects_schema/schema_project_nuke.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json @@ -8,15 +8,14 @@ { "type": "dict", "collapsible": true, - "key": "menu", - "label": "Menu shortcuts", + "key": "general", + "label": "General", "children": [ { "type": "dict", - "collapsible": false, - "key": "Pype", - "label": "Pype", - "is_group": true, + "collapsible": true, + "key": "menu", + "label": "OpenPype Menu shortcuts", "children": [ { "type": "text", diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_resolve.json b/openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_resolve.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_resolve.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_standalonepublisher.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_syncserver.json b/openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_syncserver.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_syncserver.json diff --git a/pype/settings/entities/schemas/projects_schema/schema_project_unreal.json b/openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schema_project_unreal.json rename to openpype/settings/entities/schemas/projects_schema/schema_project_unreal.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_attributes.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_imageio.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_anatomy_templates.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json similarity index 98% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index b4d1876297..3c079a130d 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -119,7 +119,10 @@ "slate-frame": "Add slate frame" }, { - "no-hnadles": "Skip handle frames" + "no-handles": "Skip handle frames" + }, + { + "sequence": "Output as image sequence" } ] }, diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json similarity index 86% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json index 3615c1477c..dd9d0508b4 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_load.json @@ -151,32 +151,6 @@ ] } ] - }, - { - "type": "schema_template", - "name": "template_loader_plugin", - "template_data": [ - { - "key": "ReferenceLoader", - "label": "Reference Loader" - }, - { - "key": "AudioLoader", - "label": "Audio Loader" - }, - { - "key": "GpuCacheLoader", - "label": "GpuCache Loader" - }, - { - "key": "ImagePlaneLoader", - "label": "Imageplane Loader" - }, - { - "key": "MatchmoveLoader", - "label": "Matchmove Loader" - } - ] } ] } diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json similarity index 92% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json index 9d132e33b4..737843ad98 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json @@ -6,7 +6,7 @@ "children": [ { "type": "schema_template", - "name": "template_loader_plugin", + "name": "template_loader_plugin_nuke", "template_data": [ { "key": "LoadImage", diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_publish.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_publish_gui_filter.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json rename to openpype/settings/entities/schemas/projects_schema/schemas/schema_workfile_build.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_color.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_color.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/template_color.json rename to openpype/settings/entities/schemas/projects_schema/schemas/template_color.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_create_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_create_plugin.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/template_create_plugin.json rename to openpype/settings/entities/schemas/projects_schema/schemas/template_create_plugin.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json similarity index 60% rename from pype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin.json rename to openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json index 20dca6df17..d01691ed5f 100644 --- a/pype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/template_loader_plugin_nuke.json @@ -11,11 +11,22 @@ "key": "enabled", "label": "Enabled" }, + { + "type": "list", + "key": "families", + "label": "Families", + "object_type": "text" + }, { "type": "list", "key": "representations", "label": "Representations", "object_type": "text" + }, + { + "type": "text", + "key": "node_name_template", + "label": "Node name template" } ] } diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json rename to openpype/settings/entities/schemas/projects_schema/schemas/template_maya_capture.json diff --git a/pype/settings/entities/schemas/projects_schema/schemas/template_publish_plugin.json b/openpype/settings/entities/schemas/projects_schema/schemas/template_publish_plugin.json similarity index 100% rename from pype/settings/entities/schemas/projects_schema/schemas/template_publish_plugin.json rename to openpype/settings/entities/schemas/projects_schema/schemas/template_publish_plugin.json diff --git a/pype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/example_schema.json rename to openpype/settings/entities/schemas/system_schema/example_schema.json diff --git a/pype/settings/entities/schemas/system_schema/example_template.json b/openpype/settings/entities/schemas/system_schema/example_template.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/example_template.json rename to openpype/settings/entities/schemas/system_schema/example_template.json diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json index 6e1ba352fc..cd080bf0f2 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "aftereffects" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_blender.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_blender.json index 5030f8280f..2501e94b50 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_blender.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_blender.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "blender" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json similarity index 93% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json index c5fe824f94..fbdad62a92 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_celaction.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "celaction" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_djv.json similarity index 92% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_djv.json index 3f3af3585a..381437d4ff 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_djv.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_djv.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "djvview" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json index d693c39ffe..8661916d06 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_fusion.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "fusion" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json index 8ad07c95ba..7c59b0febd 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_harmony.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "harmony" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json similarity index 95% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json index 7698cb4cc1..70e06d170d 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_houdini.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "houdini" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_maya.json similarity index 95% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_maya.json index d8396b16cb..07c8aa0106 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_maya.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_maya.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "maya" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json similarity index 95% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json index af7cc3d301..bea59656af 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_mayabatch.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "mayabatch" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json index a8e3574aa3..6f67e29df2 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_photoshop.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "photoshop" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json similarity index 93% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json index 052a935410..644e3046ce 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "resolve" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_shell.json similarity index 95% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_shell.json index 3288fe2ffb..f2f9376029 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_shell.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_shell.json @@ -13,8 +13,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "shell" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json similarity index 94% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json index a3cc6188ac..fa28c4448c 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_tvpaint.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "tvpaint" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json similarity index 93% rename from pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json rename to openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json index c79f08b71a..e9d1b68130 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_unreal.json @@ -17,8 +17,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "unreal" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json rename to openpype/settings/entities/schemas/system_schema/host_settings/template_host_unchangables.json diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json similarity index 77% rename from pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json rename to openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json index c809891b30..10aab06466 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant.json @@ -17,13 +17,6 @@ "key": "enabled", "label": "Enabled" }, - { - "type": "text", - "key": "label", - "label": "Label", - "placeholder": "Used from host label if not filled.", - "roles": ["developer"] - }, { "type": "text", "key": "variant_label", @@ -31,13 +24,6 @@ "placeholder": "Only \"Label\" is used if not filled.", "roles": ["developer"] }, - { - "type": "text", - "key": "icon", - "label": "Icon", - "placeholder": "Host icon path template. Used from host if not filled.", - "roles": ["developer"] - }, { "type": "path", "key": "executables", @@ -77,8 +63,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "{app_name}_{app_variant}" + "type": "raw-json" } ] } diff --git a/pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json b/openpype/settings/entities/schemas/system_schema/host_settings/template_nuke.json similarity index 95% rename from pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json rename to openpype/settings/entities/schemas/system_schema/host_settings/template_nuke.json index c86c2aef61..d99e0b9a85 100644 --- a/pype/settings/entities/schemas/system_schema/host_settings/template_nuke.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/template_nuke.json @@ -18,8 +18,7 @@ { "key": "environment", "label": "Environment", - "type": "raw-json", - "env_group_key": "{nuke_type}" + "type": "raw-json" }, { "type": "dict", diff --git a/pype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json b/openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json rename to openpype/settings/entities/schemas/system_schema/module_settings/schema_ftrack.json diff --git a/pype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json b/openpype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json rename to openpype/settings/entities/schemas/system_schema/module_settings/template_custom_attribute.json diff --git a/pype/settings/entities/schemas/system_schema/schema_applications.json b/openpype/settings/entities/schemas/system_schema/schema_applications.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/schema_applications.json rename to openpype/settings/entities/schemas/system_schema/schema_applications.json diff --git a/pype/settings/entities/schemas/system_schema/schema_general.json b/openpype/settings/entities/schemas/system_schema/schema_general.json similarity index 86% rename from pype/settings/entities/schemas/system_schema/schema_general.json rename to openpype/settings/entities/schemas/system_schema/schema_general.json index cf88043cd0..fd650b4a1e 100644 --- a/pype/settings/entities/schemas/system_schema/schema_general.json +++ b/openpype/settings/entities/schemas/system_schema/schema_general.json @@ -19,20 +19,20 @@ "type": "splitter" }, { - "key": "studio_soft", - "type": "path", - "label": "Studio Software Location", - "multiplatform": true, - "multipath": false + "key": "environment", + "label": "Environment", + "type": "raw-json", + "env_group_key": "global" }, { "type": "splitter" }, { - "key": "environment", - "label": "Environment", - "type": "raw-json", - "env_group_key": "global" + "type": "path", + "key": "openpype_path", + "label": "Versions Repository", + "multiplatform": true, + "multipath": true } ] } diff --git a/pype/settings/entities/schemas/system_schema/schema_main.json b/openpype/settings/entities/schemas/system_schema/schema_main.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/schema_main.json rename to openpype/settings/entities/schemas/system_schema/schema_main.json diff --git a/pype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/schema_modules.json rename to openpype/settings/entities/schemas/system_schema/schema_modules.json diff --git a/pype/settings/entities/schemas/system_schema/schema_tools.json b/openpype/settings/entities/schemas/system_schema/schema_tools.json similarity index 100% rename from pype/settings/entities/schemas/system_schema/schema_tools.json rename to openpype/settings/entities/schemas/system_schema/schema_tools.json diff --git a/pype/settings/handlers.py b/openpype/settings/handlers.py similarity index 71% rename from pype/settings/handlers.py rename to openpype/settings/handlers.py index 6e93f2f405..b3e1b1b1e1 100644 --- a/pype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -6,8 +6,9 @@ import collections import datetime from abc import ABCMeta, abstractmethod import six -import pype +import openpype from .constants import ( + GLOBAL_SETTINGS_KEY, SYSTEM_SETTINGS_KEY, PROJECT_SETTINGS_KEY, PROJECT_ANATOMY_KEY, @@ -126,183 +127,6 @@ class LocalSettingsHandler: pass -class SettingsFileHandler(SettingsHandler): - def __init__(self): - self.log = logging.getLogger("SettingsFileHandler") - - # Folder where studio overrides are stored - studio_overrides_dir = os.getenv("PYPE_PROJECT_CONFIGS") - if not studio_overrides_dir: - studio_overrides_dir = os.path.dirname(os.path.dirname( - os.path.abspath(pype.__file__) - )) - system_settings_path = os.path.join( - studio_overrides_dir, SYSTEM_SETTINGS_KEY + ".json" - ) - - # File where studio's default project overrides are stored - project_settings_filename = PROJECT_SETTINGS_KEY + ".json" - project_settings_path = os.path.join( - studio_overrides_dir, project_settings_filename - ) - - project_anatomy_filename = PROJECT_ANATOMY_KEY + ".json" - project_anatomy_path = os.path.join( - studio_overrides_dir, project_anatomy_filename - ) - - self.studio_overrides_dir = studio_overrides_dir - self.system_settings_path = system_settings_path - - self.project_settings_filename = project_settings_filename - self.project_anatomy_filename = project_anatomy_filename - - self.project_settings_path = project_settings_path - self.project_anatomy_path = project_anatomy_path - - def path_to_project_settings(self, project_name): - if not project_name: - return self.project_settings_path - return os.path.join( - self.studio_overrides_dir, - project_name, - self.project_settings_filename - ) - - def path_to_project_anatomy(self, project_name): - if not project_name: - return self.project_anatomy_path - return os.path.join( - self.studio_overrides_dir, - project_name, - self.project_anatomy_filename - ) - - def save_studio_settings(self, data): - """Save studio overrides of system settings. - - Do not use to store whole system settings data with defaults but only - it's overrides with metadata defining how overrides should be applied - in load function. For loading should be used function - `studio_system_settings`. - - Args: - data(dict): Data of studio overrides with override metadata. - """ - dirpath = os.path.dirname(self.system_settings_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - self.log.debug( - "Saving studio overrides. Output path: {}".format( - self.system_settings_path - ) - ) - with open(self.system_settings_path, "w") as file_stream: - json.dump(data, file_stream, indent=4) - - def save_project_settings(self, project_name, overrides): - """Save studio overrides of project settings. - - Data are saved for specific project or as defaults for all projects. - - Do not use to store whole project settings data with defaults but only - it's overrides with metadata defining how overrides should be applied - in load function. For loading should be used function - `get_studio_project_settings_overrides` for global project settings - and `get_project_settings_overrides` for project specific settings. - - Args: - project_name(str, null): Project name for which overrides are - or None for global settings. - data(dict): Data of project overrides with override metadata. - """ - project_overrides_json_path = self.path_to_project_settings( - project_name - ) - dirpath = os.path.dirname(project_overrides_json_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - self.log.debug( - "Saving overrides of project \"{}\". Output path: {}".format( - project_name, project_overrides_json_path - ) - ) - with open(project_overrides_json_path, "w") as file_stream: - json.dump(overrides, file_stream, indent=4) - - def save_project_anatomy(self, project_name, anatomy_data): - """Save studio overrides of project anatomy data. - - Args: - project_name(str, null): Project name for which overrides are - or None for global settings. - data(dict): Data of project overrides with override metadata. - """ - project_anatomy_json_path = self.path_to_project_anatomy(project_name) - dirpath = os.path.dirname(project_anatomy_json_path) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - - self.log.debug( - "Saving anatomy of project \"{}\". Output path: {}".format( - project_name, project_anatomy_json_path - ) - ) - with open(project_anatomy_json_path, "w") as file_stream: - json.dump(anatomy_data, file_stream, indent=4) - - def get_studio_system_settings_overrides(self): - """Studio overrides of system settings.""" - if os.path.exists(self.system_settings_path): - return load_json_file(self.system_settings_path) - return {} - - def get_studio_project_settings_overrides(self): - """Studio overrides of default project settings.""" - if os.path.exists(self.project_settings_path): - return load_json_file(self.project_settings_path) - return {} - - def get_studio_project_anatomy_overrides(self): - """Studio overrides of default project anatomy data.""" - if os.path.exists(self.project_anatomy_path): - return load_json_file(self.project_anatomy_path) - return {} - - def get_project_settings_overrides(self, project_name): - """Studio overrides of project settings for specific project. - - Args: - project_name(str): Name of project for which data should be loaded. - - Returns: - dict: Only overrides for entered project, may be empty dictionary. - """ - path_to_json = self.path_to_project_settings(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json_file(path_to_json) - - def get_project_anatomy_overrides(self, project_name): - """Studio overrides of project anatomy for specific project. - - Args: - project_name(str): Name of project for which data should be loaded. - - Returns: - dict: Only overrides for entered project, may be empty dictionary. - """ - if not project_name: - return {} - - path_to_json = self.path_to_project_anatomy(project_name) - if not os.path.exists(path_to_json): - return {} - return load_json_file(path_to_json) - - class CacheValues: cache_lifetime = 10 @@ -320,10 +144,15 @@ class CacheValues: self.creation_time = datetime.datetime.now() def update_from_document(self, document): - value = "{}" + data = {} if document: - value = document.get("value") or value - self.data = json.loads(value) + if "data" in document: + data = document["data"] + elif "value" in document: + value = document["value"] + if value: + data = json.loads(value) + self.data = data def to_json_string(self): return json.dumps(self.data or {}) @@ -341,18 +170,18 @@ class MongoSettingsHandler(SettingsHandler): def __init__(self): # Get mongo connection - from pype.lib import PypeMongoConnection + from openpype.lib import OpenPypeMongoConnection from avalon.api import AvalonMongoDB - settings_collection = PypeMongoConnection.get_mongo_client() + settings_collection = OpenPypeMongoConnection.get_mongo_client() self._anatomy_keys = None self._attribute_keys = None # TODO prepare version of pype # - pype version should define how are settings saved and loaded + database_name = os.environ["OPENPYPE_DATABASE_NAME"] # TODO modify to not use hardcoded keys - database_name = "pype" collection_name = "settings" self.settings_collection = settings_collection @@ -396,6 +225,13 @@ class MongoSettingsHandler(SettingsHandler): self._prepare_project_settings_keys() return self._attribute_keys + def _prepare_global_settings(self, data): + output = {} + # Add "openpype_path" key to global settings if is set + if "general" in data and "openpype_path" in data["general"]: + output["openpype_path"] = data["general"]["openpype_path"] + return output + def save_studio_settings(self, data): """Save studio overrides of system settings. @@ -407,15 +243,31 @@ class MongoSettingsHandler(SettingsHandler): Args: data(dict): Data of studio overrides with override metadata. """ + # Store system settings self.system_settings_cache.update_data(data) - self.collection.replace_one( { "type": SYSTEM_SETTINGS_KEY }, { "type": SYSTEM_SETTINGS_KEY, - "value": self.system_settings_cache.to_json_string() + "data": self.system_settings_cache.data + }, + upsert=True + ) + + # Get global settings from system settings + global_settings = self._prepare_global_settings( + self.system_settings_cache.data + ) + # Store global settings + self.collection.replace_one( + { + "type": GLOBAL_SETTINGS_KEY + }, + { + "type": GLOBAL_SETTINGS_KEY, + "data": global_settings }, upsert=True ) @@ -550,7 +402,7 @@ class MongoSettingsHandler(SettingsHandler): } replace_data = { "type": doc_type, - "value": data_cache.to_json_string(), + "data": data_cache.data, "is_default": is_default } if not is_default: @@ -610,6 +462,9 @@ class MongoSettingsHandler(SettingsHandler): Probably should fill missing keys and values. """ + if not project_doc: + return {} + attributes = {} project_doc_data = project_doc.get("data") or {} for key in self.attribute_keys: @@ -685,20 +540,20 @@ class MongoLocalSettingsHandler(LocalSettingsHandler): def __init__(self, local_site_id=None): # Get mongo connection - from pype.lib import ( - PypeMongoConnection, + from openpype.lib import ( + OpenPypeMongoConnection, get_local_site_id ) if local_site_id is None: local_site_id = get_local_site_id() - settings_collection = PypeMongoConnection.get_mongo_client() + settings_collection = OpenPypeMongoConnection.get_mongo_client() # TODO prepare version of pype # - pype version should define how are settings saved and loaded + database_name = os.environ["OPENPYPE_DATABASE_NAME"] # TODO modify to not use hardcoded keys - database_name = "pype" collection_name = "settings" self.settings_collection = settings_collection @@ -730,7 +585,7 @@ class MongoLocalSettingsHandler(LocalSettingsHandler): { "type": LOCAL_SETTING_KEY, "site_id": self.local_site_id, - "value": self.local_settings_cache.to_json_string() + "data": self.local_settings_cache.data }, upsert=True ) diff --git a/pype/settings/lib.py b/openpype/settings/lib.py similarity index 99% rename from pype/settings/lib.py rename to openpype/settings/lib.py index 9cb0bc9ecb..60a51c01a0 100644 --- a/pype/settings/lib.py +++ b/openpype/settings/lib.py @@ -105,7 +105,7 @@ def save_studio_settings(data): data(dict): Overrides data with metadata defying studio overrides. """ # Notify Pype modules - from pype.modules import ModulesManager, ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener old_data = get_system_settings() default_values = get_default_settings()[SYSTEM_SETTINGS_KEY] @@ -136,7 +136,7 @@ def save_project_settings(project_name, overrides): overrides(dict): Overrides data with metadata defying studio overrides. """ # Notify Pype modules - from pype.modules import ModulesManager, ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener default_values = get_default_settings()[PROJECT_SETTINGS_KEY] if project_name: @@ -179,7 +179,7 @@ def save_project_anatomy(project_name, anatomy_data): overrides(dict): Overrides data with metadata defying studio overrides. """ # Notify Pype modules - from pype.modules import ModulesManager, ISettingsChangeListener + from openpype.modules import ModulesManager, ISettingsChangeListener default_values = get_default_settings()[PROJECT_ANATOMY_KEY] if project_name: diff --git a/pype/settings/local_settings.md b/openpype/settings/local_settings.md similarity index 100% rename from pype/settings/local_settings.md rename to openpype/settings/local_settings.md diff --git a/pype/tests/README.md b/openpype/tests/README.md similarity index 100% rename from pype/tests/README.md rename to openpype/tests/README.md diff --git a/pype/tests/__init__.py b/openpype/tests/__init__.py similarity index 100% rename from pype/tests/__init__.py rename to openpype/tests/__init__.py diff --git a/pype/tests/lib.py b/openpype/tests/lib.py similarity index 100% rename from pype/tests/lib.py rename to openpype/tests/lib.py diff --git a/pype/tests/test_avalon_plugin_presets.py b/openpype/tests/test_avalon_plugin_presets.py similarity index 96% rename from pype/tests/test_avalon_plugin_presets.py rename to openpype/tests/test_avalon_plugin_presets.py index 7f023ea358..cc1858554c 100644 --- a/pype/tests/test_avalon_plugin_presets.py +++ b/openpype/tests/test_avalon_plugin_presets.py @@ -1,5 +1,5 @@ import avalon.api as api -import pype +import openpype class MyTestCreator(api.Creator): @@ -24,7 +24,7 @@ class Test: def test_avalon_plugin_presets(monkeypatch, printer): - pype.install() + openpype.install() api.register_host(Test()) api.register_plugin(api.Creator, MyTestCreator) plugins = api.discover(api.Creator) diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py new file mode 100644 index 0000000000..d0461e55fb --- /dev/null +++ b/openpype/tests/test_lib_restructuralization.py @@ -0,0 +1,35 @@ +# Test for backward compatibility of restructure of lib.py into lib library +# Contains simple imports that should still work + + +def test_backward_compatibility(printer): + printer("Test if imports still work") + try: + from openpype.lib import filter_pyblish_plugins + from openpype.lib import execute_hook + from openpype.lib import PypeHook + + from openpype.lib import get_latest_version + from openpype.lib import ApplicationLaunchFailed + + from openpype.lib import get_ffmpeg_tool_path + from openpype.lib import get_last_version_from_path + from openpype.lib import get_paths_from_environ + from openpype.lib import get_version_from_path + from openpype.lib import version_up + + from openpype.lib import is_latest + from openpype.lib import any_outdated + from openpype.lib import get_asset + from openpype.lib import get_hierarchy + from openpype.lib import get_linked_assets + from openpype.lib import get_latest_version + from openpype.lib import ffprobe_streams + + from openpype.hosts.fusion.lib import switch_item + + from openpype.lib import source_hash + from openpype.lib import run_subprocess + + except ImportError as e: + raise diff --git a/pype/tests/test_mongo_performance.py b/openpype/tests/test_mongo_performance.py similarity index 99% rename from pype/tests/test_mongo_performance.py rename to openpype/tests/test_mongo_performance.py index ea5ea90aae..cd606d6483 100644 --- a/pype/tests/test_mongo_performance.py +++ b/openpype/tests/test_mongo_performance.py @@ -108,7 +108,7 @@ class TestPerformance(): "template": "{root}\\{project[name]}\\{hierarchy}\\{asset}\\publish\\{family}\\{subset}\\v{version:0>3}\\{project[code]}_{asset}_{subset}_v{version:0>3}<_{output}><.{frame:0>4}>.{representation}" }, "type": "representation", - "schema": "pype:representation-2.0" + "schema": "openpype:representation-2.0" } insert_recs.append(document) diff --git a/pype/tests/test_pyblish_filter.py b/openpype/tests/test_pyblish_filter.py similarity index 97% rename from pype/tests/test_pyblish_filter.py rename to openpype/tests/test_pyblish_filter.py index cf3d5d6015..ea23da26e4 100644 --- a/pype/tests/test_pyblish_filter.py +++ b/openpype/tests/test_pyblish_filter.py @@ -2,7 +2,7 @@ from . import lib import pyblish.api import pyblish.util import pyblish.plugin -from pype.lib import filter_pyblish_plugins +from openpype.lib import filter_pyblish_plugins import os diff --git a/pype/tools/__init__.py b/openpype/tools/__init__.py similarity index 100% rename from pype/tools/__init__.py rename to openpype/tools/__init__.py diff --git a/pype/tools/assetcreator/__init__.py b/openpype/tools/assetcreator/__init__.py similarity index 100% rename from pype/tools/assetcreator/__init__.py rename to openpype/tools/assetcreator/__init__.py diff --git a/pype/tools/assetcreator/__main__.py b/openpype/tools/assetcreator/__main__.py similarity index 100% rename from pype/tools/assetcreator/__main__.py rename to openpype/tools/assetcreator/__main__.py diff --git a/pype/tools/assetcreator/app.py b/openpype/tools/assetcreator/app.py similarity index 99% rename from pype/tools/assetcreator/app.py rename to openpype/tools/assetcreator/app.py index f025af9662..5c2553e81e 100644 --- a/pype/tools/assetcreator/app.py +++ b/openpype/tools/assetcreator/app.py @@ -6,8 +6,8 @@ try: import ftrack_api_old as ftrack_api except Exception: import ftrack_api -from pype.api import get_current_project_settings -from pype import lib as pypelib +from openpype.api import get_current_project_settings +from openpype import lib as pypelib from avalon.vendor.Qt import QtWidgets, QtCore from avalon import io, api, style, schema from avalon.tools import lib as parentlib @@ -396,7 +396,7 @@ class Window(QtWidgets.QDialog): new_asset_info = { 'parent': av_project['_id'], 'name': name, - 'schema': "avalon-core:asset-3.0", + 'schema': "openpype:asset-3.0", 'type': 'asset', 'data': new_asset_data } diff --git a/pype/tools/assetcreator/model.py b/openpype/tools/assetcreator/model.py similarity index 100% rename from pype/tools/assetcreator/model.py rename to openpype/tools/assetcreator/model.py diff --git a/pype/tools/assetcreator/widget.py b/openpype/tools/assetcreator/widget.py similarity index 100% rename from pype/tools/assetcreator/widget.py rename to openpype/tools/assetcreator/widget.py diff --git a/pype/tools/launcher/__init__.py b/openpype/tools/launcher/__init__.py similarity index 100% rename from pype/tools/launcher/__init__.py rename to openpype/tools/launcher/__init__.py diff --git a/pype/tools/launcher/actions.py b/openpype/tools/launcher/actions.py similarity index 98% rename from pype/tools/launcher/actions.py rename to openpype/tools/launcher/actions.py index aefa190768..6261fe91ca 100644 --- a/pype/tools/launcher/actions.py +++ b/openpype/tools/launcher/actions.py @@ -2,8 +2,8 @@ import os import importlib from avalon import api, lib, style -from pype.api import Logger, resources -from pype.lib import ( +from openpype.api import Logger, resources +from openpype.lib import ( ApplictionExecutableNotFound, ApplicationLaunchFailed ) diff --git a/pype/tools/launcher/constants.py b/openpype/tools/launcher/constants.py similarity index 100% rename from pype/tools/launcher/constants.py rename to openpype/tools/launcher/constants.py diff --git a/pype/tools/launcher/delegates.py b/openpype/tools/launcher/delegates.py similarity index 100% rename from pype/tools/launcher/delegates.py rename to openpype/tools/launcher/delegates.py diff --git a/pype/tools/launcher/flickcharm.py b/openpype/tools/launcher/flickcharm.py similarity index 100% rename from pype/tools/launcher/flickcharm.py rename to openpype/tools/launcher/flickcharm.py diff --git a/pype/tools/launcher/lib.py b/openpype/tools/launcher/lib.py similarity index 98% rename from pype/tools/launcher/lib.py rename to openpype/tools/launcher/lib.py index 7d2a49db9d..b4e6a0c3e9 100644 --- a/pype/tools/launcher/lib.py +++ b/openpype/tools/launcher/lib.py @@ -17,7 +17,7 @@ provides a bridge between the file-based project inventory and configuration. import os from Qt import QtGui from avalon.vendor import qtawesome -from pype.api import resources +from openpype.api import resources ICON_CACHE = {} NOT_FOUND = type("NotFound", (object, ), {}) diff --git a/pype/tools/launcher/models.py b/openpype/tools/launcher/models.py similarity index 98% rename from pype/tools/launcher/models.py rename to openpype/tools/launcher/models.py index d1742014ef..25b6dcdbf0 100644 --- a/pype/tools/launcher/models.py +++ b/openpype/tools/launcher/models.py @@ -14,7 +14,7 @@ from .actions import ApplicationAction from Qt import QtCore, QtGui from avalon.vendor import qtawesome from avalon import style, api -from pype.lib import ApplicationManager +from openpype.lib import ApplicationManager log = logging.getLogger(__name__) @@ -162,9 +162,9 @@ class ActionModel(QtGui.QStandardItemModel): (ApplicationAction,), { "application": app, - "name": app.app_name, - "label": app.label, - "label_variant": app.variant_label, + "name": app.name, + "label": app.group.label, + "label_variant": app.label, "group": None, "icon": app.icon, "color": getattr(app, "color", None), diff --git a/pype/tools/launcher/widgets.py b/openpype/tools/launcher/widgets.py similarity index 100% rename from pype/tools/launcher/widgets.py rename to openpype/tools/launcher/widgets.py diff --git a/pype/tools/launcher/window.py b/openpype/tools/launcher/window.py similarity index 99% rename from pype/tools/launcher/window.py rename to openpype/tools/launcher/window.py index f4ed7013a8..a89e724f1c 100644 --- a/pype/tools/launcher/window.py +++ b/openpype/tools/launcher/window.py @@ -5,7 +5,7 @@ from Qt import QtWidgets, QtCore, QtGui from avalon import style from avalon.api import AvalonMongoDB -from pype.api import resources +from openpype.api import resources from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget diff --git a/pype/tools/mayalookassigner/LICENSE b/openpype/tools/mayalookassigner/LICENSE similarity index 100% rename from pype/tools/mayalookassigner/LICENSE rename to openpype/tools/mayalookassigner/LICENSE diff --git a/pype/tools/mayalookassigner/__init__.py b/openpype/tools/mayalookassigner/__init__.py similarity index 100% rename from pype/tools/mayalookassigner/__init__.py rename to openpype/tools/mayalookassigner/__init__.py diff --git a/pype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py similarity index 99% rename from pype/tools/mayalookassigner/app.py rename to openpype/tools/mayalookassigner/app.py index 92ab7046a8..09782ea6ac 100644 --- a/pype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -2,7 +2,7 @@ import sys import time import logging -from pype.hosts.maya.api.lib import assign_look_by_version +from openpype.hosts.maya.api.lib import assign_look_by_version from avalon import style, io from avalon.tools import lib diff --git a/pype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py similarity index 99% rename from pype/tools/mayalookassigner/commands.py rename to openpype/tools/mayalookassigner/commands.py index a379a109f4..98eb3d37b7 100644 --- a/pype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -4,7 +4,7 @@ import os import maya.cmds as cmds -from pype.hosts.maya.api import lib +from openpype.hosts.maya.api import lib from avalon import io, api diff --git a/pype/tools/mayalookassigner/models.py b/openpype/tools/mayalookassigner/models.py similarity index 100% rename from pype/tools/mayalookassigner/models.py rename to openpype/tools/mayalookassigner/models.py diff --git a/pype/tools/mayalookassigner/views.py b/openpype/tools/mayalookassigner/views.py similarity index 100% rename from pype/tools/mayalookassigner/views.py rename to openpype/tools/mayalookassigner/views.py diff --git a/pype/tools/mayalookassigner/widgets.py b/openpype/tools/mayalookassigner/widgets.py similarity index 100% rename from pype/tools/mayalookassigner/widgets.py rename to openpype/tools/mayalookassigner/widgets.py diff --git a/pype/tools/pyblish_pype/__init__.py b/openpype/tools/pyblish_pype/__init__.py similarity index 100% rename from pype/tools/pyblish_pype/__init__.py rename to openpype/tools/pyblish_pype/__init__.py diff --git a/pype/tools/pyblish_pype/__main__.py b/openpype/tools/pyblish_pype/__main__.py similarity index 100% rename from pype/tools/pyblish_pype/__main__.py rename to openpype/tools/pyblish_pype/__main__.py diff --git a/pype/tools/pyblish_pype/app.css b/openpype/tools/pyblish_pype/app.css similarity index 100% rename from pype/tools/pyblish_pype/app.css rename to openpype/tools/pyblish_pype/app.css diff --git a/pype/tools/pyblish_pype/app.py b/openpype/tools/pyblish_pype/app.py similarity index 100% rename from pype/tools/pyblish_pype/app.py rename to openpype/tools/pyblish_pype/app.py diff --git a/pype/tools/pyblish_pype/awesome.py b/openpype/tools/pyblish_pype/awesome.py similarity index 100% rename from pype/tools/pyblish_pype/awesome.py rename to openpype/tools/pyblish_pype/awesome.py diff --git a/pype/tools/pyblish_pype/constants.py b/openpype/tools/pyblish_pype/constants.py similarity index 100% rename from pype/tools/pyblish_pype/constants.py rename to openpype/tools/pyblish_pype/constants.py diff --git a/pype/tools/pyblish_pype/control.py b/openpype/tools/pyblish_pype/control.py similarity index 99% rename from pype/tools/pyblish_pype/control.py rename to openpype/tools/pyblish_pype/control.py index 4f7a43d6d1..ae9ca40be5 100644 --- a/pype/tools/pyblish_pype/control.py +++ b/openpype/tools/pyblish_pype/control.py @@ -22,7 +22,7 @@ import pyblish.version from . import util from .constants import InstanceStates -from pype.api import get_project_settings +from openpype.api import get_project_settings class IterationBreak(Exception): diff --git a/pype/tools/pyblish_pype/delegate.py b/openpype/tools/pyblish_pype/delegate.py similarity index 100% rename from pype/tools/pyblish_pype/delegate.py rename to openpype/tools/pyblish_pype/delegate.py diff --git a/pype/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf b/openpype/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf rename to openpype/tools/pyblish_pype/font/fontawesome/fontawesome-webfont.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/LICENSE.txt b/openpype/tools/pyblish_pype/font/opensans/LICENSE.txt similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/LICENSE.txt rename to openpype/tools/pyblish_pype/font/opensans/LICENSE.txt diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-Bold.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-BoldItalic.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBold.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-ExtraBoldItalic.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-Italic.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-Light.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-LightItalic.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-Regular.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-Semibold.ttf diff --git a/pype/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf b/openpype/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf similarity index 100% rename from pype/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf rename to openpype/tools/pyblish_pype/font/opensans/OpenSans-SemiboldItalic.ttf diff --git a/pype/tools/pyblish_pype/i18n/pyblish_lite.pro b/openpype/tools/pyblish_pype/i18n/pyblish_lite.pro similarity index 100% rename from pype/tools/pyblish_pype/i18n/pyblish_lite.pro rename to openpype/tools/pyblish_pype/i18n/pyblish_lite.pro diff --git a/pype/tools/pyblish_pype/i18n/zh_CN.qm b/openpype/tools/pyblish_pype/i18n/zh_CN.qm similarity index 100% rename from pype/tools/pyblish_pype/i18n/zh_CN.qm rename to openpype/tools/pyblish_pype/i18n/zh_CN.qm diff --git a/pype/tools/pyblish_pype/i18n/zh_CN.ts b/openpype/tools/pyblish_pype/i18n/zh_CN.ts similarity index 100% rename from pype/tools/pyblish_pype/i18n/zh_CN.ts rename to openpype/tools/pyblish_pype/i18n/zh_CN.ts diff --git a/pype/tools/pyblish_pype/img/down_arrow.png b/openpype/tools/pyblish_pype/img/down_arrow.png similarity index 100% rename from pype/tools/pyblish_pype/img/down_arrow.png rename to openpype/tools/pyblish_pype/img/down_arrow.png diff --git a/pype/tools/pyblish_pype/img/logo-extrasmall.png b/openpype/tools/pyblish_pype/img/logo-extrasmall.png similarity index 100% rename from pype/tools/pyblish_pype/img/logo-extrasmall.png rename to openpype/tools/pyblish_pype/img/logo-extrasmall.png diff --git a/pype/tools/pyblish_pype/img/tab-overview.png b/openpype/tools/pyblish_pype/img/tab-overview.png similarity index 100% rename from pype/tools/pyblish_pype/img/tab-overview.png rename to openpype/tools/pyblish_pype/img/tab-overview.png diff --git a/pype/tools/pyblish_pype/img/tab-terminal.png b/openpype/tools/pyblish_pype/img/tab-terminal.png similarity index 100% rename from pype/tools/pyblish_pype/img/tab-terminal.png rename to openpype/tools/pyblish_pype/img/tab-terminal.png diff --git a/pype/tools/pyblish_pype/mock.py b/openpype/tools/pyblish_pype/mock.py similarity index 100% rename from pype/tools/pyblish_pype/mock.py rename to openpype/tools/pyblish_pype/mock.py diff --git a/pype/tools/pyblish_pype/model.py b/openpype/tools/pyblish_pype/model.py similarity index 99% rename from pype/tools/pyblish_pype/model.py rename to openpype/tools/pyblish_pype/model.py index b537d7724d..50ba27166b 100644 --- a/pype/tools/pyblish_pype/model.py +++ b/openpype/tools/pyblish_pype/model.py @@ -35,7 +35,7 @@ from six import text_type from .vendor import qtawesome from .constants import PluginStates, InstanceStates, GroupStates, Roles -from pype.api import get_system_settings +from openpype.api import get_system_settings # ItemTypes diff --git a/pype/tools/pyblish_pype/settings.py b/openpype/tools/pyblish_pype/settings.py similarity index 89% rename from pype/tools/pyblish_pype/settings.py rename to openpype/tools/pyblish_pype/settings.py index 5848cdf698..11539f67a6 100644 --- a/pype/tools/pyblish_pype/settings.py +++ b/openpype/tools/pyblish_pype/settings.py @@ -24,4 +24,4 @@ TerminalFilters = { } # Allow animations in GUI -Animated = env_variable_to_bool("PYPE_PYBLISH_ANIMATED", True) +Animated = env_variable_to_bool("OPENPYPE_PYBLISH_ANIMATED", True) diff --git a/pype/tools/pyblish_pype/util.py b/openpype/tools/pyblish_pype/util.py similarity index 100% rename from pype/tools/pyblish_pype/util.py rename to openpype/tools/pyblish_pype/util.py diff --git a/pype/tools/pyblish_pype/vendor/__init__.py b/openpype/tools/pyblish_pype/vendor/__init__.py similarity index 100% rename from pype/tools/pyblish_pype/vendor/__init__.py rename to openpype/tools/pyblish_pype/vendor/__init__.py diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/__init__.py b/openpype/tools/pyblish_pype/vendor/qtawesome/__init__.py similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/__init__.py rename to openpype/tools/pyblish_pype/vendor/qtawesome/__init__.py diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/_version.py b/openpype/tools/pyblish_pype/vendor/qtawesome/_version.py similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/_version.py rename to openpype/tools/pyblish_pype/vendor/qtawesome/_version.py diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/animation.py b/openpype/tools/pyblish_pype/vendor/qtawesome/animation.py similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/animation.py rename to openpype/tools/pyblish_pype/vendor/qtawesome/animation.py diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json b/openpype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json rename to openpype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont-charmap.json diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf b/openpype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf rename to openpype/tools/pyblish_pype/vendor/qtawesome/fonts/elusiveicons-webfont.ttf diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json b/openpype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json rename to openpype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont-charmap.json diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf b/openpype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf rename to openpype/tools/pyblish_pype/vendor/qtawesome/fonts/fontawesome-webfont.ttf diff --git a/pype/tools/pyblish_pype/vendor/qtawesome/iconic_font.py b/openpype/tools/pyblish_pype/vendor/qtawesome/iconic_font.py similarity index 100% rename from pype/tools/pyblish_pype/vendor/qtawesome/iconic_font.py rename to openpype/tools/pyblish_pype/vendor/qtawesome/iconic_font.py diff --git a/pype/tools/pyblish_pype/version.py b/openpype/tools/pyblish_pype/version.py similarity index 100% rename from pype/tools/pyblish_pype/version.py rename to openpype/tools/pyblish_pype/version.py diff --git a/pype/tools/pyblish_pype/view.py b/openpype/tools/pyblish_pype/view.py similarity index 100% rename from pype/tools/pyblish_pype/view.py rename to openpype/tools/pyblish_pype/view.py diff --git a/pype/tools/pyblish_pype/widgets.py b/openpype/tools/pyblish_pype/widgets.py similarity index 100% rename from pype/tools/pyblish_pype/widgets.py rename to openpype/tools/pyblish_pype/widgets.py diff --git a/pype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py similarity index 100% rename from pype/tools/pyblish_pype/window.py rename to openpype/tools/pyblish_pype/window.py diff --git a/pype/tools/settings/__init__.py b/openpype/tools/settings/__init__.py similarity index 100% rename from pype/tools/settings/__init__.py rename to openpype/tools/settings/__init__.py diff --git a/pype/tools/settings/__main__.py b/openpype/tools/settings/__main__.py similarity index 100% rename from pype/tools/settings/__main__.py rename to openpype/tools/settings/__main__.py diff --git a/pype/tools/settings/local_settings/__init__.py b/openpype/tools/settings/local_settings/__init__.py similarity index 100% rename from pype/tools/settings/local_settings/__init__.py rename to openpype/tools/settings/local_settings/__init__.py diff --git a/pype/tools/settings/local_settings/apps_widget.py b/openpype/tools/settings/local_settings/apps_widget.py similarity index 100% rename from pype/tools/settings/local_settings/apps_widget.py rename to openpype/tools/settings/local_settings/apps_widget.py diff --git a/pype/tools/settings/local_settings/constants.py b/openpype/tools/settings/local_settings/constants.py similarity index 100% rename from pype/tools/settings/local_settings/constants.py rename to openpype/tools/settings/local_settings/constants.py diff --git a/pype/tools/settings/local_settings/general_widget.py b/openpype/tools/settings/local_settings/general_widget.py similarity index 100% rename from pype/tools/settings/local_settings/general_widget.py rename to openpype/tools/settings/local_settings/general_widget.py diff --git a/pype/tools/settings/local_settings/mongo_widget.py b/openpype/tools/settings/local_settings/mongo_widget.py similarity index 73% rename from pype/tools/settings/local_settings/mongo_widget.py rename to openpype/tools/settings/local_settings/mongo_widget.py index c6f6ab1591..eebafdffdd 100644 --- a/pype/tools/settings/local_settings/mongo_widget.py +++ b/openpype/tools/settings/local_settings/mongo_widget.py @@ -5,16 +5,16 @@ import traceback from Qt import QtWidgets from pymongo.errors import ServerSelectionTimeoutError -from pype.api import change_pype_mongo_url +from openpype.api import change_openpype_mongo_url -class PypeMongoWidget(QtWidgets.QWidget): +class OpenPypeMongoWidget(QtWidgets.QWidget): def __init__(self, parent): - super(PypeMongoWidget, self).__init__(parent) + super(OpenPypeMongoWidget, self).__init__(parent) # Warning label warning_label = QtWidgets.QLabel(( - "WARNING: Requires restart. Change of Pype Mongo requires to" + "WARNING: Requires restart. Change of OpenPype Mongo requires to" " restart of all running Pype processes and process using Pype" " (Including this)." "\n- all changes in different categories won't be saved." @@ -22,12 +22,12 @@ class PypeMongoWidget(QtWidgets.QWidget): warning_label.setStyleSheet("font-weight: bold;") # Label - mongo_url_label = QtWidgets.QLabel("Pype Mongo URL", self) + mongo_url_label = QtWidgets.QLabel("OpenPype Mongo URL", self) # Input mongo_url_input = QtWidgets.QLineEdit(self) - mongo_url_input.setPlaceholderText("< Pype Mongo URL >") - mongo_url_input.setText(os.environ["PYPE_MONGO"]) + mongo_url_input.setPlaceholderText("< OpenPype Mongo URL >") + mongo_url_input.setText(os.environ["OPENPYPE_MONGO"]) # Confirm button mongo_url_change_btn = QtWidgets.QPushButton("Confirm Change", self) @@ -48,14 +48,15 @@ class PypeMongoWidget(QtWidgets.QWidget): dialog = QtWidgets.QMessageBox(self) - title = "Pype mongo changed" + title = "OpenPype mongo changed" message = ( - "Pype mongo url was successfully changed. Restart Pype please." + "OpenPype mongo url was successfully changed." + " Restart OpenPype application please." ) details = None try: - change_pype_mongo_url(value) + change_openpype_mongo_url(value) except Exception as exc: if isinstance(exc, ServerSelectionTimeoutError): error_message = ( @@ -65,10 +66,10 @@ class PypeMongoWidget(QtWidgets.QWidget): else: error_message = str(exc) - title = "Pype mongo change failed" + title = "OpenPype mongo change failed" # TODO catch exception message more gracefully message = ( - "Pype mongo change was not successful." + "OpenPype mongo change was not successful." " Full traceback can be found in details section.\n\n" "Error message:\n{}" ).format(error_message) diff --git a/pype/tools/settings/local_settings/projects_widget.py b/openpype/tools/settings/local_settings/projects_widget.py similarity index 99% rename from pype/tools/settings/local_settings/projects_widget.py rename to openpype/tools/settings/local_settings/projects_widget.py index 2a7d801a03..a48c504d59 100644 --- a/pype/tools/settings/local_settings/projects_widget.py +++ b/openpype/tools/settings/local_settings/projects_widget.py @@ -1,8 +1,8 @@ import platform import copy from Qt import QtWidgets, QtCore, QtGui -from pype.tools.settings.settings import ProjectListWidget -from pype.settings.constants import ( +from openpype.tools.settings.settings import ProjectListWidget +from openpype.settings.constants import ( PROJECT_ANATOMY_KEY, DEFAULT_PROJECT_KEY ) diff --git a/pype/tools/settings/local_settings/widgets.py b/openpype/tools/settings/local_settings/widgets.py similarity index 96% rename from pype/tools/settings/local_settings/widgets.py rename to openpype/tools/settings/local_settings/widgets.py index 2293fdfed5..a262188906 100644 --- a/pype/tools/settings/local_settings/widgets.py +++ b/openpype/tools/settings/local_settings/widgets.py @@ -1,5 +1,5 @@ from Qt import QtWidgets, QtCore -from pype.tools.settings.settings.widgets.widgets import ( +from openpype.tools.settings.settings.widgets.widgets import ( ExpandingWidget, SpacerWidget ) diff --git a/pype/tools/settings/local_settings/window.py b/openpype/tools/settings/local_settings/window.py similarity index 94% rename from pype/tools/settings/local_settings/window.py rename to openpype/tools/settings/local_settings/window.py index c5f5d15831..b6ca56d348 100644 --- a/pype/tools/settings/local_settings/window.py +++ b/openpype/tools/settings/local_settings/window.py @@ -3,21 +3,21 @@ from Qt import QtWidgets, QtGui from ..settings import style -from pype.settings.lib import ( +from openpype.settings.lib import ( get_local_settings, save_local_settings ) -from pype.api import ( +from openpype.api import ( SystemSettings, ProjectSettings ) -from pype.modules import ModulesManager +from openpype.modules import ModulesManager from .widgets import ( SpacerWidget, ExpandingWidget ) -from .mongo_widget import PypeMongoWidget +from .mongo_widget import OpenPypeMongoWidget from .general_widget import LocalGeneralWidgets from .apps_widget import LocalApplicationsWidgets from .projects_widget import ProjectSettingsWidget @@ -56,13 +56,13 @@ class LocalSettingsWidget(QtWidgets.QWidget): self.main_layout.addWidget(SpacerWidget(self), 1) def _create_pype_mongo_ui(self): - pype_mongo_expand_widget = ExpandingWidget("Pype Mongo URL", self) + pype_mongo_expand_widget = ExpandingWidget("OpenPype Mongo URL", self) pype_mongo_content = QtWidgets.QWidget(self) pype_mongo_layout = QtWidgets.QVBoxLayout(pype_mongo_content) pype_mongo_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) pype_mongo_expand_widget.set_content_widget(pype_mongo_content) - pype_mongo_widget = PypeMongoWidget(self) + pype_mongo_widget = OpenPypeMongoWidget(self) pype_mongo_layout.addWidget(pype_mongo_widget) self.main_layout.addWidget(pype_mongo_expand_widget) @@ -158,7 +158,7 @@ class LocalSettingsWindow(QtWidgets.QWidget): self.resize(1000, 600) - self.setWindowTitle("Pype Local settings") + self.setWindowTitle("OpenPype Local settings") stylesheet = style.load_stylesheet() self.setStyleSheet(stylesheet) diff --git a/pype/tools/settings/settings/README.md b/openpype/tools/settings/settings/README.md similarity index 98% rename from pype/tools/settings/settings/README.md rename to openpype/tools/settings/settings/README.md index 65d73a0728..31d8fc5b74 100644 --- a/pype/tools/settings/settings/README.md +++ b/openpype/tools/settings/settings/README.md @@ -7,9 +7,9 @@ - each input may have "input modifiers" (keys in dictionary) that are required or optional - only required modifier for all input items is key `"type"` which says what type of item it is - there are special keys across all inputs - - `"is_file"` - this key is for storing pype defaults in `pype` repo + - `"is_file"` - this key is for storing openpype defaults in `openpype` repo - reasons of existence: developing new schemas does not require to create defaults manually - - key is validated, must be once in hierarchy else it won't be possible to store pype defaults + - key is validated, must be once in hierarchy else it won't be possible to store openpype defaults - `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides - this keys is not allowed for all inputs as they may have not reason for that - key is validated, can be only once in hierarchy but is not required @@ -191,7 +191,7 @@ { "type": "boolean", "key": "my_boolean_key", - "label": "Do you want to use Pype?" + "label": "Do you want to use OpenPype?" } ``` diff --git a/pype/tools/settings/settings/__init__.py b/openpype/tools/settings/settings/__init__.py similarity index 100% rename from pype/tools/settings/settings/__init__.py rename to openpype/tools/settings/settings/__init__.py diff --git a/pype/tools/settings/settings/style/__init__.py b/openpype/tools/settings/settings/style/__init__.py similarity index 76% rename from pype/tools/settings/settings/style/__init__.py rename to openpype/tools/settings/settings/style/__init__.py index a8f202d97b..9bb5e851b4 100644 --- a/pype/tools/settings/settings/style/__init__.py +++ b/openpype/tools/settings/settings/style/__init__.py @@ -9,4 +9,4 @@ def load_stylesheet(): def app_icon_path(): - return os.path.join(os.path.dirname(__file__), "pype_icon.png") + return os.path.join(os.path.dirname(__file__), "openpype_icon.png") diff --git a/pype/resources/icons/pype_icon.png b/openpype/tools/settings/settings/style/pype_icon.png similarity index 100% rename from pype/resources/icons/pype_icon.png rename to openpype/tools/settings/settings/style/pype_icon.png diff --git a/pype/tools/settings/settings/style/style.css b/openpype/tools/settings/settings/style/style.css similarity index 100% rename from pype/tools/settings/settings/style/style.css rename to openpype/tools/settings/settings/style/style.css diff --git a/pype/tools/settings/settings/widgets/__init__.py b/openpype/tools/settings/settings/widgets/__init__.py similarity index 100% rename from pype/tools/settings/settings/widgets/__init__.py rename to openpype/tools/settings/settings/widgets/__init__.py diff --git a/pype/tools/settings/settings/widgets/base.py b/openpype/tools/settings/settings/widgets/base.py similarity index 100% rename from pype/tools/settings/settings/widgets/base.py rename to openpype/tools/settings/settings/widgets/base.py diff --git a/pype/tools/settings/settings/widgets/categories.py b/openpype/tools/settings/settings/widgets/categories.py similarity index 99% rename from pype/tools/settings/settings/widgets/categories.py rename to openpype/tools/settings/settings/widgets/categories.py index f1e154ee4d..9d286485a3 100644 --- a/pype/tools/settings/settings/widgets/categories.py +++ b/openpype/tools/settings/settings/widgets/categories.py @@ -4,7 +4,7 @@ import traceback from enum import Enum from Qt import QtWidgets, QtCore, QtGui -from pype.settings.entities import ( +from openpype.settings.entities import ( SystemSettings, ProjectSettings, @@ -27,7 +27,7 @@ from pype.settings.entities import ( SchemaError ) -from pype.settings.lib import get_system_settings +from openpype.settings.lib import get_system_settings from .widgets import ProjectListWidget from . import lib diff --git a/pype/tools/settings/settings/widgets/dict_mutable_widget.py b/openpype/tools/settings/settings/widgets/dict_mutable_widget.py similarity index 99% rename from pype/tools/settings/settings/widgets/dict_mutable_widget.py rename to openpype/tools/settings/settings/widgets/dict_mutable_widget.py index e44ffdf35a..3b5f15f519 100644 --- a/pype/tools/settings/settings/widgets/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/widgets/dict_mutable_widget.py @@ -12,7 +12,7 @@ from .lib import ( BTN_FIXED_SIZE, CHILD_OFFSET ) -from pype.settings.constants import KEY_REGEX +from openpype.settings.constants import KEY_REGEX def create_add_btn(parent): diff --git a/pype/tools/settings/settings/widgets/item_widgets.py b/openpype/tools/settings/settings/widgets/item_widgets.py similarity index 99% rename from pype/tools/settings/settings/widgets/item_widgets.py rename to openpype/tools/settings/settings/widgets/item_widgets.py index e2e31a7588..6045b05227 100644 --- a/pype/tools/settings/settings/widgets/item_widgets.py +++ b/openpype/tools/settings/settings/widgets/item_widgets.py @@ -59,6 +59,9 @@ class DictImmutableKeysWidget(BaseWidget): ) ) + if self.entity.use_label_wrap and self.content_layout.count() == 0: + self.body_widget.hide_toolbox(True) + self.entity_widget.add_widget_to_layout(self, label) def _prepare_entity_layouts(self, children, widget): diff --git a/pype/tools/settings/settings/widgets/lib.py b/openpype/tools/settings/settings/widgets/lib.py similarity index 99% rename from pype/tools/settings/settings/widgets/lib.py rename to openpype/tools/settings/settings/widgets/lib.py index d399c94ddc..aeca943617 100644 --- a/pype/tools/settings/settings/widgets/lib.py +++ b/openpype/tools/settings/settings/widgets/lib.py @@ -2,7 +2,7 @@ import os import re import json import copy -from pype.settings.constants import ( +from openpype.settings.constants import ( M_OVERRIDEN_KEY, M_ENVIRONMENT_KEY, M_DYNAMIC_KEY_LABEL diff --git a/pype/tools/settings/settings/widgets/list_item_widget.py b/openpype/tools/settings/settings/widgets/list_item_widget.py similarity index 100% rename from pype/tools/settings/settings/widgets/list_item_widget.py rename to openpype/tools/settings/settings/widgets/list_item_widget.py diff --git a/pype/tools/settings/settings/widgets/list_strict_widget.py b/openpype/tools/settings/settings/widgets/list_strict_widget.py similarity index 100% rename from pype/tools/settings/settings/widgets/list_strict_widget.py rename to openpype/tools/settings/settings/widgets/list_strict_widget.py diff --git a/pype/tools/settings/settings/widgets/multiselection_combobox.py b/openpype/tools/settings/settings/widgets/multiselection_combobox.py similarity index 100% rename from pype/tools/settings/settings/widgets/multiselection_combobox.py rename to openpype/tools/settings/settings/widgets/multiselection_combobox.py diff --git a/pype/tools/settings/settings/widgets/tests.py b/openpype/tools/settings/settings/widgets/tests.py similarity index 100% rename from pype/tools/settings/settings/widgets/tests.py rename to openpype/tools/settings/settings/widgets/tests.py diff --git a/pype/tools/settings/settings/widgets/widgets.py b/openpype/tools/settings/settings/widgets/widgets.py similarity index 99% rename from pype/tools/settings/settings/widgets/widgets.py rename to openpype/tools/settings/settings/widgets/widgets.py index ccd437ece3..aa79cc4b62 100644 --- a/pype/tools/settings/settings/widgets/widgets.py +++ b/openpype/tools/settings/settings/widgets/widgets.py @@ -6,7 +6,7 @@ from avalon.mongodb import ( AvalonMongoDB ) -from pype.settings.lib import get_system_settings +from openpype.settings.lib import get_system_settings class ShadowWidget(QtWidgets.QWidget): @@ -643,7 +643,7 @@ class ProjectListWidget(QtWidgets.QWidget): items = [self.default] - mongo_url = os.environ["PYPE_MONGO"] + mongo_url = os.environ["OPENPYPE_MONGO"] # Force uninstall of whole avalon connection if url does not match # to current environment and set it as environment diff --git a/pype/tools/settings/settings/widgets/window.py b/openpype/tools/settings/settings/widgets/window.py similarity index 97% rename from pype/tools/settings/settings/widgets/window.py rename to openpype/tools/settings/settings/widgets/window.py index 058611c36a..96275facff 100644 --- a/pype/tools/settings/settings/widgets/window.py +++ b/openpype/tools/settings/settings/widgets/window.py @@ -15,7 +15,7 @@ class MainWidget(QtWidgets.QWidget): def __init__(self, user_role, parent=None): super(MainWidget, self).__init__(parent) self.setObjectName("MainWidget") - self.setWindowTitle("Pype Settings") + self.setWindowTitle("OpenPype Settings") self.resize(self.widget_width, self.widget_height) diff --git a/pype/tools/settings/settings/widgets/wrapper_widgets.py b/openpype/tools/settings/settings/widgets/wrapper_widgets.py similarity index 100% rename from pype/tools/settings/settings/widgets/wrapper_widgets.py rename to openpype/tools/settings/settings/widgets/wrapper_widgets.py diff --git a/pype/tools/standalonepublish/__init__.py b/openpype/tools/standalonepublish/__init__.py similarity index 100% rename from pype/tools/standalonepublish/__init__.py rename to openpype/tools/standalonepublish/__init__.py diff --git a/pype/tools/standalonepublish/app.py b/openpype/tools/standalonepublish/app.py similarity index 98% rename from pype/tools/standalonepublish/app.py rename to openpype/tools/standalonepublish/app.py index 54cde9e322..7d25a8ca55 100644 --- a/pype/tools/standalonepublish/app.py +++ b/openpype/tools/standalonepublish/app.py @@ -11,9 +11,9 @@ from .widgets import ( ) from .widgets.constants import HOST_NAME from avalon import style -from pype.api import resources +from openpype.api import resources from avalon.api import AvalonMongoDB -from pype.modules import ModulesManager +from openpype.modules import ModulesManager class Window(QtWidgets.QDialog): diff --git a/pype/tools/standalonepublish/publish.py b/openpype/tools/standalonepublish/publish.py similarity index 93% rename from pype/tools/standalonepublish/publish.py rename to openpype/tools/standalonepublish/publish.py index cfa9f8b8e8..af269c4381 100644 --- a/pype/tools/standalonepublish/publish.py +++ b/openpype/tools/standalonepublish/publish.py @@ -1,14 +1,14 @@ import os import sys -import pype +import openpype import pyblish.api def main(env): from avalon.tools import publish # Registers pype's Global pyblish plugins - pype.install() + openpype.install() # Register additional paths addition_paths_str = env.get("PUBLISH_PATHS") or "" diff --git a/pype/tools/standalonepublish/widgets/__init__.py b/openpype/tools/standalonepublish/widgets/__init__.py similarity index 100% rename from pype/tools/standalonepublish/widgets/__init__.py rename to openpype/tools/standalonepublish/widgets/__init__.py diff --git a/pype/tools/standalonepublish/widgets/constants.py b/openpype/tools/standalonepublish/widgets/constants.py similarity index 100% rename from pype/tools/standalonepublish/widgets/constants.py rename to openpype/tools/standalonepublish/widgets/constants.py diff --git a/pype/tools/standalonepublish/widgets/model_asset.py b/openpype/tools/standalonepublish/widgets/model_asset.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_asset.py rename to openpype/tools/standalonepublish/widgets/model_asset.py diff --git a/pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py b/openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py rename to openpype/tools/standalonepublish/widgets/model_filter_proxy_exact_match.py diff --git a/pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py b/openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py rename to openpype/tools/standalonepublish/widgets/model_filter_proxy_recursive_sort.py diff --git a/pype/tools/standalonepublish/widgets/model_node.py b/openpype/tools/standalonepublish/widgets/model_node.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_node.py rename to openpype/tools/standalonepublish/widgets/model_node.py diff --git a/pype/tools/standalonepublish/widgets/model_tasks_template.py b/openpype/tools/standalonepublish/widgets/model_tasks_template.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_tasks_template.py rename to openpype/tools/standalonepublish/widgets/model_tasks_template.py diff --git a/pype/tools/standalonepublish/widgets/model_tree.py b/openpype/tools/standalonepublish/widgets/model_tree.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_tree.py rename to openpype/tools/standalonepublish/widgets/model_tree.py diff --git a/pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py b/openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py similarity index 100% rename from pype/tools/standalonepublish/widgets/model_tree_view_deselectable.py rename to openpype/tools/standalonepublish/widgets/model_tree_view_deselectable.py diff --git a/pype/tools/standalonepublish/widgets/resources/__init__.py b/openpype/tools/standalonepublish/widgets/resources/__init__.py similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/__init__.py rename to openpype/tools/standalonepublish/widgets/resources/__init__.py diff --git a/pype/tools/standalonepublish/widgets/resources/edit.svg b/openpype/tools/standalonepublish/widgets/resources/edit.svg similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/edit.svg rename to openpype/tools/standalonepublish/widgets/resources/edit.svg diff --git a/pype/tools/standalonepublish/widgets/resources/file.png b/openpype/tools/standalonepublish/widgets/resources/file.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/file.png rename to openpype/tools/standalonepublish/widgets/resources/file.png diff --git a/pype/tools/standalonepublish/widgets/resources/files.png b/openpype/tools/standalonepublish/widgets/resources/files.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/files.png rename to openpype/tools/standalonepublish/widgets/resources/files.png diff --git a/pype/tools/standalonepublish/widgets/resources/houdini.png b/openpype/tools/standalonepublish/widgets/resources/houdini.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/houdini.png rename to openpype/tools/standalonepublish/widgets/resources/houdini.png diff --git a/pype/tools/standalonepublish/widgets/resources/image_file.png b/openpype/tools/standalonepublish/widgets/resources/image_file.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/image_file.png rename to openpype/tools/standalonepublish/widgets/resources/image_file.png diff --git a/pype/tools/standalonepublish/widgets/resources/image_files.png b/openpype/tools/standalonepublish/widgets/resources/image_files.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/image_files.png rename to openpype/tools/standalonepublish/widgets/resources/image_files.png diff --git a/pype/tools/standalonepublish/widgets/resources/information.svg b/openpype/tools/standalonepublish/widgets/resources/information.svg similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/information.svg rename to openpype/tools/standalonepublish/widgets/resources/information.svg diff --git a/pype/tools/standalonepublish/widgets/resources/maya.png b/openpype/tools/standalonepublish/widgets/resources/maya.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/maya.png rename to openpype/tools/standalonepublish/widgets/resources/maya.png diff --git a/pype/tools/standalonepublish/widgets/resources/menu.png b/openpype/tools/standalonepublish/widgets/resources/menu.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/menu.png rename to openpype/tools/standalonepublish/widgets/resources/menu.png diff --git a/pype/tools/standalonepublish/widgets/resources/menu_disabled.png b/openpype/tools/standalonepublish/widgets/resources/menu_disabled.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/menu_disabled.png rename to openpype/tools/standalonepublish/widgets/resources/menu_disabled.png diff --git a/pype/tools/standalonepublish/widgets/resources/menu_hover.png b/openpype/tools/standalonepublish/widgets/resources/menu_hover.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/menu_hover.png rename to openpype/tools/standalonepublish/widgets/resources/menu_hover.png diff --git a/pype/tools/standalonepublish/widgets/resources/menu_pressed.png b/openpype/tools/standalonepublish/widgets/resources/menu_pressed.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/menu_pressed.png rename to openpype/tools/standalonepublish/widgets/resources/menu_pressed.png diff --git a/pype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png b/openpype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png rename to openpype/tools/standalonepublish/widgets/resources/menu_pressed_hover.png diff --git a/pype/tools/standalonepublish/widgets/resources/nuke.png b/openpype/tools/standalonepublish/widgets/resources/nuke.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/nuke.png rename to openpype/tools/standalonepublish/widgets/resources/nuke.png diff --git a/pype/tools/standalonepublish/widgets/resources/premiere.png b/openpype/tools/standalonepublish/widgets/resources/premiere.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/premiere.png rename to openpype/tools/standalonepublish/widgets/resources/premiere.png diff --git a/pype/tools/standalonepublish/widgets/resources/trash.png b/openpype/tools/standalonepublish/widgets/resources/trash.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/trash.png rename to openpype/tools/standalonepublish/widgets/resources/trash.png diff --git a/pype/tools/standalonepublish/widgets/resources/trash_disabled.png b/openpype/tools/standalonepublish/widgets/resources/trash_disabled.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/trash_disabled.png rename to openpype/tools/standalonepublish/widgets/resources/trash_disabled.png diff --git a/pype/tools/standalonepublish/widgets/resources/trash_hover.png b/openpype/tools/standalonepublish/widgets/resources/trash_hover.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/trash_hover.png rename to openpype/tools/standalonepublish/widgets/resources/trash_hover.png diff --git a/pype/tools/standalonepublish/widgets/resources/trash_pressed.png b/openpype/tools/standalonepublish/widgets/resources/trash_pressed.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/trash_pressed.png rename to openpype/tools/standalonepublish/widgets/resources/trash_pressed.png diff --git a/pype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png b/openpype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png rename to openpype/tools/standalonepublish/widgets/resources/trash_pressed_hover.png diff --git a/pype/tools/standalonepublish/widgets/resources/video_file.png b/openpype/tools/standalonepublish/widgets/resources/video_file.png similarity index 100% rename from pype/tools/standalonepublish/widgets/resources/video_file.png rename to openpype/tools/standalonepublish/widgets/resources/video_file.png diff --git a/pype/tools/standalonepublish/widgets/widget_asset.py b/openpype/tools/standalonepublish/widgets/widget_asset.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_asset.py rename to openpype/tools/standalonepublish/widgets/widget_asset.py diff --git a/pype/tools/standalonepublish/widgets/widget_component_item.py b/openpype/tools/standalonepublish/widgets/widget_component_item.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_component_item.py rename to openpype/tools/standalonepublish/widgets/widget_component_item.py diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/openpype/tools/standalonepublish/widgets/widget_components.py similarity index 99% rename from pype/tools/standalonepublish/widgets/widget_components.py rename to openpype/tools/standalonepublish/widgets/widget_components.py index d09c12f0ab..e6682d97aa 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/openpype/tools/standalonepublish/widgets/widget_components.py @@ -8,8 +8,8 @@ from Qt import QtWidgets, QtCore from . import DropDataFrame from .constants import HOST_NAME from avalon import io -from pype.api import execute, Logger -from pype.lib import ( +from openpype.api import execute, Logger +from openpype.lib import ( get_pype_execute_args, apply_project_environments_value ) diff --git a/pype/tools/standalonepublish/widgets/widget_components_list.py b/openpype/tools/standalonepublish/widgets/widget_components_list.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_components_list.py rename to openpype/tools/standalonepublish/widgets/widget_components_list.py diff --git a/pype/tools/standalonepublish/widgets/widget_drop_empty.py b/openpype/tools/standalonepublish/widgets/widget_drop_empty.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_drop_empty.py rename to openpype/tools/standalonepublish/widgets/widget_drop_empty.py diff --git a/pype/tools/standalonepublish/widgets/widget_drop_frame.py b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py similarity index 99% rename from pype/tools/standalonepublish/widgets/widget_drop_frame.py rename to openpype/tools/standalonepublish/widgets/widget_drop_frame.py index 339093cf73..63dcb82e83 100644 --- a/pype/tools/standalonepublish/widgets/widget_drop_frame.py +++ b/openpype/tools/standalonepublish/widgets/widget_drop_frame.py @@ -3,7 +3,7 @@ import re import json import clique import subprocess -import pype.lib +import openpype.lib from Qt import QtWidgets, QtCore from . import DropEmpty, ComponentsList, ComponentItem @@ -264,7 +264,7 @@ class DropDataFrame(QtWidgets.QFrame): self._process_data(data) def load_data_with_probe(self, filepath): - ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe") + ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe") args = [ "\"{}\"".format(ffprobe_path), '-v', 'quiet', diff --git a/pype/tools/standalonepublish/widgets/widget_family.py b/openpype/tools/standalonepublish/widgets/widget_family.py similarity index 99% rename from pype/tools/standalonepublish/widgets/widget_family.py rename to openpype/tools/standalonepublish/widgets/widget_family.py index 3150646624..50335e3109 100644 --- a/pype/tools/standalonepublish/widgets/widget_family.py +++ b/openpype/tools/standalonepublish/widgets/widget_family.py @@ -5,11 +5,11 @@ from Qt import QtWidgets, QtCore from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget -from pype.api import ( +from openpype.api import ( get_project_settings, Creator ) -from pype.plugin import TaskNotSetError +from openpype.lib import TaskNotSetError from avalon.tools.creator.app import SubsetAllowedSymbols diff --git a/pype/tools/standalonepublish/widgets/widget_family_desc.py b/openpype/tools/standalonepublish/widgets/widget_family_desc.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_family_desc.py rename to openpype/tools/standalonepublish/widgets/widget_family_desc.py diff --git a/pype/tools/standalonepublish/widgets/widget_shadow.py b/openpype/tools/standalonepublish/widgets/widget_shadow.py similarity index 100% rename from pype/tools/standalonepublish/widgets/widget_shadow.py rename to openpype/tools/standalonepublish/widgets/widget_shadow.py diff --git a/pype/tools/texture_copy/app.py b/openpype/tools/texture_copy/app.py similarity index 98% rename from pype/tools/texture_copy/app.py rename to openpype/tools/texture_copy/app.py index ad8f239d2f..ceca98a082 100644 --- a/pype/tools/texture_copy/app.py +++ b/openpype/tools/texture_copy/app.py @@ -4,8 +4,8 @@ import click from avalon import io, api from pprint import pprint -from pype.lib import Terminal -from pype.api import Anatomy +from openpype.lib import Terminal +from openpype.api import Anatomy import shutil import speedcopy diff --git a/pype/tools/tray/__init__.py b/openpype/tools/tray/__init__.py similarity index 100% rename from pype/tools/tray/__init__.py rename to openpype/tools/tray/__init__.py diff --git a/pype/tools/tray/__main__.py b/openpype/tools/tray/__main__.py similarity index 100% rename from pype/tools/tray/__main__.py rename to openpype/tools/tray/__main__.py diff --git a/pype/tools/tray/pype_info_widget.py b/openpype/tools/tray/pype_info_widget.py similarity index 95% rename from pype/tools/tray/pype_info_widget.py rename to openpype/tools/tray/pype_info_widget.py index a4c52eb1d0..dbff36eca7 100644 --- a/pype/tools/tray/pype_info_widget.py +++ b/openpype/tools/tray/pype_info_widget.py @@ -4,9 +4,9 @@ import collections from avalon import style from Qt import QtCore, QtGui, QtWidgets -from pype.api import resources -from pype.settings.lib import get_local_settings -from pype.lib.pype_info import ( +from openpype.api import resources +from openpype.settings.lib import get_local_settings +from openpype.lib.pype_info import ( get_all_current_info, get_pype_info, get_workstation_info, @@ -204,11 +204,11 @@ class PypeInfoWidget(QtWidgets.QWidget): icon = QtGui.QIcon(resources.pype_icon_filepath()) self.setWindowIcon(icon) - self.setWindowTitle("Pype info") + self.setWindowTitle("OpenPype info") main_layout = QtWidgets.QVBoxLayout(self) main_layout.setAlignment(QtCore.Qt.AlignTop) - main_layout.addWidget(self._create_pype_info_widget(), 0) + main_layout.addWidget(self._create_openpype_info_widget(), 0) main_layout.addWidget(self._create_separator(), 0) main_layout.addWidget(self._create_workstation_widget(), 0) main_layout.addWidget(self._create_separator(), 0) @@ -347,8 +347,8 @@ class PypeInfoWidget(QtWidgets.QWidget): return env_widget - def _create_pype_info_widget(self): - """Create widget with information about pype application.""" + def _create_openpype_info_widget(self): + """Create widget with information about OpenPype application.""" # Get pype info data pype_info = get_pype_info() @@ -360,10 +360,10 @@ class PypeInfoWidget(QtWidgets.QWidget): pype_info["version_value"] = version_value # Prepare lable mapping key_label_mapping = { - "version_value": "Pype version:", - "executable": "Pype executable:", - "pype_root": "Pype location:", - "mongo_url": "Pype Mongo URL:" + "version_value": "OpenPype version:", + "executable": "OpenPype executable:", + "pype_root": "OpenPype location:", + "mongo_url": "OpenPype Mongo URL:" } # Prepare keys order keys_order = ["version_value", "executable", "pype_root", "mongo_url"] diff --git a/pype/tools/tray/pype_tray.py b/openpype/tools/tray/pype_tray.py similarity index 94% rename from pype/tools/tray/pype_tray.py rename to openpype/tools/tray/pype_tray.py index 2d37c04136..534c99bd90 100644 --- a/pype/tools/tray/pype_tray.py +++ b/openpype/tools/tray/pype_tray.py @@ -4,10 +4,10 @@ import sys import platform from avalon import style from Qt import QtCore, QtGui, QtWidgets -from pype.api import Logger, resources -from pype.modules import TrayModulesManager, ITrayService -from pype.settings.lib import get_system_settings -import pype.version +from openpype.api import Logger, resources +from openpype.modules import TrayModulesManager, ITrayService +from openpype.settings.lib import get_system_settings +import openpype.version from .pype_info_widget import PypeInfoWidget @@ -77,10 +77,10 @@ class TrayManager: self.tray_widget.showMessage(*args, **kwargs) def _add_version_item(self): - subversion = os.environ.get("PYPE_SUBVERSION") - client_name = os.environ.get("PYPE_CLIENT") + subversion = os.environ.get("OPENPYPE_SUBVERSION") + client_name = os.environ.get("OPENPYPE_CLIENT") - version_string = pype.version.__version__ + version_string = openpype.version.__version__ if subversion: version_string += " ({})".format(subversion) diff --git a/pype/tools/workfiles/README.md b/openpype/tools/workfiles/README.md similarity index 100% rename from pype/tools/workfiles/README.md rename to openpype/tools/workfiles/README.md diff --git a/pype/tools/workfiles/__init__.py b/openpype/tools/workfiles/__init__.py similarity index 100% rename from pype/tools/workfiles/__init__.py rename to openpype/tools/workfiles/__init__.py diff --git a/pype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py similarity index 99% rename from pype/tools/workfiles/app.py rename to openpype/tools/workfiles/app.py index d058841462..c08f06066e 100644 --- a/pype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -18,7 +18,7 @@ from avalon.tools.delegates import PrettyTimeDelegate from .model import FilesModel from .view import FilesView -from pype.lib import ( +from openpype.lib import ( Anatomy, get_workdir, get_workfile_doc, diff --git a/pype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py similarity index 100% rename from pype/tools/workfiles/model.py rename to openpype/tools/workfiles/model.py diff --git a/pype/tools/workfiles/view.py b/openpype/tools/workfiles/view.py similarity index 100% rename from pype/tools/workfiles/view.py rename to openpype/tools/workfiles/view.py diff --git a/pype/vendor/OpenHarmony/.gitattributes b/openpype/vendor/OpenHarmony/.gitattributes similarity index 100% rename from pype/vendor/OpenHarmony/.gitattributes rename to openpype/vendor/OpenHarmony/.gitattributes diff --git a/pype/vendor/OpenHarmony/.gitignore b/openpype/vendor/OpenHarmony/.gitignore similarity index 100% rename from pype/vendor/OpenHarmony/.gitignore rename to openpype/vendor/OpenHarmony/.gitignore diff --git a/pype/vendor/OpenHarmony/Install.bat b/openpype/vendor/OpenHarmony/Install.bat similarity index 100% rename from pype/vendor/OpenHarmony/Install.bat rename to openpype/vendor/OpenHarmony/Install.bat diff --git a/pype/vendor/OpenHarmony/LICENSE b/openpype/vendor/OpenHarmony/LICENSE similarity index 100% rename from pype/vendor/OpenHarmony/LICENSE rename to openpype/vendor/OpenHarmony/LICENSE diff --git a/pype/vendor/OpenHarmony/README.md b/openpype/vendor/OpenHarmony/README.md similarity index 100% rename from pype/vendor/OpenHarmony/README.md rename to openpype/vendor/OpenHarmony/README.md diff --git a/pype/vendor/OpenHarmony/build_doc.bat b/openpype/vendor/OpenHarmony/build_doc.bat similarity index 100% rename from pype/vendor/OpenHarmony/build_doc.bat rename to openpype/vendor/OpenHarmony/build_doc.bat diff --git a/pype/vendor/OpenHarmony/docs/$.html b/openpype/vendor/OpenHarmony/docs/$.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.html rename to openpype/vendor/OpenHarmony/docs/$.html diff --git a/pype/vendor/OpenHarmony/docs/$.oApp.html b/openpype/vendor/OpenHarmony/docs/$.oApp.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oApp.html rename to openpype/vendor/OpenHarmony/docs/$.oApp.html diff --git a/pype/vendor/OpenHarmony/docs/$.oArtLayer.html b/openpype/vendor/OpenHarmony/docs/$.oArtLayer.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oArtLayer.html rename to openpype/vendor/OpenHarmony/docs/$.oArtLayer.html diff --git a/pype/vendor/OpenHarmony/docs/$.oAttribute.html b/openpype/vendor/OpenHarmony/docs/$.oAttribute.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oAttribute.html rename to openpype/vendor/OpenHarmony/docs/$.oAttribute.html diff --git a/pype/vendor/OpenHarmony/docs/$.oBackdrop.html b/openpype/vendor/OpenHarmony/docs/$.oBackdrop.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oBackdrop.html rename to openpype/vendor/OpenHarmony/docs/$.oBackdrop.html diff --git a/pype/vendor/OpenHarmony/docs/$.oBox.html b/openpype/vendor/OpenHarmony/docs/$.oBox.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oBox.html rename to openpype/vendor/OpenHarmony/docs/$.oBox.html diff --git a/pype/vendor/OpenHarmony/docs/$.oColor.html b/openpype/vendor/OpenHarmony/docs/$.oColor.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oColor.html rename to openpype/vendor/OpenHarmony/docs/$.oColor.html diff --git a/pype/vendor/OpenHarmony/docs/$.oColorValue.html b/openpype/vendor/OpenHarmony/docs/$.oColorValue.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oColorValue.html rename to openpype/vendor/OpenHarmony/docs/$.oColorValue.html diff --git a/pype/vendor/OpenHarmony/docs/$.oColumn.html b/openpype/vendor/OpenHarmony/docs/$.oColumn.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oColumn.html rename to openpype/vendor/OpenHarmony/docs/$.oColumn.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDatabase.html b/openpype/vendor/OpenHarmony/docs/$.oDatabase.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDatabase.html rename to openpype/vendor/OpenHarmony/docs/$.oDatabase.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDialog.Progress.html b/openpype/vendor/OpenHarmony/docs/$.oDialog.Progress.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDialog.Progress.html rename to openpype/vendor/OpenHarmony/docs/$.oDialog.Progress.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDialog.html b/openpype/vendor/OpenHarmony/docs/$.oDialog.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDialog.html rename to openpype/vendor/OpenHarmony/docs/$.oDialog.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDrawing.html b/openpype/vendor/OpenHarmony/docs/$.oDrawing.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDrawing.html rename to openpype/vendor/OpenHarmony/docs/$.oDrawing.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDrawingColumn.html b/openpype/vendor/OpenHarmony/docs/$.oDrawingColumn.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDrawingColumn.html rename to openpype/vendor/OpenHarmony/docs/$.oDrawingColumn.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDrawingNode.html b/openpype/vendor/OpenHarmony/docs/$.oDrawingNode.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDrawingNode.html rename to openpype/vendor/OpenHarmony/docs/$.oDrawingNode.html diff --git a/pype/vendor/OpenHarmony/docs/$.oDynList.html b/openpype/vendor/OpenHarmony/docs/$.oDynList.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oDynList.html rename to openpype/vendor/OpenHarmony/docs/$.oDynList.html diff --git a/pype/vendor/OpenHarmony/docs/$.oElement.html b/openpype/vendor/OpenHarmony/docs/$.oElement.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oElement.html rename to openpype/vendor/OpenHarmony/docs/$.oElement.html diff --git a/pype/vendor/OpenHarmony/docs/$.oFile.html b/openpype/vendor/OpenHarmony/docs/$.oFile.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oFile.html rename to openpype/vendor/OpenHarmony/docs/$.oFile.html diff --git a/pype/vendor/OpenHarmony/docs/$.oFolder.html b/openpype/vendor/OpenHarmony/docs/$.oFolder.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oFolder.html rename to openpype/vendor/OpenHarmony/docs/$.oFolder.html diff --git a/pype/vendor/OpenHarmony/docs/$.oFrame.html b/openpype/vendor/OpenHarmony/docs/$.oFrame.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oFrame.html rename to openpype/vendor/OpenHarmony/docs/$.oFrame.html diff --git a/pype/vendor/OpenHarmony/docs/$.oGroupNode.html b/openpype/vendor/OpenHarmony/docs/$.oGroupNode.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oGroupNode.html rename to openpype/vendor/OpenHarmony/docs/$.oGroupNode.html diff --git a/pype/vendor/OpenHarmony/docs/$.oLink.html b/openpype/vendor/OpenHarmony/docs/$.oLink.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oLink.html rename to openpype/vendor/OpenHarmony/docs/$.oLink.html diff --git a/pype/vendor/OpenHarmony/docs/$.oLinkPath.html b/openpype/vendor/OpenHarmony/docs/$.oLinkPath.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oLinkPath.html rename to openpype/vendor/OpenHarmony/docs/$.oLinkPath.html diff --git a/pype/vendor/OpenHarmony/docs/$.oList.html b/openpype/vendor/OpenHarmony/docs/$.oList.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oList.html rename to openpype/vendor/OpenHarmony/docs/$.oList.html diff --git a/pype/vendor/OpenHarmony/docs/$.oMetadata.html b/openpype/vendor/OpenHarmony/docs/$.oMetadata.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oMetadata.html rename to openpype/vendor/OpenHarmony/docs/$.oMetadata.html diff --git a/pype/vendor/OpenHarmony/docs/$.oNetwork.html b/openpype/vendor/OpenHarmony/docs/$.oNetwork.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oNetwork.html rename to openpype/vendor/OpenHarmony/docs/$.oNetwork.html diff --git a/pype/vendor/OpenHarmony/docs/$.oNode.html b/openpype/vendor/OpenHarmony/docs/$.oNode.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oNode.html rename to openpype/vendor/OpenHarmony/docs/$.oNode.html diff --git a/pype/vendor/OpenHarmony/docs/$.oNodeLink.html b/openpype/vendor/OpenHarmony/docs/$.oNodeLink.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oNodeLink.html rename to openpype/vendor/OpenHarmony/docs/$.oNodeLink.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPalette.html b/openpype/vendor/OpenHarmony/docs/$.oPalette.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPalette.html rename to openpype/vendor/OpenHarmony/docs/$.oPalette.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPathPoint.html b/openpype/vendor/OpenHarmony/docs/$.oPathPoint.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPathPoint.html rename to openpype/vendor/OpenHarmony/docs/$.oPathPoint.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPegNode.html b/openpype/vendor/OpenHarmony/docs/$.oPegNode.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPegNode.html rename to openpype/vendor/OpenHarmony/docs/$.oPegNode.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPieMenu.html b/openpype/vendor/OpenHarmony/docs/$.oPieMenu.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPieMenu.html rename to openpype/vendor/OpenHarmony/docs/$.oPieMenu.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPieSubMenu.html b/openpype/vendor/OpenHarmony/docs/$.oPieSubMenu.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPieSubMenu.html rename to openpype/vendor/OpenHarmony/docs/$.oPieSubMenu.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPoint.html b/openpype/vendor/OpenHarmony/docs/$.oPoint.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPoint.html rename to openpype/vendor/OpenHarmony/docs/$.oPoint.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPreference.html b/openpype/vendor/OpenHarmony/docs/$.oPreference.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPreference.html rename to openpype/vendor/OpenHarmony/docs/$.oPreference.html diff --git a/pype/vendor/OpenHarmony/docs/$.oPreferences.html b/openpype/vendor/OpenHarmony/docs/$.oPreferences.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oPreferences.html rename to openpype/vendor/OpenHarmony/docs/$.oPreferences.html diff --git a/pype/vendor/OpenHarmony/docs/$.oProcess.html b/openpype/vendor/OpenHarmony/docs/$.oProcess.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oProcess.html rename to openpype/vendor/OpenHarmony/docs/$.oProcess.html diff --git a/pype/vendor/OpenHarmony/docs/$.oProgressDialog.html b/openpype/vendor/OpenHarmony/docs/$.oProgressDialog.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oProgressDialog.html rename to openpype/vendor/OpenHarmony/docs/$.oProgressDialog.html diff --git a/pype/vendor/OpenHarmony/docs/$.oScene.html b/openpype/vendor/OpenHarmony/docs/$.oScene.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oScene.html rename to openpype/vendor/OpenHarmony/docs/$.oScene.html diff --git a/pype/vendor/OpenHarmony/docs/$.oScriptButton.html b/openpype/vendor/OpenHarmony/docs/$.oScriptButton.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oScriptButton.html rename to openpype/vendor/OpenHarmony/docs/$.oScriptButton.html diff --git a/pype/vendor/OpenHarmony/docs/$.oShape.html b/openpype/vendor/OpenHarmony/docs/$.oShape.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oShape.html rename to openpype/vendor/OpenHarmony/docs/$.oShape.html diff --git a/pype/vendor/OpenHarmony/docs/$.oStencil.html b/openpype/vendor/OpenHarmony/docs/$.oStencil.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oStencil.html rename to openpype/vendor/OpenHarmony/docs/$.oStencil.html diff --git a/pype/vendor/OpenHarmony/docs/$.oThread.html b/openpype/vendor/OpenHarmony/docs/$.oThread.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oThread.html rename to openpype/vendor/OpenHarmony/docs/$.oThread.html diff --git a/pype/vendor/OpenHarmony/docs/$.oTimeline.html b/openpype/vendor/OpenHarmony/docs/$.oTimeline.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oTimeline.html rename to openpype/vendor/OpenHarmony/docs/$.oTimeline.html diff --git a/pype/vendor/OpenHarmony/docs/$.oTimelineLayer.html b/openpype/vendor/OpenHarmony/docs/$.oTimelineLayer.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oTimelineLayer.html rename to openpype/vendor/OpenHarmony/docs/$.oTimelineLayer.html diff --git a/pype/vendor/OpenHarmony/docs/$.oTool.html b/openpype/vendor/OpenHarmony/docs/$.oTool.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oTool.html rename to openpype/vendor/OpenHarmony/docs/$.oTool.html diff --git a/pype/vendor/OpenHarmony/docs/$.oToolbar.html b/openpype/vendor/OpenHarmony/docs/$.oToolbar.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oToolbar.html rename to openpype/vendor/OpenHarmony/docs/$.oToolbar.html diff --git a/pype/vendor/OpenHarmony/docs/$.oUtils.html b/openpype/vendor/OpenHarmony/docs/$.oUtils.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oUtils.html rename to openpype/vendor/OpenHarmony/docs/$.oUtils.html diff --git a/pype/vendor/OpenHarmony/docs/$.oXml.html b/openpype/vendor/OpenHarmony/docs/$.oXml.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/$.oXml.html rename to openpype/vendor/OpenHarmony/docs/$.oXml.html diff --git a/pype/vendor/OpenHarmony/docs/404.md b/openpype/vendor/OpenHarmony/docs/404.md similarity index 100% rename from pype/vendor/OpenHarmony/docs/404.md rename to openpype/vendor/OpenHarmony/docs/404.md diff --git a/pype/vendor/OpenHarmony/docs/NodeTypes.html b/openpype/vendor/OpenHarmony/docs/NodeTypes.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/NodeTypes.html rename to openpype/vendor/OpenHarmony/docs/NodeTypes.html diff --git a/pype/vendor/OpenHarmony/docs/actions.html b/openpype/vendor/OpenHarmony/docs/actions.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/actions.html rename to openpype/vendor/OpenHarmony/docs/actions.html diff --git a/pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.eot b/openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.eot rename to openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.eot diff --git a/pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.svg b/openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.svg rename to openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.svg diff --git a/pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.ttf b/openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.ttf rename to openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.ttf diff --git a/pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.woff b/openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from pype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.woff rename to openpype/vendor/OpenHarmony/docs/fonts/glyphicons-halflings-regular.woff diff --git a/pype/vendor/OpenHarmony/docs/global.html b/openpype/vendor/OpenHarmony/docs/global.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/global.html rename to openpype/vendor/OpenHarmony/docs/global.html diff --git a/pype/vendor/OpenHarmony/docs/index.html b/openpype/vendor/OpenHarmony/docs/index.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/index.html rename to openpype/vendor/OpenHarmony/docs/index.html diff --git a/pype/vendor/OpenHarmony/docs/preferences.html b/openpype/vendor/OpenHarmony/docs/preferences.html similarity index 100% rename from pype/vendor/OpenHarmony/docs/preferences.html rename to openpype/vendor/OpenHarmony/docs/preferences.html diff --git a/pype/vendor/OpenHarmony/docs/scripts/bootstrap.min.js b/openpype/vendor/OpenHarmony/docs/scripts/bootstrap.min.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/bootstrap.min.js rename to openpype/vendor/OpenHarmony/docs/scripts/bootstrap.min.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/jaguar.js b/openpype/vendor/OpenHarmony/docs/scripts/jaguar.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/jaguar.js rename to openpype/vendor/OpenHarmony/docs/scripts/jaguar.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/jquery.min.js b/openpype/vendor/OpenHarmony/docs/scripts/jquery.min.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/jquery.min.js rename to openpype/vendor/OpenHarmony/docs/scripts/jquery.min.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/jquery.min.map b/openpype/vendor/OpenHarmony/docs/scripts/jquery.min.map similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/jquery.min.map rename to openpype/vendor/OpenHarmony/docs/scripts/jquery.min.map diff --git a/pype/vendor/OpenHarmony/docs/scripts/prettify/Apache-License-2.0.txt b/openpype/vendor/OpenHarmony/docs/scripts/prettify/Apache-License-2.0.txt similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/prettify/Apache-License-2.0.txt rename to openpype/vendor/OpenHarmony/docs/scripts/prettify/Apache-License-2.0.txt diff --git a/pype/vendor/OpenHarmony/docs/scripts/prettify/lang-css.js b/openpype/vendor/OpenHarmony/docs/scripts/prettify/lang-css.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/prettify/lang-css.js rename to openpype/vendor/OpenHarmony/docs/scripts/prettify/lang-css.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/prettify/prettify.js b/openpype/vendor/OpenHarmony/docs/scripts/prettify/prettify.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/prettify/prettify.js rename to openpype/vendor/OpenHarmony/docs/scripts/prettify/prettify.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/underscore-min.js b/openpype/vendor/OpenHarmony/docs/scripts/underscore-min.js similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/underscore-min.js rename to openpype/vendor/OpenHarmony/docs/scripts/underscore-min.js diff --git a/pype/vendor/OpenHarmony/docs/scripts/underscore-min.map b/openpype/vendor/OpenHarmony/docs/scripts/underscore-min.map similarity index 100% rename from pype/vendor/OpenHarmony/docs/scripts/underscore-min.map rename to openpype/vendor/OpenHarmony/docs/scripts/underscore-min.map diff --git a/pype/vendor/OpenHarmony/docs/styles/bootstrap.min.css b/openpype/vendor/OpenHarmony/docs/styles/bootstrap.min.css similarity index 100% rename from pype/vendor/OpenHarmony/docs/styles/bootstrap.min.css rename to openpype/vendor/OpenHarmony/docs/styles/bootstrap.min.css diff --git a/pype/vendor/OpenHarmony/docs/styles/jaguar.css b/openpype/vendor/OpenHarmony/docs/styles/jaguar.css similarity index 100% rename from pype/vendor/OpenHarmony/docs/styles/jaguar.css rename to openpype/vendor/OpenHarmony/docs/styles/jaguar.css diff --git a/pype/vendor/OpenHarmony/docs/styles/prettify-jsdoc.css b/openpype/vendor/OpenHarmony/docs/styles/prettify-jsdoc.css similarity index 100% rename from pype/vendor/OpenHarmony/docs/styles/prettify-jsdoc.css rename to openpype/vendor/OpenHarmony/docs/styles/prettify-jsdoc.css diff --git a/pype/vendor/OpenHarmony/docs/styles/prettify-tomorrow.css b/openpype/vendor/OpenHarmony/docs/styles/prettify-tomorrow.css similarity index 100% rename from pype/vendor/OpenHarmony/docs/styles/prettify-tomorrow.css rename to openpype/vendor/OpenHarmony/docs/styles/prettify-tomorrow.css diff --git a/pype/vendor/OpenHarmony/documentation.json b/openpype/vendor/OpenHarmony/documentation.json similarity index 100% rename from pype/vendor/OpenHarmony/documentation.json rename to openpype/vendor/OpenHarmony/documentation.json diff --git a/pype/vendor/OpenHarmony/install.sh b/openpype/vendor/OpenHarmony/install.sh similarity index 100% rename from pype/vendor/OpenHarmony/install.sh rename to openpype/vendor/OpenHarmony/install.sh diff --git a/pype/vendor/OpenHarmony/oH_DOM.jpg b/openpype/vendor/OpenHarmony/oH_DOM.jpg similarity index 100% rename from pype/vendor/OpenHarmony/oH_DOM.jpg rename to openpype/vendor/OpenHarmony/oH_DOM.jpg diff --git a/pype/vendor/OpenHarmony/openHarmony.js b/openpype/vendor/OpenHarmony/openHarmony.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony.js rename to openpype/vendor/OpenHarmony/openHarmony.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_actions.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_actions.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_actions.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_actions.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_application.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_application.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_application.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_application.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_attribute.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_backdrop.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_color.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_color.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_color.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_color.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_column.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_column.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_column.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_column.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_database.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_database.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_database.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_database.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_dialog.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_drawing.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_element.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_element.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_element.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_element.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_file.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_file.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_file.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_file.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_frame.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_frame.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_frame.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_frame.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_list.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_list.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_list.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_list.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_math.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_math.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_math.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_math.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_metadata.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_misc.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_misc.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_misc.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_misc.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_network.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_network.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_network.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_network.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_node.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_node.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_node.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_node.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_nodeAttributes.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_nodeAttributes.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_nodeAttributes.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_nodeAttributes.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_nodeLink.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_palette.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_palette.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_palette.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_palette.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_path.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_path.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_path.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_path.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_preferencedoc.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_preferencedoc.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_preferencedoc.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_preferencedoc.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_preferences.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_preferences.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_preferences.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_preferences.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_scene.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_scene.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_scene.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_scene.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_threading.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_threading.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_threading.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_threading.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_timeline.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_timeline.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_timeline.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_timeline.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_tool.js b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_tool.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_tool.js rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_tool.js diff --git a/pype/vendor/OpenHarmony/openHarmony/openHarmony_toolInstall.ui b/openpype/vendor/OpenHarmony/openHarmony/openHarmony_toolInstall.ui similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony/openHarmony_toolInstall.ui rename to openpype/vendor/OpenHarmony/openHarmony/openHarmony_toolInstall.ui diff --git a/pype/vendor/OpenHarmony/openHarmony_install.js b/openpype/vendor/OpenHarmony/openHarmony_install.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony_install.js rename to openpype/vendor/OpenHarmony/openHarmony_install.js diff --git a/pype/vendor/OpenHarmony/openHarmony_tools.js b/openpype/vendor/OpenHarmony/openHarmony_tools.js similarity index 100% rename from pype/vendor/OpenHarmony/openHarmony_tools.js rename to openpype/vendor/OpenHarmony/openHarmony_tools.js diff --git a/pype/vendor/OpenHarmony/package.json b/openpype/vendor/OpenHarmony/package.json similarity index 100% rename from pype/vendor/OpenHarmony/package.json rename to openpype/vendor/OpenHarmony/package.json diff --git a/pype/vendor/OpenHarmony/reference/Reference_view_currentToolManager().txt b/openpype/vendor/OpenHarmony/reference/Reference_view_currentToolManager().txt similarity index 100% rename from pype/vendor/OpenHarmony/reference/Reference_view_currentToolManager().txt rename to openpype/vendor/OpenHarmony/reference/Reference_view_currentToolManager().txt diff --git a/pype/vendor/OpenHarmony/tbpackage.json b/openpype/vendor/OpenHarmony/tbpackage.json similarity index 100% rename from pype/vendor/OpenHarmony/tbpackage.json rename to openpype/vendor/OpenHarmony/tbpackage.json diff --git a/pype/vendor/OpenHarmony/tools/OpenHarmony_basic/INSTALL b/openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/INSTALL similarity index 100% rename from pype/vendor/OpenHarmony/tools/OpenHarmony_basic/INSTALL rename to openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/INSTALL diff --git a/pype/vendor/OpenHarmony/tools/OpenHarmony_basic/README b/openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/README similarity index 100% rename from pype/vendor/OpenHarmony/tools/OpenHarmony_basic/README rename to openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/README diff --git a/pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_anim_tools.js b/openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_anim_tools.js similarity index 100% rename from pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_anim_tools.js rename to openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_anim_tools.js diff --git a/pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_basic_backdropPicker.ui b/openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_basic_backdropPicker.ui similarity index 100% rename from pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_basic_backdropPicker.ui rename to openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_basic_backdropPicker.ui diff --git a/pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_rigging_tools.js b/openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_rigging_tools.js similarity index 100% rename from pype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_rigging_tools.js rename to openpype/vendor/OpenHarmony/tools/OpenHarmony_basic/openHarmony_rigging_tools.js diff --git a/pype/vendor/__init__.py b/openpype/vendor/__init__.py similarity index 100% rename from pype/vendor/__init__.py rename to openpype/vendor/__init__.py diff --git a/pype/vendor/python/common/README.md b/openpype/vendor/python/common/README.md similarity index 100% rename from pype/vendor/python/common/README.md rename to openpype/vendor/python/common/README.md diff --git a/pype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py similarity index 100% rename from pype/vendor/python/common/capture.py rename to openpype/vendor/python/common/capture.py diff --git a/pype/vendor/python/common/capture_gui/__init__.py b/openpype/vendor/python/common/capture_gui/__init__.py similarity index 100% rename from pype/vendor/python/common/capture_gui/__init__.py rename to openpype/vendor/python/common/capture_gui/__init__.py diff --git a/pype/vendor/python/common/capture_gui/accordion.py b/openpype/vendor/python/common/capture_gui/accordion.py similarity index 100% rename from pype/vendor/python/common/capture_gui/accordion.py rename to openpype/vendor/python/common/capture_gui/accordion.py diff --git a/pype/vendor/python/common/capture_gui/app.py b/openpype/vendor/python/common/capture_gui/app.py similarity index 100% rename from pype/vendor/python/common/capture_gui/app.py rename to openpype/vendor/python/common/capture_gui/app.py diff --git a/pype/vendor/python/common/capture_gui/colorpicker.py b/openpype/vendor/python/common/capture_gui/colorpicker.py similarity index 100% rename from pype/vendor/python/common/capture_gui/colorpicker.py rename to openpype/vendor/python/common/capture_gui/colorpicker.py diff --git a/pype/vendor/python/common/capture_gui/lib.py b/openpype/vendor/python/common/capture_gui/lib.py similarity index 100% rename from pype/vendor/python/common/capture_gui/lib.py rename to openpype/vendor/python/common/capture_gui/lib.py diff --git a/pype/vendor/python/common/capture_gui/plugin.py b/openpype/vendor/python/common/capture_gui/plugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugin.py rename to openpype/vendor/python/common/capture_gui/plugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/cameraplugin.py b/openpype/vendor/python/common/capture_gui/plugins/cameraplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/cameraplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/cameraplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/codecplugin.py b/openpype/vendor/python/common/capture_gui/plugins/codecplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/codecplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/codecplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/defaultoptionsplugin.py b/openpype/vendor/python/common/capture_gui/plugins/defaultoptionsplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/defaultoptionsplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/defaultoptionsplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/displayplugin.py b/openpype/vendor/python/common/capture_gui/plugins/displayplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/displayplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/displayplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/genericplugin.py b/openpype/vendor/python/common/capture_gui/plugins/genericplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/genericplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/genericplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/ioplugin.py b/openpype/vendor/python/common/capture_gui/plugins/ioplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/ioplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/ioplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/panzoomplugin.py b/openpype/vendor/python/common/capture_gui/plugins/panzoomplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/panzoomplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/panzoomplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/rendererplugin.py b/openpype/vendor/python/common/capture_gui/plugins/rendererplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/rendererplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/rendererplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/resolutionplugin.py b/openpype/vendor/python/common/capture_gui/plugins/resolutionplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/resolutionplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/resolutionplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/timeplugin.py b/openpype/vendor/python/common/capture_gui/plugins/timeplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/timeplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/timeplugin.py diff --git a/pype/vendor/python/common/capture_gui/plugins/viewportplugin.py b/openpype/vendor/python/common/capture_gui/plugins/viewportplugin.py similarity index 100% rename from pype/vendor/python/common/capture_gui/plugins/viewportplugin.py rename to openpype/vendor/python/common/capture_gui/plugins/viewportplugin.py diff --git a/pype/vendor/python/common/capture_gui/presets.py b/openpype/vendor/python/common/capture_gui/presets.py similarity index 100% rename from pype/vendor/python/common/capture_gui/presets.py rename to openpype/vendor/python/common/capture_gui/presets.py diff --git a/pype/vendor/python/common/capture_gui/resources/config.png b/openpype/vendor/python/common/capture_gui/resources/config.png similarity index 100% rename from pype/vendor/python/common/capture_gui/resources/config.png rename to openpype/vendor/python/common/capture_gui/resources/config.png diff --git a/pype/vendor/python/common/capture_gui/resources/import.png b/openpype/vendor/python/common/capture_gui/resources/import.png similarity index 100% rename from pype/vendor/python/common/capture_gui/resources/import.png rename to openpype/vendor/python/common/capture_gui/resources/import.png diff --git a/pype/vendor/python/common/capture_gui/resources/reset.png b/openpype/vendor/python/common/capture_gui/resources/reset.png similarity index 100% rename from pype/vendor/python/common/capture_gui/resources/reset.png rename to openpype/vendor/python/common/capture_gui/resources/reset.png diff --git a/pype/vendor/python/common/capture_gui/resources/save.png b/openpype/vendor/python/common/capture_gui/resources/save.png similarity index 100% rename from pype/vendor/python/common/capture_gui/resources/save.png rename to openpype/vendor/python/common/capture_gui/resources/save.png diff --git a/pype/vendor/python/common/capture_gui/tokens.py b/openpype/vendor/python/common/capture_gui/tokens.py similarity index 100% rename from pype/vendor/python/common/capture_gui/tokens.py rename to openpype/vendor/python/common/capture_gui/tokens.py diff --git a/pype/vendor/python/common/capture_gui/vendor/Qt.py b/openpype/vendor/python/common/capture_gui/vendor/Qt.py similarity index 100% rename from pype/vendor/python/common/capture_gui/vendor/Qt.py rename to openpype/vendor/python/common/capture_gui/vendor/Qt.py diff --git a/pype/vendor/python/common/capture_gui/vendor/__init__.py b/openpype/vendor/python/common/capture_gui/vendor/__init__.py similarity index 100% rename from pype/vendor/python/common/capture_gui/vendor/__init__.py rename to openpype/vendor/python/common/capture_gui/vendor/__init__.py diff --git a/pype/vendor/python/common/capture_gui/version.py b/openpype/vendor/python/common/capture_gui/version.py similarity index 100% rename from pype/vendor/python/common/capture_gui/version.py rename to openpype/vendor/python/common/capture_gui/version.py diff --git a/pype/vendor/python/common/pysync.py b/openpype/vendor/python/common/pysync.py similarity index 100% rename from pype/vendor/python/common/pysync.py rename to openpype/vendor/python/common/pysync.py diff --git a/pype/vendor/python/python_2/README.md b/openpype/vendor/python/python_2/README.md similarity index 100% rename from pype/vendor/python/python_2/README.md rename to openpype/vendor/python/python_2/README.md diff --git a/openpype/vendor/python/python_2/opentimelineio/__init__.py b/openpype/vendor/python/python_2/opentimelineio/__init__.py new file mode 100644 index 0000000000..a8b0a636ad --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/__init__.py @@ -0,0 +1,51 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""An editorial interchange format and library. + +see: http://opentimeline.io + +.. moduleauthor:: Pixar Animation Studios +""" + +# flake8: noqa + +# in dependency hierarchy +from . import ( + opentime, + exceptions, + core, + schema, + schemadef, + plugins, + media_linker, + adapters, + hooks, + algorithms, +) + +__version__ = "0.11.0" +__author__ = "Pixar Animation Studios" +__author_email__ = "opentimelineio@pixar.com" +__license__ = "Modified Apache 2.0 License" diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/__init__.py b/openpype/vendor/python/python_2/opentimelineio/adapters/__init__.py new file mode 100644 index 0000000000..afbe3f8e8a --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/__init__.py @@ -0,0 +1,213 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Expose the adapter interface to developers. + +To read from an existing representation, use the read_from_string and +read_from_file functions. To query the list of adapters, use the +available_adapter_names function. + +The otio_json adapter is provided as a the canonical, lossless, serialization +of the in-memory otio schema. Other adapters are to varying degrees lossy. +For more information, consult the documentation in the individual adapter +modules. +""" + +import os +import itertools + +from .. import ( + exceptions, + plugins, + media_linker +) + +from .adapter import Adapter # noqa + +# OTIO Json adapter is always available +from . import otio_json # noqa + + +def suffixes_with_defined_adapters(read=False, write=False): + """Return a set of all the suffixes that have adapters defined for them.""" + + if not read and not write: + read = True + write = True + + positive_adapters = [] + for adp in plugins.ActiveManifest().adapters: + if read and adp.has_feature("read"): + positive_adapters.append(adp) + continue + + if write and adp.has_feature("write"): + positive_adapters.append(adp) + + return set( + itertools.chain.from_iterable( + adp.suffixes for adp in positive_adapters + ) + ) + + +def available_adapter_names(): + """Return a string list of the available adapters.""" + + return [str(adp.name) for adp in plugins.ActiveManifest().adapters] + + +def _from_filepath_or_name(filepath, adapter_name): + if adapter_name is not None: + return plugins.ActiveManifest().from_name(adapter_name) + else: + return from_filepath(filepath) + + +def from_filepath(filepath): + """Guess the adapter object to use for a given filepath. + + example: + "foo.otio" returns the "otio_json" adapter. + """ + + outext = os.path.splitext(filepath)[1][1:] + + try: + return plugins.ActiveManifest().from_filepath(outext) + except exceptions.NoKnownAdapterForExtensionError: + raise exceptions.NoKnownAdapterForExtensionError( + "No adapter for suffix '{}' on file '{}'".format( + outext, + filepath + ) + ) + + +def from_name(name): + """Fetch the adapter object by the name of the adapter directly.""" + + try: + return plugins.ActiveManifest().from_name(name) + except exceptions.NotSupportedError: + raise exceptions.NotSupportedError( + "adapter not supported: {}, available: {}".format( + name, + available_adapter_names() + ) + ) + + +def read_from_file( + filepath, + adapter_name=None, + media_linker_name=media_linker.MediaLinkingPolicy.ForceDefaultLinker, + media_linker_argument_map=None, + **adapter_argument_map +): + """Read filepath using adapter_name. + + If adapter_name is None, try and infer the adapter name from the filepath. + + For example: + timeline = read_from_file("example_trailer.otio") + timeline = read_from_file("file_with_no_extension", "cmx_3600") + """ + + adapter = _from_filepath_or_name(filepath, adapter_name) + + return adapter.read_from_file( + filepath=filepath, + media_linker_name=media_linker_name, + media_linker_argument_map=media_linker_argument_map, + **adapter_argument_map + ) + + +def read_from_string( + input_str, + adapter_name='otio_json', + media_linker_name=media_linker.MediaLinkingPolicy.ForceDefaultLinker, + media_linker_argument_map=None, + **adapter_argument_map +): + """Read a timeline from input_str using adapter_name. + + This is useful if you obtain a timeline from someplace other than the + filesystem. + + Example: + raw_text = urlopen(my_url).read() + timeline = read_from_string(raw_text, "otio_json") + """ + + adapter = plugins.ActiveManifest().from_name(adapter_name) + return adapter.read_from_string( + input_str=input_str, + media_linker_name=media_linker_name, + media_linker_argument_map=media_linker_argument_map, + **adapter_argument_map + ) + + +def write_to_file( + input_otio, + filepath, + adapter_name=None, + **adapter_argument_map +): + """Write input_otio to filepath using adapter_name. + + If adapter_name is None, infer the adapter_name to use based on the + filepath. + + Example: + otio.adapters.write_to_file(my_timeline, "output.otio") + """ + + adapter = _from_filepath_or_name(filepath, adapter_name) + + return adapter.write_to_file( + input_otio=input_otio, + filepath=filepath, + **adapter_argument_map + ) + + +def write_to_string( + input_otio, + adapter_name='otio_json', + **adapter_argument_map +): + """Return input_otio written to a string using adapter_name. + + Example: + raw_text = otio.adapters.write_to_string(my_timeline, "otio_json") + """ + + adapter = plugins.ActiveManifest().from_name(adapter_name) + return adapter.write_to_string( + input_otio=input_otio, + **adapter_argument_map + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/adapter.py b/openpype/vendor/python/python_2/opentimelineio/adapters/adapter.py new file mode 100644 index 0000000000..82ac405065 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/adapter.py @@ -0,0 +1,317 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of the OTIO internal `Adapter` system. + +For information on writing adapters, please consult: + https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html# # noqa +""" + +from .. import ( + core, + plugins, + media_linker, + hooks, +) + + +@core.register_type +class Adapter(plugins.PythonPlugin): + """Adapters convert between OTIO and other formats. + + Note that this class is not subclassed by adapters. Rather, an adapter is + a python module that implements at least one of the following functions: + + write_to_string(input_otio) + write_to_file(input_otio, filepath) (optionally inferred) + read_from_string(input_str) + read_from_file(filepath) (optionally inferred) + + ...as well as a small json file that advertises the features of the adapter + to OTIO. This class serves as the wrapper around these modules internal + to OTIO. You should not need to extend this class to create new adapters + for OTIO. + + For more information: + https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html# # noqa + """ + _serializable_label = "Adapter.1" + + def __init__( + self, + name=None, + execution_scope=None, + filepath=None, + suffixes=None + ): + plugins.PythonPlugin.__init__( + self, + name, + execution_scope, + filepath + ) + + self.suffixes = suffixes or [] + + suffixes = core.serializable_field( + "suffixes", + type([]), + doc="File suffixes associated with this adapter." + ) + + def has_feature(self, feature_string): + """ + return true if adapter supports feature_string, which must be a key + of the _FEATURE_MAP dictionary. + + Will trigger a call to self.module(), which imports the plugin. + """ + + if feature_string.lower() not in _FEATURE_MAP: + return False + + search_strs = _FEATURE_MAP[feature_string] + + try: + return any(hasattr(self.module(), s) for s in search_strs) + except ImportError: + # @TODO: should issue a warning that the plugin was not importable? + return False + + def read_from_file( + self, + filepath, + media_linker_name=media_linker.MediaLinkingPolicy.ForceDefaultLinker, + media_linker_argument_map=None, + hook_function_argument_map={}, + **adapter_argument_map + ): + """Execute the read_from_file function on this adapter. + + If read_from_string exists, but not read_from_file, execute that with + a trivial file object wrapper. + """ + + if media_linker_argument_map is None: + media_linker_argument_map = {} + + result = None + + if ( + not self.has_feature("read_from_file") and + self.has_feature("read_from_string") + ): + with open(filepath, 'r') as fo: + contents = fo.read() + result = self._execute_function( + "read_from_string", + input_str=contents, + **adapter_argument_map + ) + else: + result = self._execute_function( + "read_from_file", + filepath=filepath, + **adapter_argument_map + ) + + hook_function_argument_map['adapter_arguments'] = adapter_argument_map + hook_function_argument_map['media_linker_argument_map'] = \ + media_linker_argument_map + result = hooks.run("post_adapter_read", result, + extra_args=hook_function_argument_map) + + if media_linker_name and ( + media_linker_name != media_linker.MediaLinkingPolicy.DoNotLinkMedia + ): + _with_linked_media_references( + result, + media_linker_name, + media_linker_argument_map + ) + + result = hooks.run("post_media_linker", result, + extra_args=media_linker_argument_map) + + return result + + def write_to_file( + self, + input_otio, + filepath, + hook_function_argument_map={}, + **adapter_argument_map + ): + """Execute the write_to_file function on this adapter. + + If write_to_string exists, but not write_to_file, execute that with + a trivial file object wrapper. + """ + hook_function_argument_map['adapter_arguments'] = adapter_argument_map + input_otio = hooks.run("pre_adapter_write", input_otio, + extra_args=hook_function_argument_map) + + if ( + not self.has_feature("write_to_file") and + self.has_feature("write_to_string") + ): + result = self.write_to_string(input_otio, **adapter_argument_map) + with open(filepath, 'w') as fo: + fo.write(result) + return filepath + + return self._execute_function( + "write_to_file", + input_otio=input_otio, + filepath=filepath, + **adapter_argument_map + ) + + def read_from_string( + self, + input_str, + media_linker_name=media_linker.MediaLinkingPolicy.ForceDefaultLinker, + media_linker_argument_map=None, + hook_function_argument_map={}, + **adapter_argument_map + ): + """Call the read_from_string function on this adapter.""" + + result = self._execute_function( + "read_from_string", + input_str=input_str, + **adapter_argument_map + ) + hook_function_argument_map['adapter_arguments'] = adapter_argument_map + hook_function_argument_map['media_linker_argument_map'] = \ + media_linker_argument_map + + result = hooks.run("post_adapter_read", result, + extra_args=hook_function_argument_map) + + if media_linker_name and ( + media_linker_name != media_linker.MediaLinkingPolicy.DoNotLinkMedia + ): + _with_linked_media_references( + result, + media_linker_name, + media_linker_argument_map + ) + + # @TODO: Should this run *ONLY* if the media linker ran? + result = hooks.run("post_media_linker", result, + extra_args=hook_function_argument_map) + + return result + + def write_to_string( + self, + input_otio, + hook_function_argument_map={}, + **adapter_argument_map + ): + """Call the write_to_string function on this adapter.""" + + hook_function_argument_map['adapter_arguments'] = adapter_argument_map + input_otio = hooks.run("pre_adapter_write", input_otio, + extra_args=hook_function_argument_map) + + return self._execute_function( + "write_to_string", + input_otio=input_otio, + **adapter_argument_map + ) + + def __str__(self): + return ( + "Adapter(" + "{}, " + "{}, " + "{}, " + "{}" + ")".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath), + repr(self.suffixes), + ) + ) + + def __repr__(self): + return ( + "otio.adapter.Adapter(" + "name={}, " + "execution_scope={}, " + "filepath={}, " + "suffixes={}" + ")".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath), + repr(self.suffixes), + ) + ) + + +def _with_linked_media_references( + read_otio, + media_linker_name, + media_linker_argument_map +): + """Link media references in the read_otio if possible. + + Makes changes in place and returns the read_otio structure back. + """ + + if not read_otio or not media_linker.from_name(media_linker_name): + return read_otio + + # not every object the adapter reads has an "each_clip" method, so this + # skips objects without one. + clpfn = getattr(read_otio, "each_clip", None) + if clpfn is None: + return read_otio + + for cl in read_otio.each_clip(): + new_mr = media_linker.linked_media_reference( + cl, + media_linker_name, + # @TODO: should any context get wired in at this point? + media_linker_argument_map + ) + if new_mr is not None: + cl.media_reference = new_mr + + return read_otio + + +# map of attr to look for vs feature name in the adapter plugin +_FEATURE_MAP = { + 'read_from_file': ['read_from_file'], + 'read_from_string': ['read_from_string'], + 'read': ['read_from_file', 'read_from_string'], + 'write_to_file': ['write_to_file'], + 'write_to_string': ['write_to_string'], + 'write': ['write_to_file', 'write_to_string'] +} diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/builtin_adapters.plugin_manifest.json b/openpype/vendor/python/python_2/opentimelineio/adapters/builtin_adapters.plugin_manifest.json new file mode 100644 index 0000000000..5e394a67d8 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/builtin_adapters.plugin_manifest.json @@ -0,0 +1,31 @@ +{ + "OTIO_SCHEMA" : "PluginManifest.1", + "adapters": [ + { + "OTIO_SCHEMA": "Adapter.1", + "name": "fcp_xml", + "execution_scope": "in process", + "filepath": "fcp_xml.py", + "suffixes": ["xml"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "otio_json", + "execution_scope" : "in process", + "filepath" : "otio_json.py", + "suffixes" : ["otio"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "cmx_3600", + "execution_scope" : "in process", + "filepath" : "cmx_3600.py", + "suffixes" : ["edl"] + } + ], + "hooks": { + "post_adapter_read" : [], + "post_media_linker" : [], + "pre_adapter_write" : [] + } +} diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/cmx_3600.py b/openpype/vendor/python/python_2/opentimelineio/adapters/cmx_3600.py new file mode 100644 index 0000000000..f3275e3929 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/cmx_3600.py @@ -0,0 +1,1306 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO CMX 3600 EDL Adapter""" + +# Note: this adapter is not an ideal model for new adapters, but it works. +# If you want to write your own adapter, please see: +# https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html# + +# TODO: Flesh out Attribute Handler +# TODO: Add line numbers to errors and warnings +# TODO: currently tracks with linked audio/video will lose their linkage when +# read into OTIO. + +import os +import re +import math +import collections + +from .. import ( + exceptions, + schema, + opentime, +) + + +class EDLParseError(exceptions.OTIOError): + pass + + +# regex for parsing the playback speed of an M2 event +SPEED_EFFECT_RE = re.compile( + r"(?P.*?)\s*(?P[0-9\.]*)\s*(?P[0-9:]{11})$" +) + + +# these are all CMX_3600 transition codes +# the wipe is written in regex format because it is W### where the ### is +# a 'wipe code' +# @TODO: not currently read by the transition code +transition_regex_map = { + 'C': 'cut', + 'D': 'dissolve', + r'W\d{3}': 'wipe', + 'KB': 'key_background', + 'K': 'key_foreground', + 'KO': 'key_overlay' +} + +# CMX_3600 supports some shorthand for channel assignments +# We name the actual tracks V and A1,A2,A3,etc. +# This channel_map tells you which track to use for each channel shorthand. +# Channels not listed here are used as track names verbatim. +channel_map = { + 'A': ['A1'], + 'AA': ['A1', 'A2'], + 'B': ['V', 'A1'], + 'A2/V': ['V', 'A2'], + 'AA/V': ['V', 'A1', 'A2'] +} + + +# Currently, the 'style' argument determines +# the comment string for the media reference: +# 'avid': '* FROM CLIP:' (default) +# 'nucoda': '* FROM FILE:' +# When adding a new style, please be sure to add sufficient tests +# to verify both the new and existing styles. +VALID_EDL_STYLES = ['avid', 'nucoda'] + + +class EDLParser(object): + def __init__(self, edl_string, rate=24, ignore_timecode_mismatch=False): + self.timeline = schema.Timeline() + + # Start with no tracks. They will be added as we encounter them. + # This dict maps a track name (e.g "A2" or "V") to an OTIO Track. + self.tracks_by_name = {} + + self.ignore_timecode_mismatch = ignore_timecode_mismatch + + self.parse_edl(edl_string, rate=rate) + + # TODO: Sort the tracks V, then A1,A2,etc. + + def add_clip(self, line, comments, rate=24): + comment_handler = CommentHandler(comments) + clip_handler = ClipHandler(line, comment_handler.handled, rate=rate) + clip = clip_handler.clip + if comment_handler.unhandled: + clip.metadata.setdefault("cmx_3600", {}) + clip.metadata['cmx_3600'].setdefault("comments", []) + clip.metadata['cmx_3600']['comments'] += ( + comment_handler.unhandled + ) + + # Add reel name to metadata + # A reel name of `AX` represents an unknown or auxilary source + # We don't currently track these sources outside of this adapter + # So lets skip adding AX reels as metadata for now, + # as that would dirty json outputs with non-relevant information + if clip_handler.reel and clip_handler.reel != 'AX': + clip.metadata.setdefault("cmx_3600", {}) + clip.metadata['cmx_3600']['reel'] = clip_handler.reel + + # each edit point between two clips is a transition. the default is a + # cut in the edl format the transition codes are for the transition + # into the clip + self.add_transition( + clip_handler, + clip_handler.transition_type, + clip_handler.transition_data + ) + + tracks = self.tracks_for_channel(clip_handler.channel_code) + for track in tracks: + + edl_rate = clip_handler.edl_rate + record_in = opentime.from_timecode( + clip_handler.record_tc_in, + edl_rate + ) + record_out = opentime.from_timecode( + clip_handler.record_tc_out, + edl_rate + ) + + src_duration = clip.duration() + rec_duration = record_out - record_in + if rec_duration != src_duration: + motion = comment_handler.handled.get('motion_effect') + freeze = comment_handler.handled.get('freeze_frame') + if motion is not None or freeze is not None: + # Adjust the clip to match the record duration + clip.source_range = opentime.TimeRange( + start_time=clip.source_range.start_time, + duration=rec_duration + ) + + if freeze is not None: + clip.effects.append(schema.FreezeFrame()) + # XXX remove 'FF' suffix (writing edl will add it back) + if clip.name.endswith(' FF'): + clip.name = clip.name[:-3] + elif motion is not None: + fps = float( + SPEED_EFFECT_RE.match(motion).group("speed") + ) + time_scalar = fps / rate + clip.effects.append( + schema.LinearTimeWarp(time_scalar=time_scalar) + ) + + elif self.ignore_timecode_mismatch: + # Pretend there was no problem by adjusting the record_out. + # Note that we don't actually use record_out after this + # point in the code, since all of the subsequent math uses + # the clip's source_range. Adjusting the record_out is + # just to document what the implications of ignoring the + # mismatch here entails. + record_out = record_in + src_duration + + else: + raise EDLParseError( + "Source and record duration don't match: {} != {}" + " for clip {}".format( + src_duration, + rec_duration, + clip.name + )) + + if track.source_range is None: + zero = opentime.RationalTime(0, edl_rate) + track.source_range = opentime.TimeRange( + start_time=zero - record_in, + duration=zero + ) + + track_end = track.duration() - track.source_range.start_time + if record_in < track_end: + if self.ignore_timecode_mismatch: + # shift it over + record_in = track_end + record_out = record_in + rec_duration + else: + raise EDLParseError( + "Overlapping record in value: {} for clip {}".format( + clip_handler.record_tc_in, + clip.name + )) + + # If the next clip is supposed to start beyond the end of the + # clips we've accumulated so far, then we need to add a Gap + # to fill that space. This can happen when an EDL has record + # timecodes that are sparse (e.g. from a single track of a + # multi-track composition). + if record_in > track_end and len(track) > 0: + gap = schema.Gap() + gap.source_range = opentime.TimeRange( + start_time=opentime.RationalTime(0, edl_rate), + duration=record_in - track_end + ) + track.append(gap) + track.source_range = opentime.TimeRange( + start_time=track.source_range.start_time, + duration=track.source_range.duration + gap.duration() + ) + + track.append(clip) + track.source_range = opentime.TimeRange( + start_time=track.source_range.start_time, + duration=track.source_range.duration + clip.duration() + ) + + def guess_kind_for_track_name(self, name): + if name.startswith("V"): + return schema.TrackKind.Video + if name.startswith("A"): + return schema.TrackKind.Audio + return schema.TrackKind.Video + + def tracks_for_channel(self, channel_code): + # Expand channel shorthand into a list of track names. + if channel_code in channel_map: + track_names = channel_map[channel_code] + else: + track_names = [channel_code] + + # Create any channels we don't already have + for track_name in track_names: + if track_name not in self.tracks_by_name: + track = schema.Track( + name=track_name, + kind=self.guess_kind_for_track_name(track_name) + ) + self.tracks_by_name[track_name] = track + self.timeline.tracks.append(track) + + # Return a list of actual tracks + return [self.tracks_by_name[c] for c in track_names] + + def add_transition(self, clip_handler, transition, data): + if transition not in ['C']: + md = clip_handler.clip.metadata.setdefault("cmx_3600", {}) + md["transition"] = transition + + def parse_edl(self, edl_string, rate=24): + # edl 'events' can be comprised of an indeterminate amount of lines + # we are to translating 'events' to a single clip and transition + # then we add the transition and the clip to all channels the 'event' + # channel code is mapped to the transition given in the 'event' + # precedes the clip + + # remove all blank lines from the edl + edl_lines = [ + l for l in (l.strip() for l in edl_string.splitlines()) if l + ] + + while edl_lines: + # a basic for loop wont work cleanly since we need to look ahead at + # array elements to determine what type of 'event' we are looking + # at + line = edl_lines.pop(0) + + if line.startswith('TITLE:'): + # this is the first line of interest in an edl + # it is required to be in the header + self.timeline.name = line.replace('TITLE:', '').strip() + + elif line.startswith('FCM'): + # this can occur either in the header or before any 'event' + # in both cases we can ignore it since it is meant for tape + # timecode + pass + + elif line.startswith('SPLIT'): + # this is the only comment preceding an 'event' that we care + # about in our context it simply means the next two clips will + # have the same comment data it is for reading purposes only + audio_delay = None + video_delay = None + + if 'AUDIO DELAY' in line: + audio_delay = line.split()[-1].strip() + if 'VIDEO DELAY' in line: + video_delay = line.split()[-1].strip() + if audio_delay and video_delay: + raise EDLParseError( + 'both audio and video delay declared after SPLIT.' + ) + if not (audio_delay or video_delay): + raise EDLParseError( + 'either audio or video delay declared after SPLIT.' + ) + + line_1 = edl_lines.pop(0) + line_2 = edl_lines.pop(0) + + comments = [] + while edl_lines: + if re.match(r'^\D', edl_lines[0]): + comments.append(edl_lines.pop(0)) + else: + break + self.add_clip(line_1, comments, rate=rate) + self.add_clip(line_2, comments, rate=rate) + + elif line[0].isdigit(): + # all 'events' start_time with an edit decision. this is + # denoted by the line beginning with a padded integer 000-999 + comments = [] + while edl_lines: + # any non-numbered lines after an edit decision should be + # treated as 'comments' + # comments are string tags used by the reader to get extra + # information not able to be found in the restricted edl + # format + if re.match(r'^\D', edl_lines[0]): + comments.append(edl_lines.pop(0)) + else: + break + + self.add_clip(line, comments, rate=rate) + + else: + raise EDLParseError('Unknown event type') + + for track in self.timeline.tracks: + # if the source_range is the same as the available_range + # then we don't need to set it at all. + if track.source_range == track.available_range(): + track.source_range = None + + +class ClipHandler(object): + + def __init__(self, line, comment_data, rate=24): + self.clip_num = None + self.reel = None + self.channel_code = None + self.edl_rate = rate + self.transition_id = None + self.transition_data = None + self.source_tc_in = None + self.source_tc_out = None + self.record_tc_in = None + self.record_tc_out = None + + self.parse(line) + self.clip = self.make_clip(comment_data) + + def make_clip(self, comment_data): + clip = schema.Clip() + clip.name = str(self.clip_num) + + # BLACK/BL and BARS are called out as "Special Source Identifiers" in + # the documents referenced here: + # https://github.com/PixarAnimationStudios/OpenTimelineIO#cmx3600-edl + if self.reel in ['BL', 'BLACK']: + clip.media_reference = schema.GeneratorReference() + # TODO: Replace with enum, once one exists + clip.media_reference.generator_kind = 'black' + elif self.reel == 'BARS': + clip.media_reference = schema.GeneratorReference() + # TODO: Replace with enum, once one exists + clip.media_reference.generator_kind = 'SMPTEBars' + elif 'media_reference' in comment_data: + clip.media_reference = schema.ExternalReference() + clip.media_reference.target_url = comment_data[ + 'media_reference' + ] + else: + clip.media_reference = schema.MissingReference() + + # this could currently break without a 'FROM CLIP' comment. + # Without that there is no 'media_reference' Do we have a default + # clip name? + if 'clip_name' in comment_data: + clip.name = comment_data["clip_name"] + elif ( + clip.media_reference and + hasattr(clip.media_reference, 'target_url') and + clip.media_reference.target_url is not None + ): + clip.name = os.path.splitext( + os.path.basename(clip.media_reference.target_url) + )[0] + + asc_sop = comment_data.get('asc_sop', None) + asc_sat = comment_data.get('asc_sat', None) + if asc_sop or asc_sat: + slope = (1, 1, 1) + offset = (0, 0, 0) + power = (1, 1, 1) + sat = 1.0 + + if asc_sop: + triple = r'([-+]?[\d.]+) ([-+]?[\d.]+) ([-+]?[\d.]+)' + m = re.match( + r'\(' + + triple + + r'\)\s*\(' + + triple + r'\)\s*\(' + + triple + r'\)', + asc_sop + ) + if m: + floats = [float(g) for g in m.groups()] + slope = [floats[0], floats[1], floats[2]] + offset = [floats[3], floats[4], floats[5]] + power = [floats[6], floats[7], floats[8]] + else: + raise EDLParseError( + 'Invalid ASC_SOP found: {}'.format(asc_sop)) + + if asc_sat: + sat = float(asc_sat) + + clip.metadata['cdl'] = { + 'asc_sat': sat, + 'asc_sop': { + 'slope': slope, + 'offset': offset, + 'power': power + } + } + + if 'locator' in comment_data: + # An example EDL locator line looks like this: + # * LOC: 01:00:01:14 RED ANIM FIX NEEDED + # We get the part after "LOC: " as the comment_data entry + # Given the fixed-width nature of these, we could be more + # strict about the field widths, but there are many + # variations of EDL, so if we are lenient then maybe we + # can handle more of them? Only real-world testing will + # determine this for sure... + m = re.match( + r'(\d\d:\d\d:\d\d:\d\d)\s+(\w*)\s+(.*)', + comment_data["locator"] + ) + if m: + marker = schema.Marker() + marker.marked_range = opentime.TimeRange( + start_time=opentime.from_timecode( + m.group(1), + self.edl_rate + ), + duration=opentime.RationalTime() + ) + + # always write the source value into metadata, in case it + # is not a valid enum somehow. + color_parsed_from_file = m.group(2) + + marker.metadata = { + "cmx_3600": { + "color": color_parsed_from_file + } + } + + # @TODO: if it is a valid + if hasattr( + schema.MarkerColor, + color_parsed_from_file.upper() + ): + marker.color = color_parsed_from_file.upper() + else: + marker.color = schema.MarkerColor.RED + + marker.name = m.group(3) + clip.markers.append(marker) + else: + # TODO: Should we report this as a warning somehow? + pass + + clip.source_range = opentime.range_from_start_end_time( + opentime.from_timecode(self.source_tc_in, self.edl_rate), + opentime.from_timecode(self.source_tc_out, self.edl_rate) + ) + + return clip + + def parse(self, line): + fields = tuple(e.strip() for e in line.split() if e.strip()) + field_count = len(fields) + + if field_count == 9: + # has transition data + # this is for edits with timing or other needed info + # transition data for D and W*** transitions is a n integer that + # denotes frame count + # i haven't figured out how the key transitions (K, KB, KO) work + ( + self.clip_num, + self.reel, + self.channel_code, + self.transition_type, + self.transition_data, + self.source_tc_in, + self.source_tc_out, + self.record_tc_in, + self.record_tc_out + ) = fields + + elif field_count == 8: + # no transition data + # this is for basic cuts + ( + self.clip_num, + self.reel, + self.channel_code, + self.transition_type, + self.source_tc_in, + self.source_tc_out, + self.record_tc_in, + self.record_tc_out + ) = fields + + else: + raise EDLParseError( + 'incorrect number of fields [{0}] in form statement: {1}' + ''.format(field_count, line)) + + # Frame numbers (not just timecode) are ok + for prop in [ + 'source_tc_in', + 'source_tc_out', + 'record_tc_in', + 'record_tc_out' + ]: + if ':' not in getattr(self, prop): + setattr( + self, + prop, + opentime.to_timecode( + opentime.from_frames( + int(getattr(self, prop)), + self.edl_rate + ), + self.edl_rate + ) + ) + + +class CommentHandler(object): + # this is the for that all comment 'id' tags take + regex_template = r'\*?\s*{id}:?\s*(?P.*)' + + # this should be a map of all known comments that we can read + # 'FROM CLIP' or 'FROM FILE' is a required comment to link media + # An exception is raised if both 'FROM CLIP' and 'FROM FILE' are found + # needs to be ordered so that FROM CLIP NAME gets matched before FROM CLIP + comment_id_map = collections.OrderedDict([ + ('FROM CLIP NAME', 'clip_name'), + ('FROM CLIP', 'media_reference'), + ('FROM FILE', 'media_reference'), + ('LOC', 'locator'), + ('ASC_SOP', 'asc_sop'), + ('ASC_SAT', 'asc_sat'), + ('M2', 'motion_effect'), + ('\\* FREEZE FRAME', 'freeze_frame'), + ]) + + def __init__(self, comments): + self.handled = {} + self.unhandled = [] + for comment in comments: + self.parse(comment) + + def parse(self, comment): + for comment_id, comment_type in self.comment_id_map.items(): + regex = self.regex_template.format(id=comment_id) + match = re.match(regex, comment) + if match: + self.handled[comment_type] = match.group( + 'comment_body' + ).strip() + break + else: + stripped = comment.lstrip('*').strip() + if stripped: + self.unhandled.append(stripped) + + +def _expand_transitions(timeline): + """Convert clips with metadata/transition == 'D' into OTIO transitions.""" + + tracks = timeline.tracks + remove_list = [] + replace_list = [] + append_list = [] + for track in tracks: + track_iter = iter(track) + # avid inserts an extra clip for the source + prev_prev = None + prev = None + clip = next(track_iter, None) + next_clip = next(track_iter, None) + while clip is not None: + transition_type = clip.metadata.get('cmx_3600', {}).get( + 'transition', + 'C' + ) + + if transition_type == 'C': + # nothing to do, continue to the next iteration of the loop + prev_prev = prev + prev = clip + clip = next_clip + next_clip = next(track_iter, None) + continue + if transition_type not in ['D']: + raise EDLParseError( + "Transition type '{}' not supported by the CMX EDL reader " + "currently.".format(transition_type) + ) + + transition_duration = clip.duration() + + # EDL doesn't have enough data to know where the cut point was, so + # this arbitrarily puts it in the middle of the transition + pre_cut = math.floor(transition_duration.value / 2) + post_cut = transition_duration.value - pre_cut + mid_tran_cut_pre_duration = opentime.RationalTime( + pre_cut, + transition_duration.rate + ) + mid_tran_cut_post_duration = opentime.RationalTime( + post_cut, + transition_duration.rate + ) + + # expand the previous + expansion_clip = None + if prev and not prev_prev: + expansion_clip = prev + elif prev_prev: + expansion_clip = prev_prev + if prev: + remove_list.append((track, prev)) + + sr = expansion_clip.source_range + expansion_clip.source_range = opentime.TimeRange( + start_time=sr.start_time, + duration=sr.duration + mid_tran_cut_pre_duration + ) + + # rebuild the clip as a transition + new_trx = schema.Transition( + name=clip.name, + # only supported type at the moment + transition_type=schema.TransitionTypes.SMPTE_Dissolve, + metadata=clip.metadata + ) + new_trx.in_offset = mid_tran_cut_pre_duration + new_trx.out_offset = mid_tran_cut_post_duration + + # in from to + replace_list.append((track, clip, new_trx)) + + # expand the next_clip + if next_clip: + next_clip.source_range = opentime.TimeRange( + next_clip.source_range.start_time - mid_tran_cut_post_duration, + next_clip.source_range.duration + mid_tran_cut_post_duration + ) + else: + fill = schema.Gap( + source_range=opentime.TimeRange( + duration=mid_tran_cut_post_duration, + start_time=opentime.RationalTime( + 0, + transition_duration.rate + ) + ) + ) + append_list.append((track, fill)) + + prev = clip + clip = next_clip + next_clip = next(track_iter, None) + + for (track, from_clip, to_transition) in replace_list: + track[track.index(from_clip)] = to_transition + + for (track, clip_to_remove) in list(set(remove_list)): + # if clip_to_remove in track: + track.remove(clip_to_remove) + + for (track, clip) in append_list: + track.append(clip) + + return timeline + + +def read_from_string(input_str, rate=24, ignore_timecode_mismatch=False): + """Reads a CMX Edit Decision List (EDL) from a string. + Since EDLs don't contain metadata specifying the rate they are meant + for, you may need to specify the rate parameter (default is 24). + By default, read_from_string will throw an exception if it discovers + invalid timecode in the EDL. For example, if a clip's record timecode + overlaps with the previous cut. Since this is a common mistake in + many EDLs, you can specify ignore_timecode_mismatch=True, which will + supress these errors and attempt to guess at the correct record + timecode based on the source timecode and adjacent cuts. + For best results, you may wish to do something like this: + + Example: + >>> try: + ... timeline = otio.adapters.read_from_string("mymovie.edl", rate=30) + ... except EDLParseError: + ... print('Log a warning here') + ... try: + ... timeline = otio.adapters.read_from_string( + ... "mymovie.edl", + ... rate=30, + ... ignore_timecode_mismatch=True) + ... except EDLParseError: + ... print('Log an error here') + """ + parser = EDLParser( + input_str, + rate=float(rate), + ignore_timecode_mismatch=ignore_timecode_mismatch + ) + result = parser.timeline + result = _expand_transitions(result) + return result + + +def write_to_string(input_otio, rate=None, style='avid', reelname_len=8): + # TODO: We should have convenience functions in Timeline for this? + # also only works for a single video track at the moment + + video_tracks = [t for t in input_otio.tracks + if t.kind == schema.TrackKind.Video] + audio_tracks = [t for t in input_otio.tracks + if t.kind == schema.TrackKind.Audio] + + if len(video_tracks) != 1: + raise exceptions.NotSupportedError( + "Only a single video track is supported, got: {}".format( + len(video_tracks) + ) + ) + + if len(audio_tracks) > 2: + raise exceptions.NotSupportedError( + "No more than 2 audio tracks are supported." + ) + # if audio_tracks: + # raise exceptions.NotSupportedError( + # "No audio tracks are currently supported." + # ) + + # TODO: We should try to detect the frame rate and output an + # appropriate "FCM: NON-DROP FRAME" etc here. + + writer = EDLWriter( + tracks=input_otio.tracks, + # Assume all rates are the same as the 1st track's + rate=rate or input_otio.tracks[0].duration().rate, + style=style, + reelname_len=reelname_len + ) + + return writer.get_content_for_track_at_index(0, title=input_otio.name) + + +class EDLWriter(object): + def __init__(self, tracks, rate, style, reelname_len=8): + self._tracks = tracks + self._rate = rate + self._style = style + self._reelname_len = reelname_len + + if style not in VALID_EDL_STYLES: + raise exceptions.NotSupportedError( + "The EDL style '{}' is not supported.".format( + style + ) + ) + + def get_content_for_track_at_index(self, idx, title): + track = self._tracks[idx] + + # Add a gap if the last child is a transition. + if isinstance(track[-1], schema.Transition): + gap = schema.Gap( + source_range=opentime.TimeRange( + start_time=track[-1].duration(), + duration=opentime.RationalTime(0.0, self._rate) + ) + ) + track.append(gap) + + # Note: Transitions in EDLs are unconventionally represented. + # + # Where a transition might normally be visualized like: + # |---57.0 Trans 43.0----| + # |------Clip1 102.0------|----------Clip2 143.0----------|Clip3 24.0| + # + # In an EDL it can be thought of more like this: + # |---0.0 Trans 100.0----| + # |Clip1 45.0|----------------Clip2 200.0-----------------|Clip3 24.0| + + # Adjust cut points to match EDL event representation. + for idx, child in enumerate(track): + if isinstance(child, schema.Transition): + if idx != 0: + # Shorten the a-side + sr = track[idx - 1].source_range + track[idx - 1].source_range = opentime.TimeRange( + start_time=sr.start_time, + duration=sr.duration - child.in_offset + ) + + # Lengthen the b-side + sr = track[idx + 1].source_range + track[idx + 1].source_range = opentime.TimeRange( + start_time=sr.start_time - child.in_offset, + duration=sr.duration + child.in_offset + ) + + # Just clean up the transition for goodness sake + in_offset = child.in_offset + child.in_offset = opentime.RationalTime(0.0, self._rate) + child.out_offset += in_offset + + # Group events into either simple clip/a-side or transition and b-side + # to match EDL edit/event representation and edit numbers. + events = [] + for idx, child in enumerate(track): + if isinstance(child, schema.Transition): + # Transition will be captured in subsequent iteration. + continue + + prv = track[idx - 1] if idx > 0 else None + + if isinstance(prv, schema.Transition): + events.append( + DissolveEvent( + events[-1] if len(events) else None, + prv, + child, + self._tracks, + track.kind, + self._rate, + self._style, + self._reelname_len + ) + ) + elif isinstance(child, schema.Clip): + events.append( + Event( + child, + self._tracks, + track.kind, + self._rate, + self._style, + self._reelname_len + ) + ) + elif isinstance(child, schema.Gap): + # Gaps are represented as missing record timecode, no event + # needed. + pass + + content = "TITLE: {}\n\n".format(title) if title else '' + + # Convert each event/dissolve-event into plain text. + for idx, event in enumerate(events): + event.edit_number = idx + 1 + content += event.to_edl_format() + '\n' + + return content + + +def _supported_timing_effects(clip): + return [ + fx for fx in clip.effects + if isinstance(fx, schema.LinearTimeWarp) + ] + + +def _relevant_timing_effect(clip): + # check to see if there is more than one timing effect + effects = _supported_timing_effects(clip) + + if effects != clip.effects: + for thing in clip.effects: + if thing not in effects and isinstance(thing, schema.TimeEffect): + raise exceptions.NotSupportedError( + "Clip contains timing effects not supported by the EDL" + " adapter.\nClip: {}".format(str(clip))) + + timing_effect = None + if effects: + timing_effect = effects[0] + if len(effects) > 1: + raise exceptions.NotSupportedError( + "EDL Adapter only allows one timing effect / clip." + ) + + return timing_effect + + +class Event(object): + def __init__( + self, + clip, + tracks, + kind, + rate, + style, + reelname_len + ): + + line = EventLine(kind, rate, reel=_reel_from_clip(clip, reelname_len)) + line.source_in = clip.source_range.start_time + line.source_out = clip.source_range.end_time_exclusive() + + timing_effect = _relevant_timing_effect(clip) + + if timing_effect: + if timing_effect.effect_name == "FreezeFrame": + line.source_out = line.source_in + opentime.RationalTime( + 1, + line.source_in.rate + ) + elif timing_effect.effect_name == "LinearTimeWarp": + value = clip.trimmed_range().duration.value / timing_effect.time_scalar + line.source_out = ( + line.source_in + opentime.RationalTime(value, rate)) + + range_in_timeline = clip.transformed_time_range( + clip.trimmed_range(), + tracks + ) + line.record_in = range_in_timeline.start_time + line.record_out = range_in_timeline.end_time_exclusive() + self.line = line + + self.comments = _generate_comment_lines( + clip=clip, + style=style, + edl_rate=rate, + reelname_len=reelname_len, + from_or_to='FROM' + ) + + self.clip = clip + self.source_out = line.source_out + self.record_out = line.record_out + self.reel = line.reel + + def __str__(self): + return '{type}({name})'.format( + type=self.clip.schema_name(), + name=self.clip.name + ) + + def to_edl_format(self): + """ + Example output: + 002 AX V C 00:00:00:00 00:00:00:05 00:00:00:05 00:00:00:10 + * FROM CLIP NAME: test clip2 + * FROM FILE: S:\\var\\tmp\\test.exr + + """ + lines = [self.line.to_edl_format(self.edit_number)] + lines += self.comments if len(self.comments) else [] + + return "\n".join(lines) + + +class DissolveEvent(object): + + def __init__( + self, + a_side_event, + transition, + b_side_clip, + tracks, + kind, + rate, + style, + reelname_len + ): + # Note: We don't make the A-Side event line here as it is represented + # by its own event (edit number). + + cut_line = EventLine(kind, rate) + + if a_side_event: + cut_line.reel = a_side_event.reel + cut_line.source_in = a_side_event.source_out + cut_line.source_out = a_side_event.source_out + cut_line.record_in = a_side_event.record_out + cut_line.record_out = a_side_event.record_out + + self.from_comments = _generate_comment_lines( + clip=a_side_event.clip, + style=style, + edl_rate=rate, + reelname_len=reelname_len, + from_or_to='FROM' + ) + else: + cut_line.reel = 'BL' + cut_line.source_in = opentime.RationalTime(0.0, rate) + cut_line.source_out = opentime.RationalTime(0.0, rate) + cut_line.record_in = opentime.RationalTime(0.0, rate) + cut_line.record_out = opentime.RationalTime(0.0, rate) + + self.cut_line = cut_line + + dslve_line = EventLine( + kind, + rate, + reel=_reel_from_clip(b_side_clip, reelname_len) + ) + dslve_line.source_in = b_side_clip.source_range.start_time + dslve_line.source_out = b_side_clip.source_range.end_time_exclusive() + range_in_timeline = b_side_clip.transformed_time_range( + b_side_clip.trimmed_range(), + tracks + ) + dslve_line.record_in = range_in_timeline.start_time + dslve_line.record_out = range_in_timeline.end_time_exclusive() + dslve_line.dissolve_length = transition.out_offset + self.dissolve_line = dslve_line + + self.to_comments = _generate_comment_lines( + clip=b_side_clip, + style=style, + edl_rate=rate, + reelname_len=reelname_len, + from_or_to='TO' + ) + + self.a_side_event = a_side_event + self.transition = transition + self.b_side_clip = b_side_clip + + # Expose so that any subsequent dissolves can borrow their values. + self.clip = b_side_clip + self.source_out = dslve_line.source_out + self.record_out = dslve_line.record_out + self.reel = dslve_line.reel + + def __str__(self): + a_side = self.a_side_event + return '{a_type}({a_name}) -> {b_type}({b_name})'.format( + a_type=a_side.clip.schema_name() if a_side else '', + a_name=a_side.clip.name if a_side else '', + b_type=self.b_side_clip.schema_name(), + b_name=self.b_side_clip.name + ) + + def to_edl_format(self): + """ + Example output: + + Cross dissolve... + 002 Clip1 V C 00:00:07:08 00:00:07:08 00:00:01:21 00:00:01:21 + 002 Clip2 V D 100 00:00:09:07 00:00:17:15 00:00:01:21 00:00:10:05 + * FROM CLIP NAME: Clip1 + * FROM CLIP: /var/tmp/clip1.001.exr + * TO CLIP NAME: Clip2 + * TO CLIP: /var/tmp/clip2.001.exr + + Fade in... + 001 BL V C 00:00:00:00 00:00:00:00 00:00:00:00 00:00:00:00 + 001 My_Clip V D 012 00:00:02:02 00:00:03:04 00:00:00:00 00:00:01:02 + * TO CLIP NAME: My Clip + * TO FILE: /var/tmp/clip.001.exr + + Fade out... + 002 My_Clip V C 00:00:01:12 00:00:01:12 00:00:00:12 00:00:00:12 + 002 BL V D 012 00:00:00:00 00:00:00:12 00:00:00:12 00:00:01:00 + * FROM CLIP NAME: My Clip + * FROM FILE: /var/tmp/clip.001.exr + """ + + lines = [ + self.cut_line.to_edl_format(self.edit_number), + self.dissolve_line.to_edl_format(self.edit_number) + ] + lines += self.from_comments if hasattr(self, 'from_comments') else [] + lines += self.to_comments if len(self.to_comments) else [] + + return "\n".join(lines) + + +class EventLine(object): + def __init__(self, kind, rate, reel='AX'): + self.reel = reel + self._kind = 'V' if kind == schema.TrackKind.Video else 'A' + self._rate = rate + + self.source_in = opentime.RationalTime(0.0, rate=rate) + self.source_out = opentime.RationalTime(0.0, rate=rate) + self.record_in = opentime.RationalTime(0.0, rate=rate) + self.record_out = opentime.RationalTime(0.0, rate=rate) + + self.dissolve_length = opentime.RationalTime(0.0, rate) + + def to_edl_format(self, edit_number): + ser = { + 'edit': edit_number, + 'reel': self.reel, + 'kind': self._kind, + 'src_in': opentime.to_timecode(self.source_in, self._rate), + 'src_out': opentime.to_timecode(self.source_out, self._rate), + 'rec_in': opentime.to_timecode(self.record_in, self._rate), + 'rec_out': opentime.to_timecode(self.record_out, self._rate), + 'diss': int( + opentime.to_frames(self.dissolve_length, self._rate) + ), + } + + if self.is_dissolve(): + return "{edit:03d} {reel:8} {kind:5} D {diss:03d} " \ + "{src_in} {src_out} {rec_in} {rec_out}".format(**ser) + else: + return "{edit:03d} {reel:8} {kind:5} C " \ + "{src_in} {src_out} {rec_in} {rec_out}".format(**ser) + + def is_dissolve(self): + return self.dissolve_length.value > 0 + + +def _generate_comment_lines( + clip, + style, + edl_rate, + reelname_len, + from_or_to='FROM' +): + lines = [] + url = None + + if not clip or isinstance(clip, schema.Gap): + return [] + + suffix = '' + timing_effect = _relevant_timing_effect(clip) + if timing_effect and timing_effect.effect_name == 'FreezeFrame': + suffix = ' FF' + + if clip.media_reference: + if hasattr(clip.media_reference, 'target_url'): + url = clip.media_reference.target_url + + else: + url = clip.name + + if from_or_to not in ['FROM', 'TO']: + raise exceptions.NotSupportedError( + "The clip FROM or TO value '{}' is not supported.".format( + from_or_to + ) + ) + + if timing_effect and isinstance(timing_effect, schema.LinearTimeWarp): + lines.append( + 'M2 {}\t\t{}\t\t\t{}'.format( + clip.name, + timing_effect.time_scalar * edl_rate, + opentime.to_timecode( + clip.trimmed_range().start_time, + edl_rate + ) + ) + ) + + if clip.name: + # Avid Media Composer outputs two spaces before the + # clip name so we match that. + lines.append( + "* {from_or_to} CLIP NAME: {name}{suffix}".format( + from_or_to=from_or_to, + name=clip.name, + suffix=suffix + ) + ) + if timing_effect and timing_effect.effect_name == "FreezeFrame": + lines.append('* * FREEZE FRAME') + if url and style == 'avid': + lines.append("* {from_or_to} CLIP: {url}".format( + from_or_to=from_or_to, + url=url + )) + if url and style == 'nucoda': + lines.append("* {from_or_to} FILE: {url}".format( + from_or_to=from_or_to, + url=url + )) + + if reelname_len and not clip.metadata.get('cmx_3600', {}).get('reel'): + lines.append("* OTIO TRUNCATED REEL NAME FROM: {url}".format( + url=os.path.basename(_flip_windows_slashes(url or clip.name)) + )) + + cdl = clip.metadata.get('cdl') + if cdl: + asc_sop = cdl.get('asc_sop') + asc_sat = cdl.get('asc_sat') + if asc_sop: + lines.append( + "*ASC_SOP ({} {} {}) ({} {} {}) ({} {} {})".format( + asc_sop['slope'][0], + asc_sop['slope'][1], + asc_sop['slope'][2], + asc_sop['offset'][0], + asc_sop['offset'][1], + asc_sop['offset'][2], + asc_sop['power'][0], + asc_sop['power'][1], + asc_sop['power'][2] + )) + if asc_sat: + lines.append("*ASC_SAT {}".format( + asc_sat + )) + + # Output any markers on this clip + for marker in clip.markers: + timecode = opentime.to_timecode( + marker.marked_range.start_time, + edl_rate + ) + + color = marker.color + meta = marker.metadata.get("cmx_3600") + if not color and meta and meta.get("color"): + color = meta.get("color").upper() + comment = (marker.name or '').upper() + lines.append("* LOC: {} {:7} {}".format(timecode, color, comment)) + + # If we are carrying any unhandled CMX 3600 comments on this clip + # then output them blindly. + extra_comments = clip.metadata.get('cmx_3600', {}).get('comments', []) + for comment in extra_comments: + lines.append("* {}".format(comment)) + + return lines + + +def _flip_windows_slashes(path): + return re.sub(r'\\', '/', path) + + +def _reel_from_clip(clip, reelname_len): + if isinstance(clip, schema.Gap): + return 'BL' + + elif clip.metadata.get('cmx_3600', {}).get('reel'): + return clip.metadata.get('cmx_3600').get('reel') + + _reel = clip.name or 'AX' + + if isinstance(clip.media_reference, schema.ExternalReference): + _reel = clip.media_reference.name or os.path.basename( + clip.media_reference.target_url + ) + + # Flip Windows slashes + _reel = os.path.basename(_flip_windows_slashes(_reel)) + + # Strip extension + reel = re.sub(r'([.][a-zA-Z]+)$', '', _reel) + + if reelname_len: + # Remove non valid characters + reel = re.sub(r'[^ a-zA-Z0-9]+', '', reel) + + if len(reel) > reelname_len: + reel = reel[:reelname_len] + + elif len(reel) < reelname_len: + reel += ' ' * (reelname_len - len(reel)) + + return reel diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/fcp_xml.py b/openpype/vendor/python/python_2/opentimelineio/adapters/fcp_xml.py new file mode 100644 index 0000000000..48f684cc36 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/fcp_xml.py @@ -0,0 +1,1941 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO Final Cut Pro 7 XML Adapter.""" + +import collections +import functools +import itertools +import math +import os +import re +from xml.etree import cElementTree +from xml.dom import minidom + +# urlparse's name changes in Python 3 +try: + # Python 2.7 + import urlparse as urllib_parse +except ImportError: + # Python 3 + import urllib.parse as urllib_parse + +# Same with the ABC classes from collections +try: + # Python 3 + from collections.abc import Mapping +except ImportError: + # Python 2.7 + from collections import Mapping + +from opentimelineio import ( + core, + opentime, + schema, +) + +# namespace to use for metadata +META_NAMESPACE = 'fcp_xml' + +# Regex to match identifiers like clipitem-22 +ID_RE = re.compile(r"^(?P[a-zA-Z]*)-(?P\d*)$") + + +# --------- +# utilities +# --------- + + +class _Context(Mapping): + """ + An inherited value context. + + In FCP XML there is a concept of inheritance down the element heirarchy. + For instance, a ``clip`` element may not specify the ``rate`` locally, but + instead inherit it from the parent ``track`` element. + + This object models that as a stack of elements. When a value needs to be + queried from the context, it will be gathered by walking from the top of + the stack until the value is found. + + For example, to find the ``rate`` element as an immediate child most + appropriate to the current context, you would do something like:: + ``my_current_context["./rate"]`` + + This object can be thought of as immutable. You get a new context when you + push an element. This prevents inadvertant tampering with parent contexts + that may be used at levels above. + + This DOES NOT support ``id`` attribute dereferencing, please make sure to + do that prior to using this structure. + + .. seealso:: https://developer.apple.com/library/archive/documentation\ + /AppleApplications/Reference/FinalCutPro_XML/Basics/Basics.html#\ + //apple_ref/doc/uid/TP30001154-TPXREF102 + """ + + def __init__(self, element=None, parent_elements=None): + if parent_elements is not None: + self.elements = parent_elements[:] + else: + self.elements = [] + + if element is not None: + self.elements.append(element) + + def _all_keys(self): + """ + Returns a set of all the keys available in the context stack. + """ + return set( + itertools.chain.fromiterable(e.keys() for e in self.elements) + ) + + def __getitem__(self, key): + # Walk down the contexts until the item is found + for element in reversed(self.elements): + found_element = element.find(key) + if found_element is not None: + return found_element + + raise KeyError(key) + + def __iter__(self): + # This is unlikely to be used, so we'll do it the expensive way + return iter(self._all_keys) + + def __len__(self): + # This is unlikely to be used, so we'll do it the expensive way + return len(self._all_keys) + + def context_pushing_element(self, element): + """ + Pushes an element to the top of the stack. + + :param element: Element to push to the stack. + :return: The new context with the provided element pushed to the top + of the stack. + :raises: :class:`ValueError` if the element is already in the stack. + """ + for context_element in self.elements: + if context_element == element: + raise ValueError( + "element {} already in context".format(element) + ) + + return _Context(element, self.elements) + + +def _url_to_path(url): + parsed = urllib_parse.urlparse(url) + return parsed.path + + +def _bool_value(element): + """ + Given an xml element, returns the tag text converted to a bool. + + :param element: The element to fetch the value from. + + :return: A boolean. + """ + return (element.text.lower() == "true") + + +def _element_identification_string(element): + """ + Gets a string that will hopefully help in identifing an element when there + is an error. + """ + info_string = "tag: {}".format(element.tag) + try: + elem_id = element.attrib["id"] + info_string += " id: {}".format(elem_id) + except KeyError: + pass + + return info_string + + +def _name_from_element(element): + """ + Fetches the name from the ``name`` element child of the provided element. + If no element exists, returns ``None``. + + :param element: The element to find the name for. + + :return: The name string or ``None`` + """ + name_elem = element.find("./name") + if name_elem is not None: + return name_elem.text + + return None + + +def _rate_for_element(element): + """ + Takes an FCP rate element and returns a rate to use with otio. + + :param element: An FCP rate element. + + :return: The float rate. + """ + # rate is encoded as a timebase (int) which can be drop-frame + base = float(element.find("./timebase").text) + if _bool_value(element.find("./ntsc")): + base *= 1000.0 / 1001 + + return base + + +def _rate_from_context(context): + """ + Given the context object, gets the appropriate rate. + + :param context: The :class:`_Context` instance to find the rate in. + + :return: The rate value or ``None`` if no rate is available in the context. + """ + try: + rate_element = context["./rate"] + except KeyError: + return None + + return _rate_for_element(rate_element) + + +def _time_from_timecode_element(tc_element, context=None): + """ + Given a timecode xml element, returns the time that represents. + + .. todo:: Non Drop-Frame timecode is not yet supported by OTIO. + + :param tc_element: The ``timecode`` element. + :param context: The context dict under which this timecode is being gotten. + + :return: The :class:`opentime.RationalTime` representation of the + timecode. + """ + if context is not None: + local_context = context.context_pushing_element(tc_element) + else: + local_context = _Context(tc_element) + + # Resolve the rate + rate = _rate_from_context(local_context) + + # Try using the display format and frame number + frame = tc_element.find("./frame") + + # Use frame number, if available + if frame is not None: + frame_num = int(frame.text) + return opentime.RationalTime(frame_num, rate) + + # If a TC string is provided, parse rate from it + tc_string_element = tc_element.find("./string") + if tc_string_element is None: + raise ValueError("Timecode element missing required elements") + + tc_string = tc_string_element.text + + return opentime.from_timecode(tc_string, rate) + + +def _track_kind_from_element(media_element): + """ + Given an FCP XML media sub-element, returns an appropriate + :class:`schema.TrackKind` value corresponding to that media type. + + :param media_element: An XML element that is a child of the ``media`` tag. + + :return: The corresponding :class`schema.TrackKind` value. + :raises: :class:`ValueError` When the media type is unsupported. + """ + element_tag = media_element.tag.lower() + if element_tag == "audio": + return schema.TrackKind.Audio + elif element_tag == "video": + return schema.TrackKind.Video + + raise ValueError("Unsupported media kind: {}".format(media_element.tag)) + + +def _is_primary_audio_channel(track): + """ + Determines whether or not this is the "primary" audio track. + + audio may be structured in stereo where each channel occupies a separate + track. This importer keeps stereo pairs ganged together as a single track. + + :param track: An XML track element. + + :return: A boolean ``True`` if this is the first track. + """ + exploded_index = track.attrib.get('currentExplodedTrackIndex', '0') + exploded_count = track.attrib.get('totalExplodedTrackCount', '1') + + return (exploded_index == '0' or exploded_count == '1') + + +def _transition_cut_point(transition_item, context): + """ + Returns the end time at which the transition progresses from one clip to + the next. + + :param transition_item: The XML element for the transition. + :param context: The context dictionary applying to this transition. + + :return: The :class:`opentime.RationalTime` the transition cuts at. + """ + alignment = transition_item.find('./alignment').text + start = int(transition_item.find('./start').text) + end = int(transition_item.find('./end').text) + + # start/end time is in the parent context's rate + local_context = context.context_pushing_element(transition_item) + rate = _rate_from_context(local_context) + + if alignment in ('end', 'end-black'): + value = end + elif alignment in ('start', 'start-black'): + value = start + elif alignment in ('center',): + value = int((start + end) / 2) + else: + value = int((start + end) / 2) + + return opentime.RationalTime(value, rate) + + +def _xml_tree_to_dict(node, ignore_tags=None, omit_timing=True): + """ + Translates the tree under a provided node mapping to a dictionary/list + representation. XML tag attributes are placed in the dictionary with an + ``@`` prefix. + + .. note:: In addition to the provided ignore tags, this filters a subset of + timing metadata such as ``frame`` and ``string`` elements within timecode + elements. + + .. warning:: This scheme does not allow for leaf elements to have + attributes. for the moment this doesn't seem to be an issue. + + :param node: The root xml element to express childeren of in the + dictionary. + :param ignore_tags: A collection of tagnames to skip when converting. + :param omit_timing: If ``True``, omits timing-specific tags. + + :return: The dictionary representation. + """ + if node.tag == "timecode": + additional_ignore_tags = {"frame", "string"} + else: + additional_ignore_tags = tuple() + + out_dict = collections.OrderedDict() + + # Handle the attributes + out_dict.update( + collections.OrderedDict( + ("@{}".format(k), v) for k, v in node.attrib.items() + ) + ) + + # Now traverse the child tags + encountered_tags = set() + list_tags = set() + for info_node in node: + # Skip tags we were asked to omit + node_tag = info_node.tag + if ignore_tags and node_tag in ignore_tags: + continue + + # Skip some special case tags related to timing information + if node_tag in additional_ignore_tags: + continue + + # If there are children, make this a sub-dictionary by recursing + if len(info_node): + node_value = _xml_tree_to_dict(info_node) + else: + node_value = info_node.text + + # If we've seen this node before, then treat it as a list + if node_tag in list_tags: + # We've established that this tag is a list, append to that + out_dict[node_tag].append(node_value) + elif node_tag in encountered_tags: + # This appears to be a list we didn't know about, convert + out_dict[node_tag] = [ + out_dict[node_tag], node_value + ] + list_tags.add(node_tag) + else: + # Store the value + out_dict[node_tag] = node_value + encountered_tags.add(node_tag) + + return out_dict + + +def _dict_to_xml_tree(data_dict, tag): + """ + Given a dictionary, returns an element tree storing the data. This is the + inverse of :func:`_xml_tree_to_dict`. + + Any key/value pairs in the dictionary heirarchy where the key is prefixed + with ``@`` will be treated as attributes on the containing element. + + .. note:: This will automatically omit some kinds of metadata it should + be up to the xml building functions to manage (such as timecode and id). + + :param data_dict: The dictionary to turn into an XML tree. + :param tag: The tag name to use for the top-level element. + + :return: The top element for the dictionary + """ + top_attributes = collections.OrderedDict( + (k[1:], v) for k, v in data_dict.items() + if k != "@id" and k.startswith("@") + ) + top_element = cElementTree.Element(tag, **top_attributes) + + def elements_for_value(python_value, element_tag): + """ Creates a list of appropriate XML elements given a value. """ + if isinstance(python_value, dict): + element = _dict_to_xml_tree(python_value, element_tag) + return [element] + elif isinstance(python_value, list): + return itertools.chain.from_iterable( + elements_for_value(item, element_tag) for item in python_value + ) + else: + element = cElementTree.Element(element_tag) + if python_value is not None: + element.text = str(python_value) + return [element] + + # Drop timecode, rate, and link elements from roundtripping because they + # may become stale with timeline updates. + default_ignore_keys = {"timecode", "rate", "link"} + specific_ignore_keys = {"samplecharacteristics": {"timecode"}} + ignore_keys = specific_ignore_keys.get(tag, default_ignore_keys) + + # push the elements into the tree + for key, value in data_dict.items(): + if key in ignore_keys: + continue + + # We already handled the attributes + if key.startswith("@"): + continue + + elements = elements_for_value(value, key) + top_element.extend(elements) + + return top_element + + +def _element_with_item_metadata(tag, item): + """ + Given a tag name, gets the FCP XML metadata dict and creates a tree of XML + with that metadata under a top element with the provided tag. + + :param tag: The XML tag for the root element. + :param item: An otio object with a metadata dict. + """ + item_meta = item.metadata.get(META_NAMESPACE) + if item_meta: + return _dict_to_xml_tree(item_meta, tag) + + return cElementTree.Element(tag) + + +def _get_or_create_subelement(parent_element, tag): + """ + Given an element and tag name, either gets the direct child of parent with + that tag name or creates a new subelement with that tag and returns it. + + :param parent_element: The element to get or create the subelement from. + :param tag: The tag for the subelement. + """ + sub_element = parent_element.find(tag) + if sub_element is None: + sub_element = cElementTree.SubElement(parent_element, tag) + + return sub_element + + +def _make_pretty_string(tree_e): + # most of the parsing in this adapter is done with cElementTree because it + # is simpler and faster. However, the string representation it returns is + # far from elegant. Therefor we feed it through minidom to provide an xml + # with indentations. + string = cElementTree.tostring(tree_e, encoding="UTF-8", method="xml") + dom = minidom.parseString(string) + return dom.toprettyxml(indent=' ') + + +def marker_for_element(marker_element, rate): + """ + Creates an :class:`schema.Marker` for the provided element. + + :param marker_element: The XML element for the marker. + :param rate: The rate for the object the marker is attached to. + + :return: The :class:`schema.Marker` instance. + """ + # TODO: The spec doc indicates that in and out are required, but doesn't + # say they have to be locally specified, so is it possible they + # could be inherited? + marker_in = opentime.RationalTime( + float(marker_element.find("./in").text), rate + ) + marker_out_value = float(marker_element.find("./out").text) + if marker_out_value > 0: + marker_out = opentime.RationalTime( + marker_out_value, rate + ) + marker_duration = (marker_out - marker_in) + else: + marker_duration = opentime.RationalTime(rate=rate) + + marker_range = opentime.TimeRange(marker_in, marker_duration) + + md_dict = _xml_tree_to_dict(marker_element, {"in", "out", "name"}) + metadata = {META_NAMESPACE: md_dict} if md_dict else None + + return schema.Marker( + name=_name_from_element(marker_element), + marked_range=marker_range, + metadata=metadata + ) + + +def markers_from_element(element, context=None): + """ + Given an element, returns the list of markers attached to it. + + :param element: An element with one or more ``marker`` child elements. + :param context: The context for this element. + + :return: A :class:`list` of :class:`schema.Marker` instances attached + to the provided element. + """ + if context is not None: + local_context = context.context_pushing_element(element) + else: + local_context = _Context(element) + rate = _rate_from_context(local_context) + + return [marker_for_element(e, rate) for e in element.iterfind("./marker")] + + +class FCP7XMLParser: + """ + Implements parsing of an FCP XML file into an OTIO timeline. + + Parsing FCP XML elements include two concepts that require carrying state: + 1. Inheritance + 2. The id Attribute + + .. seealso:: https://developer.apple.com/library/archive/documentation/\ + AppleApplications/Reference/FinalCutPro_XML/Basics/Basics.html\ + #//apple_ref/doc/uid/TP30001154-TPXREF102 + + Inheritance is implemented using a _Context object that is pushed down + through layers of parsing. A given parsing method is passed the element to + parse into an otio object along with the context that element exists under + (e.x. a track element parsing method is given the track element and the + sequence context for that track). + + The id attribute dereferencing is handled through a lookup table stored on + parser instances and using the ``_derefed_`` methods to take an element and + find dereference elements. + """ + + _etree = None + """ The root etree for the FCP XML. """ + + _id_map = None + """ A mapping of id to the first element encountered with that id. """ + + def __init__(self, element_tree): + """ + Constructor, must be init with an xml etree. + """ + self._etree = element_tree + + self._id_map = {} + + def _derefed_element(self, element): + """ + Given an element, dereferences it by it's id attribute if needed. If + the element has an id attribute and it's our first time encountering + it, store the id. + """ + if element is None: + return element + + try: + elem_id = element.attrib["id"] + except KeyError: + return element + + return self._id_map.setdefault(elem_id, element) + + def _derefed_iterfind(self, element, path): + """ + Given an elemnt, finds elements with the provided path below and + returns an iterator of the dereferenced versions of those. + + :param element: The XML etree element. + :param path: The path to find subelements. + + :return: iterator of subelements dereferenced by id. + """ + return ( + self._derefed_element(e) for e in element.iterfind(path) + ) + + def top_level_sequences(self): + """" + Returns a list of timelines for the top-level sequences in the file. + """ + context = _Context() + + # If the tree has just sequences at the top level, this will catch them + top_iter = self._derefed_iterfind(self._etree, "./sequence") + + # If there is a project or bin at the top level, this should cath them + project_and_bin_iter = self._derefed_iterfind( + self._etree, ".//children/sequence" + ) + + # Make an iterator that will exhaust both the above + sequence_iter = itertools.chain(top_iter, project_and_bin_iter) + + return [self.timeline_for_sequence(s, context) for s in sequence_iter] + + def timeline_for_sequence(self, sequence_element, context): + """ + Returns either an :class`schema.Timeline` parsed from a sequence + element. + + :param sequence_element: The sequence element. + :param context: The context dictionary. + + :return: The appropriate OTIO object for the element. + """ + local_context = context.context_pushing_element(sequence_element) + + name = _name_from_element(sequence_element) + parsed_tags = {"name", "media", "marker", "duration"} + md_dict = _xml_tree_to_dict(sequence_element, parsed_tags) + + sequence_timecode = self._derefed_element( + sequence_element.find("./timecode") + ) + if sequence_timecode is not None: + seq_start_time = _time_from_timecode_element( + sequence_timecode, local_context + ) + else: + seq_start_time = None + + media_element = self._derefed_element(sequence_element.find("./media")) + if media_element is None: + tracks = None + else: + # Reach down into the media block and escalate metadata to the + # sequence + for media_type in media_element: + media_info_dict = _xml_tree_to_dict(media_type, {"track"}) + if media_info_dict: + media_dict = md_dict.setdefault( + "media", collections.OrderedDict() + ) + media_dict[media_type.tag] = media_info_dict + + tracks = self.stack_for_element(media_element, local_context) + tracks.name = name + + # TODO: Should we be parsing the duration tag and pad out a track with + # gap to match? + + timeline = schema.Timeline( + name=name, + global_start_time=seq_start_time, + metadata={META_NAMESPACE: md_dict} if md_dict else {}, + ) + timeline.tracks = tracks + + # Push the sequence markers onto the top stack + markers = markers_from_element(sequence_element, context) + timeline.tracks.markers.extend(markers) + + return timeline + + def stack_for_element(self, element, context): + """ + Given an element, parses out track information as a stack. + + :param element: The element under which to find the tracks (typically + a ``media`` element. + :param context: The current parser context. + + :return: A :class:`schema.Stack` of the tracks. + """ + # Determine the context + local_context = context.context_pushing_element(element) + + tracks = [] + media_type_elements = self._derefed_iterfind(element, "./") + for media_type_element in media_type_elements: + try: + track_kind = _track_kind_from_element(media_type_element) + except ValueError: + # Unexpected element + continue + + is_audio = (track_kind == schema.TrackKind.Audio) + track_elements = self._derefed_iterfind( + media_type_element, "./track" + ) + for track_element in track_elements: + if is_audio and not _is_primary_audio_channel(track_element): + continue + + tracks.append( + self.track_for_element( + track_element, track_kind, local_context + ) + ) + + markers = markers_from_element(element, context) + + stack = schema.Stack( + children=tracks, + markers=markers, + name=_name_from_element(element), + ) + + return stack + + def track_for_element(self, track_element, track_kind, context): + """ + Given a track element, constructs the OTIO track. + + :param track_element: The track XML element. + :param track_kind: The :class:`schema.TrackKind` for the track. + :param context: The context dict for this track. + """ + local_context = context.context_pushing_element(track_element) + name_element = track_element.find("./name") + track_name = (name_element.text if name_element is not None else None) + + timeline_item_tags = {"clipitem", "generatoritem", "transitionitem"} + + md_dict = _xml_tree_to_dict(track_element, timeline_item_tags) + track_metadata = {META_NAMESPACE: md_dict} if md_dict else None + + track = schema.Track( + name=track_name, + kind=track_kind, + metadata=track_metadata, + ) + + # Iterate through and parse track items + track_rate = _rate_from_context(local_context) + current_timeline_time = opentime.RationalTime(0, track_rate) + head_transition_element = None + for i, item_element in enumerate(track_element): + if item_element.tag not in timeline_item_tags: + continue + + item_element = self._derefed_element(item_element) + + # Do a lookahead to try and find the tail transition item + try: + tail_transition_element = track_element[i + 1] + if tail_transition_element.tag != "transitionitem": + tail_transition_element = None + else: + tail_transition_element = self._derefed_element( + tail_transition_element + ) + except IndexError: + tail_transition_element = None + + track_item, item_range = self.item_and_timing_for_element( + item_element, + head_transition_element, + tail_transition_element, + local_context, + ) + + # Insert gap between timeline cursor and the new item if needed. + if current_timeline_time < item_range.start_time: + gap_duration = (item_range.start_time - current_timeline_time) + gap_range = opentime.TimeRange( + duration=gap_duration.rescaled_to(track_rate) + ) + track.append(schema.Gap(source_range=gap_range)) + + # Add the item and advance the timeline cursor + track.append(track_item) + current_timeline_time = item_range.end_time_exclusive() + + # Stash the element for the next iteration if it's a transition + if item_element.tag == "transitionitem": + head_transition_element = item_element + + return track + + def media_reference_for_file_element(self, file_element, context): + """ + Given a file XML element, returns the + :class`schema.ExternalReference`. + + :param file_element: The file xml element. + :param context: The parent context dictionary. + + :return: An :class:`schema.ExternalReference`. + """ + local_context = context.context_pushing_element(file_element) + media_ref_rate = _rate_from_context(local_context) + + name = _name_from_element(file_element) + + # Get the full metadata + metadata_ignore_keys = {"duration", "name", "pathurl"} + md_dict = _xml_tree_to_dict(file_element, metadata_ignore_keys) + metadata_dict = {META_NAMESPACE: md_dict} if md_dict else None + + # Determine the file path + path_element = file_element.find("./pathurl") + if path_element is not None: + path = path_element.text + else: + path = None + + # Find the timing + timecode_element = file_element.find("./timecode") + if timecode_element is not None: + start_time = _time_from_timecode_element(timecode_element) + start_time = start_time.rescaled_to(media_ref_rate) + else: + start_time = opentime.RationalTime(0, media_ref_rate) + + duration_element = file_element.find("./duration") + if duration_element is not None: + duration = opentime.RationalTime( + float(duration_element.text), media_ref_rate + ) + available_range = opentime.TimeRange(start_time, duration) + elif timecode_element is not None: + available_range = opentime.TimeRange( + start_time, + opentime.RationalTime(0, media_ref_rate), + ) + else: + available_range = None + + if path is None: + media_reference = schema.MissingReference( + name=name, + available_range=available_range, + metadata=metadata_dict, + ) + else: + media_reference = schema.ExternalReference( + target_url=path, + available_range=available_range, + metadata=metadata_dict, + ) + media_reference.name = name + + return media_reference + + def media_reference_for_effect_element(self, effect_element): + """ + Given an effect element, returns a generator reference. + + :param effect_element: The effect for the generator. + + :return: An :class:`schema.GeneratorReference` instance. + """ + name = _name_from_element(effect_element) + md_dict = _xml_tree_to_dict(effect_element, {"name"}) + + return schema.GeneratorReference( + name=name, + metadata=({META_NAMESPACE: md_dict} if md_dict else None) + ) + + def item_and_timing_for_element( + self, item_element, head_transition, tail_transition, context + ): + """ + Given a track item, returns a tuple with the appropriate OpenTimelineIO + schema item as the first element and an + :class:`opentime.TimeRange`of theresolved timeline range the clip + occupies. + + :param item_element: The track item XML node. + :param head_transition: The xml element for the transition immediately + before or ``None``. + :param tail_transition: The xml element for the transition immediately + after or ``None``. + :param context: The context dictionary. + + :return: An :class:`core.Item` subclass instance and + :class:`opentime.TimeRange` for the item. + """ + parent_rate = _rate_from_context(context) + + # Establish the start/end time in the timeline + start_value = int(item_element.find("./start").text) + end_value = int(item_element.find("./end").text) + + if start_value == -1: + # determine based on the cut point of the head transition + start = _transition_cut_point(head_transition, context) + + # This offset is needed to determing how much to advance from the + # clip media's in time. Duration accounts for this offset for the + # out time. + transition_rate = _rate_from_context( + context.context_pushing_element(head_transition) + ) + start_offset = start - opentime.RationalTime( + int(head_transition.find('./start').text), transition_rate + ) + else: + start = opentime.RationalTime(start_value, parent_rate) + start_offset = opentime.RationalTime() + + if end_value == -1: + # determine based on the cut point of the tail transition + end = _transition_cut_point(tail_transition, context) + else: + end = opentime.RationalTime(end_value, parent_rate) + + item_range = opentime.TimeRange(start, (end - start)) + + # Get the metadata dictionary for the item + item_metadata_ignore_keys = { + "name", + "start", + "end", + "in", + "out", + "duration", + "file", + "marker", + "effect", + "rate", + "sequence", + } + metadata_dict = _xml_tree_to_dict( + item_element, item_metadata_ignore_keys + ) + + # deserialize the item + if item_element.tag in {"clipitem", "generatoritem"}: + item = self.clip_for_element( + item_element, item_range, start_offset, context + ) + elif item_element.tag == "transitionitem": + item = self.transition_for_element(item_element, context) + else: + name = "unknown-{}".format(item_element.tag) + item = core.Item(name=name, source_range=item_range) + + if metadata_dict: + item.metadata.setdefault(META_NAMESPACE, {}).update(metadata_dict) + + return (item, item_range) + + def clip_for_element( + self, clipitem_element, item_range, start_offset, context + ): + """ + Given a clipitem xml element, returns an :class:`schema.Clip`. + + :param clipitem_element: The element to create a clip for. + :param item_range: The time range in the timeline the clip occupies. + :param start_offset: The amount by which the ``in`` time of the clip + source should be advanced (usually due to a transition). + :param context: The parent context for the clip. + + :return: The :class:`schema.Clip` instance. + """ + local_context = context.context_pushing_element(clipitem_element) + + name = _name_from_element(clipitem_element) + + file_element = self._derefed_element(clipitem_element.find("./file")) + sequence_element = self._derefed_element( + clipitem_element.find("./sequence") + ) + if clipitem_element.tag == "generatoritem": + generator_effect_element = clipitem_element.find( + "./effect[effecttype='generator']" + ) + else: + generator_effect_element = None + + media_start_time = opentime.RationalTime() + if sequence_element is not None: + item = self.stack_for_element(sequence_element, local_context) + # TODO: is there an applicable media start time we should be + # using from nested sequences? + elif file_element is not None or generator_effect_element is not None: + if file_element is not None: + media_reference = self.media_reference_for_file_element( + file_element, local_context + ) + # See if there is a start offset + timecode_element = file_element.find("./timecode") + if timecode_element is not None: + media_start_time = _time_from_timecode_element( + timecode_element + ) + elif generator_effect_element is not None: + media_reference = self.media_reference_for_effect_element( + generator_effect_element + ) + + item = schema.Clip( + name=name, + media_reference=media_reference, + ) + else: + raise TypeError( + 'Type of clip item is not supported {}'.format( + _element_identification_string(clipitem_element) + ) + ) + + # Add the markers + markers = markers_from_element(clipitem_element, context) + item.markers.extend(markers) + + # Find the in time (source time relative to media start) + clip_rate = _rate_from_context(local_context) + in_value = float(clipitem_element.find('./in').text) + in_time = opentime.RationalTime(in_value, clip_rate) + + # Offset the "in" time by the start offset of the media + soure_start_time = in_time + media_start_time + start_offset + duration = item_range.duration + + # Source Range is the item range expressed in the clip's rate (for now) + source_range = opentime.TimeRange( + soure_start_time.rescaled_to(clip_rate), + duration.rescaled_to(clip_rate), + ) + + item.source_range = source_range + + # Parse the filters + filter_iter = self._derefed_iterfind(clipitem_element, "./filter") + for filter_element in filter_iter: + item.effects.append( + self.effect_from_filter_element(filter_element) + ) + + return item + + def effect_from_filter_element(self, filter_element): + """ + Given a filter element, creates an :class:`schema.Effect`. + + :param filter_element: The ``filter`` element containing the effect. + + :return: The effect instance. + """ + effect_element = filter_element.find("./effect") + + if effect_element is None: + raise ValueError( + "could not find effect in filter: {}".format(filter_element) + ) + + name = effect_element.find("./name").text + + effect_metadata = _xml_tree_to_dict(effect_element, {"name"}) + + return schema.Effect( + name, + metadata={META_NAMESPACE: effect_metadata}, + ) + + def transition_for_element(self, item_element, context): + """ + Creates an OTIO transition for the provided transition element. + + :param item_element: The element to create a transition for. + :param context: The parent context for the element. + + :return: The :class:`schema.Transition` instance. + """ + # start and end times are in the parent's rate + rate = _rate_from_context(context) + start = opentime.RationalTime( + int(item_element.find('./start').text), + rate + ) + end = opentime.RationalTime( + int(item_element.find('./end').text), + rate + ) + cut_point = _transition_cut_point(item_element, context) + + transition = schema.Transition( + name=item_element.find('./effect/name').text, + transition_type=schema.TransitionTypes.SMPTE_Dissolve, + in_offset=cut_point - start, + out_offset=end - cut_point, + ) + + return transition + + +# ------------------------ +# building single track +# ------------------------ + + +def _backreference_for_item(item, tag, br_map): + """ + Given an item, determines what the id in the backreference map should be. + If the item is already tracked in the map, it will be returned, otherwise + a new id will be minted. + + .. note:: ``br_map`` may be mutated by this function. ``br_map`` is + intended to be an opaque data structure and only accessed through this + function, the structure of data in br_map may change. + + :param item: The :class:`core.SerializableObject` to create an id for. + :param tag: The tag name that will be used for object in xml. + :param br_map: The dictionary containing backreference information + generated so far. + + :return: A 2-tuple of (id_string, is_new_id) where the ``id_string`` is + the value for the xml id attribute and ``is_new_id`` is ``True`` when + this is the first time that id was encountered. + """ + # br_map is structured as a dictionary with tags as keys, and dictionaries + # of hash to id int as values. + + def id_string(id_int): + return "{}-{}".format(tag, id_int) + + # Determine how to uniquely identify the referenced item + if isinstance(item, schema.ExternalReference): + item_hash = hash(str(item.target_url)) + else: + # TODO: This may become a performance issue. It means that every + # non-ref object is serialized to json and hashed each time it's + # encountered. + item_hash = hash( + core.json_serializer.serialize_json_to_string(item) + ) + + is_new_id = False + item_id = br_map.get(tag, {}).get(item_hash) + if item_id is not None: + return (id_string(item_id), is_new_id) + + # This is a new id, figure out what it should be. + is_new_id = True + + # Attempt to preserve the ID from the input metadata. + preferred_id = None + orig_id_string = item.metadata.get(META_NAMESPACE, {}).get("@id") + if orig_id_string is not None: + orig_id_match = ID_RE.match(orig_id_string) + if orig_id_match is not None: + match_groups = orig_id_match.groupdict() + orig_tagname = match_groups["tag"] + if orig_tagname == tag: + preferred_id = int(match_groups["id"]) + + # Generate an id by finding the lowest value in a contiguous range not + # colliding with an existing value + tag_id_map = br_map.setdefault(tag, {}) + existing_ids = set(tag_id_map.values()) + if preferred_id is not None and preferred_id not in existing_ids: + item_id = preferred_id + else: + # Make a range from 1 including the ID after the largest assigned + # (hence the +2 since range is non-inclusive on the upper bound) + max_assigned_id = max(existing_ids) if existing_ids else 0 + max_possible_id = (max_assigned_id + 2) + possible_ids = set(range(1, max_possible_id)) + + # Select the lowest unassigned ID + item_id = min(possible_ids.difference(existing_ids)) + + # Store the created id + tag_id_map[item_hash] = item_id + + return (id_string(item_id), is_new_id) + + +def _backreference_build(tag): + """ + A decorator for functions creating XML elements to implement the id system + described in FCP XML. + + This wrapper determines if the otio item is equivalent to one encountered + before with the provided tag name. If the item hasn't been encountered then + the wrapped function will be invoked and the XML element from that function + will have the ``id`` attribute set and be stored in br_map. + If the item is equivalent to a previously provided item, the wrapped + function won't be invoked and a simple tag with the previous instance's id + will be returned instead. + + The wrapped function must: + - Have the otio item as the first positional argument. + - Have br_map (backreference map, a dictionary) as the last positional + arg. br_map stores the state for encountered items. + + :param tag: The xml tag of the element the wrapped function generates. + """ + # We can also encode these back-references if an item is accessed multiple + # times. To do this we store an id attribute on the element. For back- + # references we then only need to return an empty element of that type with + # the id we logged before + + def singleton_decorator(func): + @functools.wraps(func) + def wrapper(item, *args, **kwargs): + br_map = args[-1] + + item_id, id_is_new = _backreference_for_item(item, tag, br_map) + + # if the item exists in the map already, we should use the + # abbreviated XML element referring to the original + if not id_is_new: + return cElementTree.Element(tag, id=item_id) + + # This is the first time for this unique item, it needs it's full + # XML. Get the element generated by the wrapped function and add + # the id attribute. + elem = func(item, *args, **kwargs) + elem.attrib["id"] = item_id + + return elem + + return wrapper + + return singleton_decorator + + +def _append_new_sub_element(parent, tag, attrib=None, text=None): + """ + Creates a sub-element with the provided tag, attributes, and text. + + This is a convenience because the :class:`SubElement` constructor does not + provide the ability to set ``text``. + + :param parent: The parent element. + :param tag: The tag string for the element. + :param attrib: An optional dictionary of attributes for the element. + :param text: Optional text value for the element. + + :return: The new XML element. + """ + elem = cElementTree.SubElement(parent, tag, **attrib or {}) + if text is not None: + elem.text = text + + return elem + + +def _build_rate(fps): + """ + Given a framerate, makes a ``rate`` xml tree. + + :param fps: The framerate. + :return: The fcp xml ``rate`` tree. + """ + rate = math.ceil(fps) + + rate_e = cElementTree.Element('rate') + _append_new_sub_element(rate_e, 'timebase', text=str(int(rate))) + _append_new_sub_element( + rate_e, + 'ntsc', + text='FALSE' if rate == fps else 'TRUE' + ) + return rate_e + + +def _build_timecode(time, fps, drop_frame=False, additional_metadata=None): + """ + Makes a timecode xml element tree. + + .. warning:: The drop_frame parameter is currently ignored and + auto-determined by rate. This is because the underlying otio timecode + conversion assumes DFTC based on rate. + + :param time: The :class:`opentime.RationalTime` for the timecode. + :param fps: The framerate for the timecode. + :param drop_frame: If True, generates drop-frame timecode. + :param additional_metadata: A dictionary with other metadata items like + ``field``, ``reel``, ``source``, and ``format``. It is assumed this + dictionary is of the form generated by :func:`_xml_tree_to_dict` when + the file was read originally. + + :return: The ``timecode`` element. + """ + if additional_metadata: + # Only allow legal child items for the timecode element + filtered = { + k: v for k, v in additional_metadata.items() + if k in {"field", "reel", "source", "format"} + } + tc_element = _dict_to_xml_tree(filtered, "timecode") + else: + tc_element = cElementTree.Element("timecode") + + tc_element.append(_build_rate(fps)) + rate_is_not_ntsc = (tc_element.find('./rate/ntsc').text == "FALSE") + if drop_frame and rate_is_not_ntsc: + tc_fps = fps * (1000 / 1001.0) + else: + tc_fps = fps + + # Get the time values + tc_time = opentime.RationalTime(time.value_rescaled_to(fps), tc_fps) + tc_string = opentime.to_timecode(tc_time, tc_fps, drop_frame) + + _append_new_sub_element(tc_element, "string", text=tc_string) + + frame_number = int(round(time.value)) + _append_new_sub_element( + tc_element, "frame", text="{:.0f}".format(frame_number) + ) + + drop_frame = (";" in tc_string) + display_format = "DF" if drop_frame else "NDF" + _append_new_sub_element(tc_element, "displayformat", text=display_format) + + return tc_element + + +def _build_item_timings( + item_e, + item, + timeline_range, + transition_offsets, + timecode +): + # source_start is absolute time taking into account the timecode of the + # media. But xml regards the source in point from the start of the media. + # So we subtract the media timecode. + item_rate = item.source_range.start_time.rate + source_start = (item.source_range.start_time - timecode) + source_start = source_start.rescaled_to(item_rate) + + source_end = (item.source_range.end_time_exclusive() - timecode) + source_end = source_end.rescaled_to(item_rate) + + start = '{:.0f}'.format(timeline_range.start_time.value) + end = '{:.0f}'.format(timeline_range.end_time_exclusive().value) + + item_e.append(_build_rate(item_rate)) + + if transition_offsets[0] is not None: + start = '-1' + source_start -= transition_offsets[0] + if transition_offsets[1] is not None: + end = '-1' + source_end += transition_offsets[1] + + _append_new_sub_element( + item_e, 'duration', + text='{:.0f}'.format(item.source_range.duration.value) + ) + _append_new_sub_element(item_e, 'start', text=start) + _append_new_sub_element(item_e, 'end', text=end) + _append_new_sub_element( + item_e, + 'in', + text='{:.0f}'.format(source_start.value) + ) + _append_new_sub_element( + item_e, + 'out', + text='{:.0f}'.format(source_end.value) + ) + + +@_backreference_build('file') +def _build_empty_file(media_ref, parent_range, br_map): + file_e = _element_with_item_metadata("file", media_ref) + _append_new_sub_element(file_e, "name", text=media_ref.name) + + if media_ref.available_range is not None: + available_range = media_ref.available_range + else: + available_range = opentime.TimeRange( + opentime.RationalTime(0, parent_range.start_time.rate), + parent_range.duration, + ) + + ref_rate = available_range.start_time.rate + file_e.append(_build_rate(ref_rate)) + + # Only provide a duration if one came from the media, don't invent one. + # For example, Slugs have no duration specified. + if media_ref.available_range: + duration = available_range.duration.rescaled_to(ref_rate) + _append_new_sub_element( + file_e, + 'duration', + text='{:.0f}'.format(duration.value), + ) + + # timecode + ref_tc_metadata = media_ref.metadata.get(META_NAMESPACE, {}).get( + "timecode" + ) + tc_element = _build_timecode_from_metadata( + available_range.start_time, ref_tc_metadata + ) + file_e.append(tc_element) + + file_media_e = _get_or_create_subelement(file_e, "media") + if file_media_e.find("video") is None: + _append_new_sub_element(file_media_e, "video") + + return file_e + + +@_backreference_build('file') +def _build_file(media_reference, br_map): + file_e = _element_with_item_metadata("file", media_reference) + + available_range = media_reference.available_range + url_path = _url_to_path(media_reference.target_url) + + file_name = ( + media_reference.name if media_reference.name + else os.path.basename(url_path) + ) + _append_new_sub_element(file_e, 'name', text=file_name) + _append_new_sub_element(file_e, 'pathurl', text=media_reference.target_url) + + # timing info + file_e.append(_build_rate(available_range.start_time.rate)) + _append_new_sub_element( + file_e, 'duration', + text='{:.0f}'.format(available_range.duration.value) + ) + + # timecode + ref_tc_metadata = media_reference.metadata.get(META_NAMESPACE, {}).get( + "timecode" + ) + tc_element = _build_timecode_from_metadata( + available_range.start_time, ref_tc_metadata + ) + file_e.append(tc_element) + + # we need to flag the file reference with the content types, otherwise it + # will not get recognized + # TODO: We should use a better method for this. Perhaps pre-walk the + # timeline and find all the track kinds this media is present in? + if not file_e.find("media"): + file_media_e = _get_or_create_subelement(file_e, "media") + + audio_exts = {'.wav', '.aac', '.mp3', '.aif', '.aiff', '.m4a'} + has_video = (os.path.splitext(url_path)[1].lower() not in audio_exts) + if has_video and file_media_e.find("video") is None: + _append_new_sub_element(file_media_e, "video") + + # TODO: This is assuming all files have an audio track. Not sure what + # the implications of that are. + if file_media_e.find("audio") is None: + _append_new_sub_element(file_media_e, "audio") + + return file_e + + +def _build_transition_item( + transition_item, + timeline_range, + transition_offsets, + br_map, +): + transition_e = _element_with_item_metadata( + "transitionitem", transition_item + ) + _append_new_sub_element( + transition_e, + 'start', + text='{:.0f}'.format(timeline_range.start_time.value) + ) + _append_new_sub_element( + transition_e, + 'end', + text='{:.0f}'.format(timeline_range.end_time_exclusive().value) + ) + + # Only add an alignment if it didn't already come in from the metadata dict + if transition_e.find("alignment") is None: + # default center aligned + alignment = "center" + if not transition_item.in_offset.value: + alignment = 'start-black' + elif not transition_item.out_offset.value: + alignment = 'end-black' + + _append_new_sub_element(transition_e, 'alignment', text=alignment) + # todo support 'start' and 'end' alignment + + transition_e.append(_build_rate(timeline_range.start_time.rate)) + + # Only add an effect if it didn't already come in from the metadata dict + if not transition_e.find("./effect"): + try: + effectid = transition_item.metadata[META_NAMESPACE]["effectid"] + except KeyError: + effectid = "Cross Dissolve" + + effect_e = _append_new_sub_element(transition_e, 'effect') + _append_new_sub_element(effect_e, 'name', text=transition_item.name) + _append_new_sub_element(effect_e, 'effectid', text=effectid) + _append_new_sub_element(effect_e, 'effecttype', text='transition') + _append_new_sub_element(effect_e, 'mediatype', text='video') + + return transition_e + + +@_backreference_build("clipitem") +def _build_clip_item_without_media( + clip_item, + timeline_range, + transition_offsets, + br_map, +): + # TODO: Does this need to be a separate function or could it be unified + # with _build_clip_item? + clip_item_e = _element_with_item_metadata("clipitem", clip_item) + if "frameBlend" not in clip_item_e.attrib: + clip_item_e.attrib["frameBlend"] = "FALSE" + + if clip_item.media_reference.available_range: + media_start_time = clip_item.media_reference.available_range.start_time + else: + media_start_time = opentime.RationalTime( + 0, timeline_range.start_time.rate + ) + + _append_new_sub_element(clip_item_e, 'name', text=clip_item.name) + clip_item_e.append( + _build_empty_file( + clip_item.media_reference, timeline_range, br_map + ) + ) + clip_item_e.extend([_build_marker(m) for m in clip_item.markers]) + + _build_item_timings( + clip_item_e, + clip_item, + timeline_range, + transition_offsets, + media_start_time, + ) + + return clip_item_e + + +@_backreference_build("clipitem") +def _build_clip_item(clip_item, timeline_range, transition_offsets, br_map): + is_generator = isinstance( + clip_item.media_reference, schema.GeneratorReference + ) + + tagname = "generatoritem" if is_generator else "clipitem" + clip_item_e = _element_with_item_metadata(tagname, clip_item) + if "frameBlend" not in clip_item_e.attrib: + clip_item_e.attrib["frameBlend"] = "FALSE" + + if is_generator: + clip_item_e.append(_build_generator_effect(clip_item, br_map)) + else: + clip_item_e.append(_build_file(clip_item.media_reference, br_map)) + + # set the clip name from the media reference if not defined on the clip + if clip_item.name is not None: + name = clip_item.name + elif is_generator: + name = clip_item.media_reference.name + else: + url_path = _url_to_path(clip_item.media_reference.target_url) + name = os.path.basename(url_path) + + _append_new_sub_element(clip_item_e, 'name', text=name) + + if clip_item.media_reference.available_range: + clip_item_e.append( + _build_rate(clip_item.source_range.start_time.rate) + ) + clip_item_e.extend(_build_marker(m) for m in clip_item.markers) + + if clip_item.media_reference.available_range: + timecode = clip_item.media_reference.available_range.start_time + else: + timecode = opentime.RationalTime( + 0, clip_item.source_range.start_time.rate + ) + + _build_item_timings( + clip_item_e, + clip_item, + timeline_range, + transition_offsets, + timecode + ) + + return clip_item_e + + +def _build_generator_effect(clip_item, br_map): + """ + Builds an effect element for the generator ref on the provided clip item. + + :param clip_item: a clip with a :class:`schema.GeneratorReference` as + its ``media_reference``. + :param br_map: The backreference map. + """ + # Since we don't support effects in a standard way, just try and build + # based on the metadata provided at deserialization so we can roundtrip + generator_ref = clip_item.media_reference + try: + fcp_xml_effect_info = generator_ref.metadata[META_NAMESPACE] + except KeyError: + return _build_empty_file( + generator_ref, + clip_item.source_range, + br_map, + ) + + # Get the XML Tree built from the metadata + effect_element = _dict_to_xml_tree(fcp_xml_effect_info, "effect") + + # Validate the metadata and make sure it contains the required elements + for required in ("effectid", "effecttype", "mediatype", "effectcategory"): + if effect_element.find(required) is None: + return _build_empty_file( + generator_ref, + clip_item.source_range, + br_map, + ) + + # Add the name + _append_new_sub_element(effect_element, "name", text=generator_ref.name) + + return effect_element + + +@_backreference_build("clipitem") +def _build_track_item(track, timeline_range, transition_offsets, br_map): + clip_item_e = _element_with_item_metadata("clipitem", track) + if "frameBlend" not in clip_item_e.attrib: + clip_item_e.attrib["frameBlend"] = "FALSE" + + _append_new_sub_element( + clip_item_e, + 'name', + text=os.path.basename(track.name) + ) + + track_e = _build_sequence_for_stack(track, timeline_range, br_map) + + clip_item_e.append(_build_rate(track.source_range.start_time.rate)) + clip_item_e.extend([_build_marker(m) for m in track.markers]) + clip_item_e.append(track_e) + timecode = opentime.RationalTime(0, timeline_range.start_time.rate) + + _build_item_timings( + clip_item_e, + track, + timeline_range, + transition_offsets, + timecode + ) + + return clip_item_e + + +def _build_item(item, timeline_range, transition_offsets, br_map): + if isinstance(item, schema.Transition): + return _build_transition_item( + item, + timeline_range, + transition_offsets, + br_map + ) + elif isinstance(item, schema.Clip): + if isinstance( + item.media_reference, + schema.MissingReference + ): + return _build_clip_item_without_media( + item, + timeline_range, + transition_offsets, + br_map + ) + else: + return _build_clip_item( + item, + timeline_range, + transition_offsets, + br_map + ) + elif isinstance(item, schema.Stack): + return _build_track_item( + item, + timeline_range, + transition_offsets, + br_map + ) + else: + raise ValueError('Unsupported item: ' + str(item)) + + +def _build_top_level_track(track, track_rate, br_map): + track_e = _element_with_item_metadata("track", track) + + for n, item in enumerate(track): + if isinstance(item, schema.Gap): + continue + + transition_offsets = [None, None] + previous_item = track[n - 1] if n > 0 else None + next_item = track[n + 1] if n + 1 < len(track) else None + if not isinstance(item, schema.Transition): + # find out if this item has any neighboring transition + if isinstance(previous_item, schema.Transition): + if previous_item.out_offset.value: + transition_offsets[0] = previous_item.in_offset + else: + transition_offsets[0] = None + if isinstance(next_item, schema.Transition): + if next_item.in_offset.value: + transition_offsets[1] = next_item.out_offset + else: + transition_offsets[1] = None + + timeline_range = track.range_of_child_at_index(n) + timeline_range = opentime.TimeRange( + timeline_range.start_time.rescaled_to(track_rate), + timeline_range.duration.rescaled_to(track_rate) + ) + track_e.append( + _build_item(item, timeline_range, transition_offsets, br_map) + ) + + return track_e + + +def _build_marker(marker): + marker_e = _element_with_item_metadata("marker", marker) + + marked_range = marker.marked_range + + _append_new_sub_element(marker_e, 'name', text=marker.name) + _append_new_sub_element( + marker_e, 'in', + text='{:.0f}'.format(marked_range.start_time.value) + ) + _append_new_sub_element(marker_e, 'out', text='-1') + + return marker_e + + +def _build_timecode_from_metadata(time, tc_metadata=None): + """ + Makes a timecode element with the given time and (if available) + ```timecode`` metadata stashed on input. + + :param time: The :class:`opentime.RationalTime` to encode. + :param tc_metadata: The xml dict for the ``timecode`` element populated + on read. + + :return: A timecode element. + """ + if tc_metadata is None: + tc_metadata = {} + + try: + # Parse the rate in the preserved metadata, if available + tc_rate = _rate_for_element( + _dict_to_xml_tree(tc_metadata["rate"], "rate") + ) + except KeyError: + # Default to the rate in the start time + tc_rate = time.rate + + drop_frame = (tc_metadata.get("displayformat", "NDF") == "DF") + + return _build_timecode( + time, + tc_rate, + drop_frame, + additional_metadata=tc_metadata, + ) + + +@_backreference_build('sequence') +def _build_sequence_for_timeline(timeline, timeline_range, br_map): + sequence_e = _element_with_item_metadata("sequence", timeline) + + _add_stack_elements_to_sequence( + timeline.tracks, sequence_e, timeline_range, br_map + ) + + # In the case of timelines, use the timeline name rather than the stack + # name. + if timeline.name: + sequence_e.find('./name').text = timeline.name + + # Add the sequence global start + if timeline.global_start_time is not None: + seq_tc_metadata = timeline.metadata.get(META_NAMESPACE, {}).get( + "timecode" + ) + tc_element = _build_timecode_from_metadata( + timeline.global_start_time, seq_tc_metadata + ) + sequence_e.append(tc_element) + + return sequence_e + + +@_backreference_build('sequence') +def _build_sequence_for_stack(stack, timeline_range, br_map): + sequence_e = _element_with_item_metadata("sequence", stack) + + _add_stack_elements_to_sequence(stack, sequence_e, timeline_range, br_map) + + return sequence_e + + +def _add_stack_elements_to_sequence(stack, sequence_e, timeline_range, br_map): + _append_new_sub_element(sequence_e, 'name', text=stack.name) + _append_new_sub_element( + sequence_e, 'duration', + text='{:.0f}'.format(timeline_range.duration.value) + ) + sequence_e.append(_build_rate(timeline_range.start_time.rate)) + track_rate = timeline_range.start_time.rate + + media_e = _get_or_create_subelement(sequence_e, "media") + video_e = _get_or_create_subelement(media_e, 'video') + audio_e = _get_or_create_subelement(media_e, 'audio') + + for track in stack: + track_elements = _build_top_level_track(track, track_rate, br_map) + if track.kind == schema.TrackKind.Video: + video_e.append(track_elements) + elif track.kind == schema.TrackKind.Audio: + audio_e.append(track_elements) + + for marker in stack.markers: + sequence_e.append(_build_marker(marker)) + + +def _build_collection(collection, br_map): + tracks = [] + for item in collection: + if not isinstance(item, schema.Timeline): + continue + + timeline_range = opentime.TimeRange( + start_time=item.global_start_time, + duration=item.duration() + ) + tracks.append( + _build_sequence_for_timeline(item, timeline_range, br_map) + ) + + return tracks + + +# -------------------- +# adapter requirements +# -------------------- + +def read_from_string(input_str): + tree = cElementTree.fromstring(input_str) + + parser = FCP7XMLParser(tree) + sequences = parser.top_level_sequences() + + if len(sequences) == 1: + return sequences[0] + elif len(sequences) > 1: + return schema.SerializableCollection( + name="Sequences", + children=sequences, + ) + else: + raise ValueError('No top-level sequences found') + + +def write_to_string(input_otio): + tree_e = cElementTree.Element('xmeml', version="4") + project_e = _append_new_sub_element(tree_e, 'project') + _append_new_sub_element(project_e, 'name', text=input_otio.name) + children_e = _append_new_sub_element(project_e, 'children') + + br_map = collections.defaultdict(dict) + + if isinstance(input_otio, schema.Timeline): + timeline_range = opentime.TimeRange( + start_time=input_otio.global_start_time, + duration=input_otio.duration() + ) + children_e.append( + _build_sequence_for_timeline( + input_otio, timeline_range, br_map + ) + ) + elif isinstance(input_otio, schema.SerializableCollection): + children_e.extend( + _build_collection(input_otio, br_map) + ) + + return _make_pretty_string(tree_e) diff --git a/openpype/vendor/python/python_2/opentimelineio/adapters/otio_json.py b/openpype/vendor/python/python_2/opentimelineio/adapters/otio_json.py new file mode 100644 index 0000000000..66b8db2904 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/adapters/otio_json.py @@ -0,0 +1,48 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""This adapter lets you read and write native .otio files""" + +from .. import ( + core +) + + +# @TODO: Implement out of process plugins that hand around JSON + + +def read_from_file(filepath): + return core.deserialize_json_from_file(filepath) + + +def read_from_string(input_str): + return core.deserialize_json_from_string(input_str) + + +def write_to_string(input_otio): + return core.serialize_json_to_string(input_otio) + + +def write_to_file(input_otio, filepath): + return core.serialize_json_to_file(input_otio, filepath) diff --git a/openpype/vendor/python/python_2/opentimelineio/algorithms/__init__.py b/openpype/vendor/python/python_2/opentimelineio/algorithms/__init__.py new file mode 100644 index 0000000000..e211598bb3 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/algorithms/__init__.py @@ -0,0 +1,44 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Algorithms for OTIO objects.""" + +# flake8: noqa +from .track_algo import ( + track_trimmed_to_range, + track_with_expanded_transitions +) + +from .stack_algo import ( + flatten_stack, + top_clip_at_time, +) + +from .filter import ( + filtered_composition, + filtered_with_sequence_context +) +from .timeline_algo import ( + timeline_trimmed_to_range +) diff --git a/openpype/vendor/python/python_2/opentimelineio/algorithms/filter.py b/openpype/vendor/python/python_2/opentimelineio/algorithms/filter.py new file mode 100644 index 0000000000..8f9e2ed41b --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/algorithms/filter.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Algorithms for filtering OTIO files. """ + +import copy + +from .. import ( + schema +) + + +def _is_in(thing, container): + return any(thing is item for item in container) + + +def _isinstance_in(child, typelist): + return any(isinstance(child, t) for t in typelist) + + +def filtered_composition( + root, + unary_filter_fn, + types_to_prune=None, +): + """Filter a deep copy of root (and children) with unary_filter_fn. + + types_to_prune:: tuple of types, example: (otio.schema.Gap,...) + + 1. Make a deep copy of root + 2. Starting with root, perform a depth first traversal + 3. For each item (including root): + a. if types_to_prune is not None and item is an instance of a type + in types_to_prune, prune it from the copy, continue. + b. Otherwise, pass the copy to unary_filter_fn. If unary_filter_fn: + I. returns an object: add it to the copy, replacing original + II. returns a tuple: insert it into the list, replacing original + III. returns None: prune it + 4. If an item is pruned, do not traverse its children + 5. Return the new deep copy. + + EXAMPLE 1 (filter): + If your unary function is: + def fn(thing): + if thing.name == B: + return thing' # some transformation of B + else: + return thing + + If you have a track: [A,B,C] + + filtered_composition(track, fn) => [A,B',C] + + EXAMPLE 2 (prune): + If your unary function is: + def fn(thing): + if thing.name == B: + return None + else: + return thing + + filtered_composition(track, fn) => [A,C] + + EXAMPLE 3 (expand): + If your unary function is: + def fn(thing): + if thing.name == B: + return tuple(B_1,B_2,B_3) + else: + return thing + + filtered_composition(track, fn) => [A,B_1,B_2,B_3,C] + + EXAMPLE 4 (prune gaps): + track :: [Gap, A, Gap] + filtered_composition( + track, lambda _:_, types_to_prune=(otio.schema.Gap,)) => [A] + """ + + # deep copy everything + mutable_object = copy.deepcopy(root) + + prune_list = set() + + header_list = [mutable_object] + + if isinstance(mutable_object, schema.Timeline): + header_list.append(mutable_object.tracks) + + iter_list = header_list + list(mutable_object.each_child()) + + for child in iter_list: + if _safe_parent(child) is not None and _is_in(child.parent(), prune_list): + prune_list.add(child) + continue + + parent = None + child_index = None + if _safe_parent(child) is not None: + child_index = child.parent().index(child) + parent = child.parent() + del child.parent()[child_index] + + # first try to prune + if (types_to_prune and _isinstance_in(child, types_to_prune)): + result = None + # finally call the user function + else: + result = unary_filter_fn(child) + + if child is mutable_object: + mutable_object = result + + if result is None: + prune_list.add(child) + continue + + if type(result) is not tuple: + result = [result] + + if parent is not None: + parent[child_index:child_index] = result + + return mutable_object + + +def _safe_parent(child): + if hasattr(child, 'parent'): + return child.parent() + return None + + +def filtered_with_sequence_context( + root, + reduce_fn, + types_to_prune=None, +): + """Filter a deep copy of root (and children) with reduce_fn. + + reduce_fn::function(previous_item, current, next_item) (see below) + types_to_prune:: tuple of types, example: (otio.schema.Gap,...) + + 1. Make a deep copy of root + 2. Starting with root, perform a depth first traversal + 3. For each item (including root): + a. if types_to_prune is not None and item is an instance of a type + in types_to_prune, prune it from the copy, continue. + b. Otherwise, pass (prev, copy, and next) to reduce_fn. If reduce_fn: + I. returns an object: add it to the copy, replacing original + II. returns a tuple: insert it into the list, replacing original + III. returns None: prune it + + ** note that reduce_fn is always passed objects from the original + deep copy, not what prior calls return. See below for examples + 4. If an item is pruned, do not traverse its children + 5. Return the new deep copy. + + EXAMPLE 1 (filter): + >>> track = [A,B,C] + >>> def fn(prev_item, thing, next_item): + ... if prev_item.name == A: + ... return D # some new clip + ... else: + ... return thing + >>> filtered_with_sequence_context(track, fn) => [A,D,C] + + order of calls to fn: + fn(None, A, B) => A + fn(A, B, C) => D + fn(B, C, D) => C # !! note that it was passed B instead of D. + + EXAMPLE 2 (prune): + >>> track = [A,B,C] + >>> def fn(prev_item, thing, next_item): + ... if prev_item.name == A: + ... return None # prune the clip + ... else: + ... return thing + >>> filtered_with_sequence_context(track, fn) => [A,C] + + order of calls to fn: + fn(None, A, B) => A + fn(A, B, C) => None + fn(B, C, D) => C # !! note that it was passed B instead of D. + + EXAMPLE 3 (expand): + >>> def fn(prev_item, thing, next_item): + ... if prev_item.name == A: + ... return (D, E) # tuple of new clips + ... else: + ... return thing + >>> filtered_with_sequence_context(track, fn) => [A, D, E, C] + + the order of calls to fn will be: + fn(None, A, B) => A + fn(A, B, C) => (D, E) + fn(B, C, D) => C # !! note that it was passed B instead of D. + """ + + # deep copy everything + mutable_object = copy.deepcopy(root) + + prune_list = set() + + header_list = [mutable_object] + + if isinstance(mutable_object, schema.Timeline): + header_list.append(mutable_object.tracks) + + iter_list = header_list + list(mutable_object.each_child()) + + # expand to include prev, next when appropriate + expanded_iter_list = [] + for child in iter_list: + if _safe_parent(child) and isinstance(child.parent(), schema.Track): + prev_item, next_item = child.parent().neighbors_of(child) + expanded_iter_list.append((prev_item, child, next_item)) + else: + expanded_iter_list.append((None, child, None)) + + for prev_item, child, next_item in expanded_iter_list: + if _safe_parent(child) is not None and _is_in(child.parent(), prune_list): + prune_list.add(child) + continue + + parent = None + child_index = None + if _safe_parent(child) is not None: + child_index = child.parent().index(child) + parent = child.parent() + del child.parent()[child_index] + + # first try to prune + if types_to_prune and _isinstance_in(child, types_to_prune): + result = None + # finally call the user function + else: + result = reduce_fn(prev_item, child, next_item) + + if child is mutable_object: + mutable_object = result + + if result is None: + prune_list.add(child) + continue + + if type(result) is not tuple: + result = [result] + + if parent is not None: + parent[child_index:child_index] = result + + return mutable_object diff --git a/openpype/vendor/python/python_2/opentimelineio/algorithms/stack_algo.py b/openpype/vendor/python/python_2/opentimelineio/algorithms/stack_algo.py new file mode 100644 index 0000000000..cdb6424b46 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/algorithms/stack_algo.py @@ -0,0 +1,138 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +__doc__ = """ Algorithms for stack objects. """ + +import copy + +from .. import ( + schema, + opentime, +) +from . import ( + track_algo +) + + +def top_clip_at_time(in_stack, t): + """Return the topmost visible child that overlaps with time t. + + Example: + tr1: G1, A, G2 + tr2: [B------] + G1, and G2 are gaps, A and B are clips. + + If t is within A, a will be returned. If t is within G1 or G2, B will be + returned. + """ + + # ensure that it only runs on stacks + if not isinstance(in_stack, schema.Stack): + raise ValueError( + "Argument in_stack must be of type otio.schema.Stack, " + "not: '{}'".format( + type(in_stack) + ) + ) + + # build a range to use the `each_child`method. + search_range = opentime.TimeRange( + start_time=t, + # 0 duration so we are just sampling a point in time. + # XXX Should this duration be equal to the length of one sample? + # opentime.RationalTime(1, rate)? + duration=opentime.RationalTime(0, t.rate) + ) + + # walk through the children of the stack in reverse order. + for track in reversed(in_stack): + valid_results = [] + if hasattr(track, "each_child"): + valid_results = list( + c for c in track.each_clip(search_range, shallow_search=True) + if c.visible() + ) + + # XXX doesn't handle nested tracks/stacks at the moment + + for result in valid_results: + return result + + return None + + +def flatten_stack(in_stack): + """Flatten a Stack, or a list of Tracks, into a single Track. + Note that the 1st Track is the bottom one, and the last is the top. + """ + + flat_track = schema.Track() + flat_track.name = "Flattened" + + # map of track to track.range_of_all_children + range_track_map = {} + + def _get_next_item( + in_stack, + track_index=None, + trim_range=None + ): + if track_index is None: + # start with the top-most track + track_index = len(in_stack) - 1 + if track_index < 0: + # if you get to the bottom, you're done + return + + track = in_stack[track_index] + if trim_range is not None: + track = track_algo.track_trimmed_to_range(track, trim_range) + + track_map = range_track_map.get(track) + if track_map is None: + track_map = track.range_of_all_children() + range_track_map[track] = track_map + + for item in track: + if ( + item.visible() + or track_index == 0 + or isinstance(item, schema.Transition) + ): + yield item + else: + trim = track_map[item] + if trim_range is not None: + trim = opentime.TimeRange( + start_time=trim.start_time + trim_range.start_time, + duration=trim.duration + ) + track_map[item] = trim + for more in _get_next_item(in_stack, track_index - 1, trim): + yield more + + for item in _get_next_item(in_stack): + flat_track.append(copy.deepcopy(item)) + + return flat_track diff --git a/openpype/vendor/python/python_2/opentimelineio/algorithms/timeline_algo.py b/openpype/vendor/python/python_2/opentimelineio/algorithms/timeline_algo.py new file mode 100644 index 0000000000..bbb0ae6275 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/algorithms/timeline_algo.py @@ -0,0 +1,56 @@ +# +# Copyright 2019 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Algorithms for timeline objects.""" + +import copy + +from . import ( + track_algo +) + + +def timeline_trimmed_to_range(in_timeline, trim_range): + """Returns a new timeline that is a copy of the in_timeline, but with items + outside the trim_range removed and items on the ends trimmed to the + trim_range. Note that the timeline is never expanded, only shortened. + Please note that you could do nearly the same thing non-destructively by + just setting the Track's source_range but sometimes you want to really cut + away the stuff outside and that's what this function is meant for.""" + new_timeline = copy.deepcopy(in_timeline) + + for track_num, child_track in enumerate(in_timeline.tracks): + # @TODO: put the trim_range into the space of the tracks + # new_range = new_timeline.tracks.transformed_time_range( + # trim_range, + # child_track + # ) + + # trim the track and assign it to the new stack. + new_timeline.tracks[track_num] = track_algo.track_trimmed_to_range( + child_track, + trim_range + ) + + return new_timeline diff --git a/openpype/vendor/python/python_2/opentimelineio/algorithms/track_algo.py b/openpype/vendor/python/python_2/opentimelineio/algorithms/track_algo.py new file mode 100644 index 0000000000..8ac406f1d6 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/algorithms/track_algo.py @@ -0,0 +1,236 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Algorithms for track objects.""" + +import copy + +from .. import ( + schema, + exceptions, + opentime, +) + + +def track_trimmed_to_range(in_track, trim_range): + """Returns a new track that is a copy of the in_track, but with items + outside the trim_range removed and items on the ends trimmed to the + trim_range. Note that the track is never expanded, only shortened. + Please note that you could do nearly the same thing non-destructively by + just setting the Track's source_range but sometimes you want to really cut + away the stuff outside and that's what this function is meant for.""" + new_track = copy.deepcopy(in_track) + + track_map = new_track.range_of_all_children() + + # iterate backwards so we can delete items + for c, child in reversed(list(enumerate(new_track))): + child_range = track_map[child] + if not trim_range.overlaps(child_range): + # completely outside the trim range, so we discard it + del new_track[c] + elif trim_range.contains(child_range): + # completely contained, keep the whole thing + pass + else: + if isinstance(child, schema.Transition): + raise exceptions.CannotTrimTransitionsError( + "Cannot trim in the middle of a Transition." + ) + + # we need to clip the end(s) + child_source_range = child.trimmed_range() + + # should we trim the start? + if trim_range.start_time > child_range.start_time: + trim_amount = trim_range.start_time - child_range.start_time + child_source_range = opentime.TimeRange( + start_time=child_source_range.start_time + trim_amount, + duration=child_source_range.duration - trim_amount + + ) + + # should we trim the end? + trim_end = trim_range.end_time_exclusive() + child_end = child_range.end_time_exclusive() + if trim_end < child_end: + trim_amount = child_end - trim_end + child_source_range = opentime.TimeRange( + start_time=child_source_range.start_time, + duration=child_source_range.duration - trim_amount + + ) + + # set the new child's trims + child.source_range = child_source_range + + return new_track + + +def track_with_expanded_transitions(in_track): + """Expands transitions such that neighboring clips are trimmed into + regions of overlap. + + For example, if your track is: + Clip1, T, Clip2 + + will return: + Clip1', Clip1_t, T, Clip2_t, Clip2' + + Where Clip1' is the part of Clip1 not in the transition, Clip1_t is the + part inside the transition and so on. + """ + + result_track = [] + + seq_iter = iter(in_track) + prev_thing = None + thing = next(seq_iter, None) + next_thing = next(seq_iter, None) + + while thing is not None: + if isinstance(thing, schema.Transition): + result_track.append(_expand_transition(thing, in_track)) + else: + # not a transition, but might be trimmed by one before or after + # in the track + pre_transition = None + next_transition = None + + if isinstance(prev_thing, schema.Transition): + pre_transition = prev_thing + + if isinstance(next_thing, schema.Transition): + next_transition = next_thing + + result_track.append( + _trim_from_transitions( + thing, + pre=pre_transition, + post=next_transition + ) + ) + + # loop + prev_thing = thing + thing = next_thing + next_thing = next(seq_iter, None) + + return result_track + + +def _expand_transition(target_transition, from_track): + """ Expand transitions into the portions of pre-and-post clips that + overlap with the transition. + """ + + result = from_track.neighbors_of( + target_transition, + schema.NeighborGapPolicy.around_transitions + ) + + trx_duration = target_transition.in_offset + target_transition.out_offset + + # make copies of the before and after, and modify their in/out points + pre = copy.deepcopy(result.previous) + + if isinstance(pre, schema.Transition): + raise exceptions.TransitionFollowingATransitionError( + "cannot put two transitions next to each other in a track: " + "{}, {}".format( + pre, + target_transition + ) + ) + if target_transition.in_offset is None: + raise RuntimeError( + "in_offset is None on: {}".format(target_transition) + ) + + if target_transition.out_offset is None: + raise RuntimeError( + "out_offset is None on: {}".format(target_transition) + ) + + pre.name = (pre.name or "") + "_transition_pre" + + # ensure that pre.source_range is set, because it will get manipulated + tr = pre.trimmed_range() + + pre.source_range = opentime.TimeRange( + start_time=( + tr.end_time_exclusive() - target_transition.in_offset + ), + duration=trx_duration.rescaled_to( + tr.start_time + ) + ) + + post = copy.deepcopy(result.next) + if isinstance(post, schema.Transition): + raise exceptions.TransitionFollowingATransitionError( + "cannot put two transitions next to each other in a track: " + "{}, {}".format( + target_transition, + post + ) + ) + + post.name = (post.name or "") + "_transition_post" + + # ensure that post.source_range is set, because it will get manipulated + tr = post.trimmed_range() + + post.source_range = opentime.TimeRange( + start_time=( + tr.start_time - target_transition.in_offset + ).rescaled_to(tr.start_time), + duration=trx_duration.rescaled_to(tr.start_time) + ) + + return pre, target_transition, post + + +def _trim_from_transitions(thing, pre=None, post=None): + """ Trim clips next to transitions. """ + + result = copy.deepcopy(thing) + + # We might not have a source_range yet, + # We can trim to the computed trimmed_range to + # ensure we have something. + new_range = result.trimmed_range() + start_time = new_range.start_time + duration = new_range.duration + + if pre: + start_time += pre.out_offset + duration -= pre.out_offset + + if post: + duration -= post.in_offset + + result.source_range = opentime.TimeRange(start_time, duration) + + return result diff --git a/openpype/vendor/python/python_2/opentimelineio/console/__init__.py b/openpype/vendor/python/python_2/opentimelineio/console/__init__.py new file mode 100644 index 0000000000..e5f6e86988 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/__init__.py @@ -0,0 +1,40 @@ +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Console scripts for OpenTimelineIO + +.. moduleauthor:: Pixar Animation Studios +""" + +# flake8: noqa + +# in dependency hierarchy +from . import ( + otioconvert, + otiocat, + otiostat, + console_utils, + autogen_serialized_datamodel, +) + diff --git a/openpype/vendor/python/python_2/opentimelineio/console/autogen_serialized_datamodel.py b/openpype/vendor/python/python_2/opentimelineio/console/autogen_serialized_datamodel.py new file mode 100644 index 0000000000..046e8cbd1c --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/autogen_serialized_datamodel.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +# +# Copyright 2019 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + + +"""Generates documentation of the serialized data model for OpenTimelineIO.""" + +import argparse +import inspect +import json +import tempfile +import sys + +try: + # python2 + import StringIO as io +except ImportError: + # python3 + import io + +import opentimelineio as otio + + +DOCUMENT_HEADER = """# OpenTimelineIO Serialized Data Documentation + +This document is a list of all the OpenTimelineIO classes that serialize to and +from JSON, omitting SchemaDef plugins. + +This document is automatically generated by running + docs/autogen_serialized_datamodel.py, or by running `make doc-model`. It is + part of the unit tests suite and should be updated whenever the schema changes. + If it needs to be updated, run: `make doc-model-update` and this file should be + regenerated. + +# Classes + +""" + +FIELDS_ONLY_HEADER = """# OpenTimelineIO Serialized Data Documentation + +This document is a list of all the OpenTimelineIO classes that serialize to and +from JSON, omitting plugins classes and docstrings. + +This document is automatically generated by running + docs/autogen_serialized_datamodel.py, or by running `make doc-model`. It is + part of the unit tests suite and should be updated whenever the schema changes. + If it needs to be updated, run: `make doc-model-update` and this file should be + regenerated. + +# Classes + +""" + +CLASS_HEADER_WITH_DOCS = """ +### {classname} + +*full module path*: `{modpath}` + +*documentation*: + +``` +{docstring} +``` + +parameters: +""" + +CLASS_HEADER_ONLY_FIELDS = """ +### {classname} + +parameters: +""" + +MODULE_HEADER = """ +## Module: {modname} +""" + +PROP_HEADER = """- *{propkey}*: {prophelp} +""" + +# @TODO: having type information here would be awesome +PROP_HEADER_NO_HELP = """- *{propkey}* +""" + +# three ways to try and get the property + docstring +PROP_FETCHERS = ( + lambda cl, k: inspect.getdoc(getattr(cl, k)), + lambda cl, k: inspect.getdoc(getattr(cl, "_" + k)), + lambda cl, k: inspect.getdoc(getattr(cl(), k)) and "" or "", +) + + +def _parsed_args(): + """ parse commandline arguments with argparse """ + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-d", + "--dryrun", + action="store_true", + default=False, + help="Dryrun mode - print out instead of perform actions" + ) + group.add_argument( + "-o", + "--output", + type=str, + default=None, + help="Update the baseline with the current version" + ) + + return parser.parse_args() + + +# things to skip +SKIP_CLASSES = [otio.core.SerializableObject, otio.core.UnknownSchema] +SKIP_KEYS = ["OTIO_SCHEMA"] # not data, just for the backing format +SKIP_MODULES = ["opentimelineio.schemadef"] # because these are plugins + + +def _generate_model_for_module(mod, classes, modules): + modules.add(mod) + + # fetch the classes from this module + serializeable_classes = [ + thing for thing in mod.__dict__.values() + if ( + inspect.isclass(thing) + and thing not in classes + and issubclass(thing, otio.core.SerializableObject) + or thing in ( + otio.opentime.RationalTime, + otio.opentime.TimeRange, + otio.opentime.TimeTransform, + ) + ) + ] + + # serialize/deserialize the classes to capture their serialized parameters + model = {} + for cl in serializeable_classes: + if cl in SKIP_CLASSES: + continue + + model[cl] = {} + field_dict = json.loads(otio.adapters.otio_json.write_to_string(cl())) + for k in field_dict.keys(): + if k in SKIP_KEYS: + continue + + for fetcher in PROP_FETCHERS: + try: + model[cl][k] = fetcher(cl, k) + break + except AttributeError: + pass + else: + sys.stderr.write("ERROR: could not fetch property: {}".format(k)) + + # Stashing the OTIO_SCHEMA back into the dictionary since the + # documentation uses this information in its header. + model[cl]["OTIO_SCHEMA"] = field_dict["OTIO_SCHEMA"] + + classes.update(model) + + # find new modules to recurse into + new_mods = sorted( + ( + thing for thing in mod.__dict__.values() + if ( + inspect.ismodule(thing) + and thing not in modules + and all(not thing.__name__.startswith(t) for t in SKIP_MODULES) + ) + ), + key=lambda mod: str(mod) + ) + + # recurse into the new modules and update the classes and modules values + [_generate_model_for_module(m, classes, modules) for m in new_mods] + + +def _generate_model(): + classes = {} + modules = set() + _generate_model_for_module(otio, classes, modules) + return classes + + +def _write_documentation(model): + md_with_helpstrings = io.StringIO() + md_only_fields = io.StringIO() + + md_with_helpstrings.write(DOCUMENT_HEADER) + md_only_fields.write(FIELDS_ONLY_HEADER) + + modules = {} + for cl in model: + modules.setdefault(cl.__module__, []).append(cl) + + CURRENT_MODULE = None + for module_list in sorted(modules): + this_mod = ".".join(module_list.split('.')[:2]) + if this_mod != CURRENT_MODULE: + CURRENT_MODULE = this_mod + md_with_helpstrings.write(MODULE_HEADER.format(modname=this_mod)) + md_only_fields.write(MODULE_HEADER.format(modname=this_mod)) + + # because these are classes, they need to sort on their stringified + # names + for cl in sorted(modules[module_list], key=lambda cl: str(cl)): + modname = inspect.getmodule(cl).__name__ + label = model[cl]["OTIO_SCHEMA"] + md_with_helpstrings.write( + CLASS_HEADER_WITH_DOCS.format( + classname=label, + modpath=modname + "." + cl.__name__, + docstring=cl.__doc__ + ) + ) + md_only_fields.write( + CLASS_HEADER_ONLY_FIELDS.format( + classname=label, + ) + ) + + for key, helpstr in sorted(model[cl].items()): + if key in SKIP_KEYS: + continue + md_with_helpstrings.write( + PROP_HEADER.format(propkey=key, prophelp=helpstr) + ) + md_only_fields.write( + PROP_HEADER_NO_HELP.format(propkey=key) + ) + + return md_with_helpstrings.getvalue(), md_only_fields.getvalue() + + +def main(): + """ main entry point """ + args = _parsed_args() + with_docs, without_docs = generate_and_write_documentation() + + # print it out somewhere + if args.dryrun: + print(with_docs) + return + + output = args.output + if not output: + output = tempfile.NamedTemporaryFile( + 'w', + suffix="otio_serialized_schema.md", + delete=False + ).name + + with open(output, 'w') as fo: + fo.write(with_docs) + + # write version without docstrings + prefix, suffix = output.rsplit('.', 1) + output_only_fields = prefix + "-only-fields." + suffix + + with open(output_only_fields, 'w') as fo: + fo.write(without_docs) + + print("wrote documentation to {} and {}".format(output, output_only_fields)) + + +def generate_and_write_documentation(): + model = _generate_model() + return _write_documentation(model) + + +if __name__ == '__main__': + main() diff --git a/openpype/vendor/python/python_2/opentimelineio/console/console_utils.py b/openpype/vendor/python/python_2/opentimelineio/console/console_utils.py new file mode 100644 index 0000000000..9c659433e3 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/console_utils.py @@ -0,0 +1,72 @@ +# +# Copyright 2019 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +import ast + +from .. import ( + media_linker, +) + +"""Utilities for OpenTimelineIO commandline modules.""" + + +def arg_list_to_map(arg_list, label): + """ + Convert an argument of the form -A foo=bar from the parsed result to a map. + """ + + argument_map = {} + for pair in arg_list: + if '=' not in pair: + raise ValueError( + "error: {} arguments must be in the form key=value" + " got: {}".format(label, pair) + ) + + key, val = pair.split('=', 1) # only split on the 1st '=' + try: + # Sometimes we need to pass a bool, int, list, etc. + parsed_value = ast.literal_eval(val) + except (ValueError, SyntaxError): + # Fall back to a simple string + parsed_value = val + argument_map[key] = parsed_value + + return argument_map + + +def media_linker_name(ml_name_arg): + """ + Parse commandline arguments for the media linker, which can be not set + (fall back to default), "" or "none" (don't link media) or the name of a + media linker to use. + """ + if ml_name_arg.lower() == 'default': + media_linker_name = media_linker.MediaLinkingPolicy.ForceDefaultLinker + elif ml_name_arg.lower() in ['none', '']: + media_linker_name = media_linker.MediaLinkingPolicy.DoNotLinkMedia + else: + media_linker_name = ml_name_arg + + return media_linker_name diff --git a/openpype/vendor/python/python_2/opentimelineio/console/otiocat.py b/openpype/vendor/python/python_2/opentimelineio/console/otiocat.py new file mode 100644 index 0000000000..9513144512 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/otiocat.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Print the contents of an OTIO file to stdout.""" + +import argparse +import sys + +import opentimelineio as otio + + +def _parsed_args(): + """ parse commandline arguments with argparse """ + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + 'filepath', + type=str, + nargs='+', + help='files to print the contents of' + ) + parser.add_argument( + '-a', + '--adapter-arg', + type=str, + default=[], + action='append', + help='Extra arguments to be passed to input adapter in the form of ' + 'key=value. Values are strings, numbers or Python literals: True, ' + 'False, etc. Can be used multiple times: -a burrito="bar" -a taco=12.' + ) + parser.add_argument( + '-m', + '--media-linker', + type=str, + default="Default", + help=( + "Specify a media linker. 'Default' means use the " + "$OTIO_DEFAULT_MEDIA_LINKER if set, 'None' or '' means explicitly " + "disable the linker, and anything else is interpreted as the name" + " of the media linker to use." + ) + ) + parser.add_argument( + '-M', + '--media-linker-arg', + type=str, + default=[], + action='append', + help='Extra arguments to be passed to the media linker in the form of ' + 'key=value. Values are strings, numbers or Python literals: True, ' + 'False, etc. Can be used multiple times: -M burrito="bar" -M taco=12.' + ) + + return parser.parse_args() + + +def _otio_compatible_file_to_json_string( + fpath, + media_linker_name, + media_linker_argument_map, + adapter_argument_map +): + """Read the file at fpath with the default otio adapter and return the json + as a string. + """ + + adapter = otio.adapters.from_name("otio_json") + return adapter.write_to_string( + otio.adapters.read_from_file( + fpath, + media_linker_name=media_linker_name, + media_linker_argument_map=media_linker_argument_map, + **adapter_argument_map + ) + ) + + +def main(): + """Parse arguments and call _otio_compatible_file_to_json_string.""" + + args = _parsed_args() + + media_linker_name = otio.console.console_utils.media_linker_name( + args.media_linker + ) + + try: + read_adapter_arg_map = otio.console.console_utils.arg_list_to_map( + args.adapter_arg, + "adapter" + ) + media_linker_argument_map = otio.console.console_utils.arg_list_to_map( + args.media_linker_arg, + "media linker" + ) + except ValueError as exc: + sys.stderr.write("\n" + str(exc) + "\n") + sys.exit(1) + + for fpath in args.filepath: + print( + _otio_compatible_file_to_json_string( + fpath, + media_linker_name, + media_linker_argument_map, + read_adapter_arg_map + ) + ) + + +if __name__ == '__main__': + main() diff --git a/openpype/vendor/python/python_2/opentimelineio/console/otioconvert.py b/openpype/vendor/python/python_2/opentimelineio/console/otioconvert.py new file mode 100644 index 0000000000..9d45a0fcf4 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/otioconvert.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +import argparse +import sys +import copy + +import opentimelineio as otio + +__doc__ = """ Python wrapper around OTIO to convert timeline files between \ +formats. + +Available adapters: {} +""".format(otio.adapters.available_adapter_names()) + + +def _parsed_args(): + """ parse commandline arguments with argparse """ + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + '-i', + '--input', + type=str, + required=True, + help='path to input file', + ) + parser.add_argument( + '-o', + '--output', + type=str, + required=True, + help='path to output file', + ) + parser.add_argument( + '-I', + '--input-adapter', + type=str, + default=None, + help="Explicitly use this adapter for reading the input file", + ) + parser.add_argument( + '-O', + '--output-adapter', + type=str, + default=None, + help="Explicitly use this adapter for writing the output file", + ) + parser.add_argument( + '-T', + '--tracks', + type=str, + default=None, + help="Pick one or more tracks, by 0-based index, separated by commas.", + ) + parser.add_argument( + '-m', + '--media-linker', + type=str, + default="Default", + help=( + "Specify a media linker. 'Default' means use the " + "$OTIO_DEFAULT_MEDIA_LINKER if set, 'None' or '' means explicitly " + "disable the linker, and anything else is interpreted as the name" + " of the media linker to use." + ) + ) + parser.add_argument( + '-M', + '--media-linker-arg', + type=str, + default=[], + action='append', + help='Extra arguments to be passed to the media linker in the form of ' + 'key=value. Values are strings, numbers or Python literals: True, ' + 'False, etc. Can be used multiple times: -M burrito="bar" -M taco=12.' + ) + parser.add_argument( + '-a', + '--adapter-arg', + type=str, + default=[], + action='append', + help='Extra arguments to be passed to input adapter in the form of ' + 'key=value. Values are strings, numbers or Python literals: True, ' + 'False, etc. Can be used multiple times: -a burrito="bar" -a taco=12.' + ) + parser.add_argument( + '-A', + '--output-adapter-arg', + type=str, + default=[], + action='append', + help='Extra arguments to be passed to output adapter in the form of ' + 'key=value. Values are strings, numbers or Python literals: True, ' + 'False, etc. Can be used multiple times: -A burrito="bar" -A taco=12.' + ) + trim_args = parser.add_argument_group( + title="Trim Arguments", + description="Arguments that allow you to trim the OTIO file." + ) + trim_args.add_argument( + '--begin', + type=str, + default=None, + help=( + "Trim out everything in the timeline before this time, in the " + "global time frame of the timeline. Argument should be in the form" + ' "VALUE,RATE", eg: --begin "10,24". Requires --end argument.' + ), + ) + trim_args.add_argument( + '--end', + type=str, + default=None, + help=( + "Trim out everything in the timeline after this time, in the " + "global time frame of the timeline. Argument should be in the form" + ' "VALUE,RATE", eg: --begin "10,24". Requires --begin argument.' + ), + ) + + result = parser.parse_args() + + if result.begin is not None and result.end is None: + parser.error("--begin requires --end.") + if result.end is not None and result.begin is None: + parser.error("--end requires --begin.") + + if result.begin is not None: + try: + value, rate = result.begin.split(",") + result.begin = otio.opentime.RationalTime(float(value), float(rate)) + except ValueError: + parser.error( + "--begin argument needs to be of the form: VALUE,RATE where " + "VALUE is the (float) time value of the resulting RationalTime " + "and RATE is the (float) time rate of the resulting RationalTime," + " not '{}'".format(result.begin) + ) + + if result.end is not None: + try: + value, rate = result.end.split(",") + result.end = otio.opentime.RationalTime(float(value), float(rate)) + except ValueError: + parser.error( + "--end argument needs to be of the form: VALUE,RATE where " + "VALUE is the (float) time value of the resulting RationalTime " + "and RATE is the (float) time rate of the resulting RationalTime," + " not '{}'".format(result.begin) + ) + + return result + + +def main(): + """Parse arguments and convert the files.""" + + args = _parsed_args() + + in_adapter = args.input_adapter + if in_adapter is None: + in_adapter = otio.adapters.from_filepath(args.input).name + + out_adapter = args.output_adapter + if out_adapter is None: + out_adapter = otio.adapters.from_filepath(args.output).name + + media_linker_name = otio.console.console_utils.media_linker_name( + args.media_linker + ) + + try: + read_adapter_arg_map = otio.console.console_utils.arg_list_to_map( + args.adapter_arg, + "input adapter" + ) + ml_args = otio.console.console_utils.arg_list_to_map( + args.media_linker_arg, + "media linker" + ) + except ValueError as exc: + sys.stderr.write("\n" + str(exc) + "\n") + sys.exit(1) + + result_tl = otio.adapters.read_from_file( + args.input, + in_adapter, + media_linker_name=media_linker_name, + media_linker_argument_map=ml_args, + **read_adapter_arg_map + ) + + if args.tracks: + result_tracks = copy.deepcopy(otio.schema.Stack()) + del result_tracks[:] + for track in args.tracks.split(","): + tr = result_tl.tracks[int(track)] + del result_tl.tracks[int(track)] + print("track {0} is of kind: '{1}'".format(track, tr.kind)) + result_tracks.append(tr) + result_tl.tracks = result_tracks + + # handle trim arguments + if args.begin is not None and args.end is not None: + result_tl = otio.algorithms.timeline_trimmed_to_range( + result_tl, + otio.opentime.range_from_start_end_time(args.begin, args.end) + ) + + try: + write_adapter_arg_map = otio.console.console_utils.arg_list_to_map( + args.output_adapter_arg, + "output adapter" + ) + except ValueError as exc: + sys.stderr.write("\n" + str(exc) + "\n") + sys.exit(1) + + otio.adapters.write_to_file( + result_tl, + args.output, + out_adapter, + **write_adapter_arg_map + ) + + +if __name__ == '__main__': + try: + main() + except otio.exceptions.OTIOError as err: + sys.stderr.write("ERROR: " + str(err) + "\n") + sys.exit(1) diff --git a/openpype/vendor/python/python_2/opentimelineio/console/otiostat.py b/openpype/vendor/python/python_2/opentimelineio/console/otiostat.py new file mode 100644 index 0000000000..9cd554727a --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/console/otiostat.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Print statistics about the otio file, including validation information.""" + +import argparse +import sys + +import opentimelineio as otio + + +def _parsed_args(): + """ parse commandline arguments with argparse """ + + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument( + 'filepath', + type=str, + nargs='+', + help='files to operate on' + ) + + return parser.parse_args() + + +TESTS = [] + + +def stat_check(name): + def real_stat_check(fn): + TESTS.append((name, fn)) + return fn + return real_stat_check + + +@stat_check("parsed") +def _did_parse(input): + return input and True or False + + +@stat_check("top level object") +def _top_level_object(input): + return input._serializable_label + + +@stat_check("number of tracks") +def _num_tracks(input): + try: + return len(input.tracks) + except AttributeError: + return 0 + + +@stat_check("Tracks are the same length") +def _equal_length_tracks(tl): + if not tl.tracks: + return True + for i, track in enumerate(tl.tracks): + if track.duration() != tl.tracks[0].duration(): + raise RuntimeError( + "track {} is not the same duration as the other tracks." + " Track {} duration, vs: {}".format( + i, + track.duration(), + tl.tracks[0].duration() + ) + ) + return True + + +@stat_check("deepest nesting") +def _deepest_nesting(input): + def depth(parent): + if not isinstance(parent, otio.core.Composition): + return 1 + d = 0 + for child in parent: + d = max(d, depth(child) + 1) + return d + if isinstance(input, otio.schema.Timeline): + return depth(input.tracks) + 1 + else: + return depth(input) + + +@stat_check("number of clips") +def _num_clips(input): + return len(list(input.each_clip())) + + +@stat_check("total duration") +def _total_duration(input): + try: + return input.tracks.duration() + except AttributeError: + return "n/a" + + +@stat_check("total duration in timecode") +def _total_duration_timecode(input): + try: + d = input.tracks.duration() + return otio.opentime.to_timecode(d, d.rate) + except AttributeError: + return "n/a" + + +@stat_check("top level rate") +def _top_level_rate(input): + try: + return input.tracks.duration().rate + except AttributeError: + return "n/a" + + +@stat_check("clips with cdl data") +def _clips_with_cdl_data(input): + return len(list(c for c in input.each_clip() if 'cdl' in c.metadata)) + + +@stat_check("Tracks with non standard types") +def _sequences_with_non_standard_types(input): + return len( + list( + c + for c in input.each_child(descended_from_type=otio.schema.Track) + if c.kind not in (otio.schema.TrackKind.__dict__) + ) + ) + + +def _stat_otio(input_otio): + for (test, testfunc) in TESTS: + try: + print("{}: {}".format(test, testfunc(input_otio))) + except (otio.exceptions.OTIOError) as e: + sys.stderr.write( + "There was an OTIO Error: " + " {}\n".format(e), + ) + continue + except (Exception) as e: + sys.stderr.write("There was a system error: {}\n".format(e)) + continue + + +def main(): + """ main entry point """ + args = _parsed_args() + + for fp in args.filepath: + try: + parsed_otio = otio.adapters.read_from_file(fp) + except (otio.exceptions.OTIOError) as e: + sys.stderr.write( + "The file did not successfully parse, with error:" + " {}\n".format(e), + ) + continue + except (Exception) as e: + sys.stderr.write("There was a system error: {}\n".format(e)) + continue + + _stat_otio(parsed_otio) + + +if __name__ == '__main__': + main() diff --git a/openpype/vendor/python/python_2/opentimelineio/core/__init__.py b/openpype/vendor/python/python_2/opentimelineio/core/__init__.py new file mode 100644 index 0000000000..ac5c0bbcc0 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/__init__.py @@ -0,0 +1,67 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Internal implementation details of OpenTimelineIO.""" + +# flake8: noqa + +from . import ( + serializable_object +) +from .serializable_object import ( + SerializableObject, + serializable_field, + deprecated_field, +) +from .composable import ( + Composable +) +from .item import ( + Item +) +from . import composition +from .composition import ( + Composition, +) +from . import type_registry +from .type_registry import ( + register_type, + upgrade_function_for, + schema_name_from_label, + schema_version_from_label, + instance_from_schema, +) +from .json_serializer import ( + serialize_json_to_string, + serialize_json_to_file, + deserialize_json_from_string, + deserialize_json_from_file, +) +from .media_reference import ( + MediaReference, +) +from . import unknown_schema +from .unknown_schema import ( + UnknownSchema +) diff --git a/openpype/vendor/python/python_2/opentimelineio/core/composable.py b/openpype/vendor/python/python_2/opentimelineio/core/composable.py new file mode 100644 index 0000000000..78c7fba349 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/composable.py @@ -0,0 +1,141 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Composable class definition. + +An object that can be composed by tracks. +""" + +import weakref + +from . import serializable_object +from . import type_registry + +import copy + + +@type_registry.register_type +class Composable(serializable_object.SerializableObject): + """An object that can be composed by tracks. + + Base class of: + Item + Transition + """ + + name = serializable_object.serializable_field( + "name", + doc="Composable name." + ) + metadata = serializable_object.serializable_field( + "metadata", + doc="Metadata dictionary for this Composable." + ) + + _serializable_label = "Composable.1" + _class_path = "core.Composable" + + def __init__(self, name=None, metadata=None): + super(Composable, self).__init__() + self._parent = None + + # initialize the serializable fields + self.name = name + self.metadata = copy.deepcopy(metadata) if metadata else {} + + @staticmethod + def visible(): + """Return the visibility of the Composable. By default True.""" + + return False + + @staticmethod + def overlapping(): + """Return whether an Item is overlapping. By default False.""" + + return False + + # @{ functions to express the composable hierarchy + def _root_parent(self): + return ([self] + self._ancestors())[-1] + + def _ancestors(self): + ancestors = [] + seqi = self + while seqi.parent() is not None: + seqi = seqi.parent() + ancestors.append(seqi) + return ancestors + + def parent(self): + """Return the parent Composable, or None if self has no parent.""" + + return self._parent() if self._parent is not None else None + + def _set_parent(self, new_parent): + if new_parent is not None and self.parent() is not None: + raise ValueError( + "Composable named '{}' is already in a composition named '{}'," + " remove from previous parent before adding to new one." + " Composable: {}, Composition: {}".format( + self.name, + self.parent() is not None and self.parent().name or None, + self, + self.parent() + ) + ) + self._parent = weakref.ref(new_parent) if new_parent is not None else None + + def is_parent_of(self, other): + """Returns true if self is a parent or ancestor of other.""" + + visited = set([]) + while other.parent() is not None and other.parent() not in visited: + if other.parent() is self: + return True + visited.add(other) + other = other.parent() + + return False + + # @} + + def __repr__(self): + return ( + "otio.{}(" + "name={}, " + "metadata={}" + ")".format( + self._class_path, + repr(self.name), + repr(self.metadata) + ) + ) + + def __str__(self): + return "{}({}, {})".format( + self._class_path.split('.')[-1], + self.name, + str(self.metadata) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/core/composition.py b/openpype/vendor/python/python_2/opentimelineio/core/composition.py new file mode 100644 index 0000000000..4da5a4b091 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/composition.py @@ -0,0 +1,718 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Composition base class. An object that contains `Items`.""" + +import collections + +from . import ( + serializable_object, + type_registry, + item, + composable, +) + +from .. import ( + opentime, + exceptions +) + + +def _bisect_right( + seq, + tgt, + key_func, + lower_search_bound=0, + upper_search_bound=None +): + """Return the index of the last item in seq such that all e in seq[:index] + have key_func(e) <= tgt, and all e in seq[index:] have key_func(e) > tgt. + + Thus, seq.insert(index, value) will insert value after the rightmost item + such that meets the above condition. + + lower_search_bound and upper_search_bound bound the slice to be searched. + + Assumes that seq is already sorted. + """ + + if lower_search_bound < 0: + raise ValueError('lower_search_bound must be non-negative') + + if upper_search_bound is None: + upper_search_bound = len(seq) + + while lower_search_bound < upper_search_bound: + midpoint_index = (lower_search_bound + upper_search_bound) // 2 + + if tgt < key_func(seq[midpoint_index]): + upper_search_bound = midpoint_index + else: + lower_search_bound = midpoint_index + 1 + + return lower_search_bound + + +def _bisect_left( + seq, + tgt, + key_func, + lower_search_bound=0, + upper_search_bound=None +): + """Return the index of the last item in seq such that all e in seq[:index] + have key_func(e) < tgt, and all e in seq[index:] have key_func(e) >= tgt. + + Thus, seq.insert(index, value) will insert value before the leftmost item + such that meets the above condition. + + lower_search_bound and upper_search_bound bound the slice to be searched. + + Assumes that seq is already sorted. + """ + + if lower_search_bound < 0: + raise ValueError('lower_search_bound must be non-negative') + + if upper_search_bound is None: + upper_search_bound = len(seq) + + while lower_search_bound < upper_search_bound: + midpoint_index = (lower_search_bound + upper_search_bound) // 2 + + if key_func(seq[midpoint_index]) < tgt: + lower_search_bound = midpoint_index + 1 + else: + upper_search_bound = midpoint_index + + return lower_search_bound + + +@type_registry.register_type +class Composition(item.Item, collections.MutableSequence): + """Base class for an OTIO Item that contains other Items. + + Should be subclassed (for example by Track and Stack), not used + directly. + """ + + _serializable_label = "Composition.1" + _composition_kind = "Composition" + _modname = "core" + _composable_base_class = composable.Composable + + def __init__( + self, + name=None, + children=None, + source_range=None, + markers=None, + effects=None, + metadata=None + ): + item.Item.__init__( + self, + name=name, + source_range=source_range, + markers=markers, + effects=effects, + metadata=metadata + ) + collections.MutableSequence.__init__(self) + + # Because we know that all children are unique, we store a set + # of all the children as well to speed up __contain__ checks. + self._child_lookup = set() + + self._children = [] + if children: + # cannot simply set ._children to children since __setitem__ runs + # extra logic (assigning ._parent pointers) and populates the + # internal membership set _child_lookup. + self.extend(children) + + _children = serializable_object.serializable_field( + "children", + list, + "Items contained by this composition." + ) + + @property + def composition_kind(self): + """Returns a label specifying the kind of composition.""" + + return self._composition_kind + + def __str__(self): + return "{}({}, {}, {}, {})".format( + self._composition_kind, + str(self.name), + str(self._children), + str(self.source_range), + str(self.metadata) + ) + + def __repr__(self): + return ( + "otio.{}.{}(" + "name={}, " + "children={}, " + "source_range={}, " + "metadata={}" + ")".format( + self._modname, + self._composition_kind, + repr(self.name), + repr(self._children), + repr(self.source_range), + repr(self.metadata) + ) + ) + + transform = serializable_object.deprecated_field() + + def child_at_time( + self, + search_time, + shallow_search=False, + ): + """Return the child that overlaps with time search_time. + + search_time is in the space of self. + + If shallow_search is false, will recurse into compositions. + """ + + range_map = self.range_of_all_children() + + # find the first item whose end_time_exclusive is after the + first_inside_range = _bisect_left( + seq=self._children, + tgt=search_time, + key_func=lambda child: range_map[child].end_time_exclusive(), + ) + + # find the last item whose start_time is before the + last_in_range = _bisect_right( + seq=self._children, + tgt=search_time, + key_func=lambda child: range_map[child].start_time, + lower_search_bound=first_inside_range, + ) + + # limit the search to children who are in the search_range + possible_matches = self._children[first_inside_range:last_in_range] + + result = None + for thing in possible_matches: + if range_map[thing].overlaps(search_time): + result = thing + break + + # if the search cannot or should not continue + if ( + result is None + or shallow_search + or not hasattr(result, "child_at_time") + ): + return result + + # before you recurse, you have to transform the time into the + # space of the child + child_search_time = self.transformed_time(search_time, result) + + return result.child_at_time(child_search_time, shallow_search) + + def each_child( + self, + search_range=None, + descended_from_type=composable.Composable, + shallow_search=False, + ): + """ Generator that returns each child contained in the composition in + the order in which it is found. + + Arguments: + search_range: if specified, only children whose range overlaps with + the search range will be yielded. + descended_from_type: if specified, only children who are a + descendent of the descended_from_type will be yielded. + shallow_search: if True, will only search children of self, not + and not recurse into children of children. + """ + if search_range: + range_map = self.range_of_all_children() + + # find the first item whose end_time_inclusive is after the + # start_time of the search range + first_inside_range = _bisect_left( + seq=self._children, + tgt=search_range.start_time, + key_func=lambda child: range_map[child].end_time_inclusive(), + ) + + # find the last item whose start_time is before the + # end_time_inclusive of the search_range + last_in_range = _bisect_right( + seq=self._children, + tgt=search_range.end_time_inclusive(), + key_func=lambda child: range_map[child].start_time, + lower_search_bound=first_inside_range, + ) + + # limit the search to children who are in the search_range + children = self._children[first_inside_range:last_in_range] + else: + # otherwise search all the children + children = self._children + + for child in children: + # filter out children who are not descended from the specified type + # shortcut the isinstance if descended_from_type is composable + # (since all objects in compositions are already composables) + is_descendant = descended_from_type == composable.Composable + if is_descendant or isinstance(child, descended_from_type): + yield child + + # if not a shallow_search, for children that are compositions, + # recurse into their children + if not shallow_search and hasattr(child, "each_child"): + + if search_range is not None: + search_range = self.transformed_time_range(search_range, child) + + for valid_child in child.each_child( + search_range, + descended_from_type, + shallow_search + ): + yield valid_child + + def range_of_child_at_index(self, index): + """Return the range of a child item in the time range of this + composition. + + For example, with a track: + [ClipA][ClipB][ClipC] + + The self.range_of_child_at_index(2) will return: + TimeRange(ClipA.duration + ClipB.duration, ClipC.duration) + + To be implemented by subclass of Composition. + """ + + raise NotImplementedError + + def trimmed_range_of_child_at_index(self, index): + """Return the trimmed range of the child item at index in the time + range of this composition. + + For example, with a track: + + [ ] + + [ClipA][ClipB][ClipC] + + The range of index 2 (ClipC) will be just like + range_of_child_at_index() but trimmed based on this Composition's + source_range. + + To be implemented by child. + """ + + raise NotImplementedError + + def range_of_all_children(self): + """Return a dict mapping children to their range in this object.""" + + raise NotImplementedError + + def __copy__(self): + result = super(Composition, self).__copy__() + + # Children are *not* copied with a shallow copy since the meaning is + # ambiguous - they have a parent pointer which would need to be flipped + # or they would need to be copied, which implies a deepcopy(). + # + # This follows from the python documentation on copy/deepcopy: + # https://docs.python.org/2/library/copy.html + # + # """ + # - A shallow copy constructs a new compound object and then (to the + # extent possible) inserts references into it to the objects found in + # the original. + # - A deep copy constructs a new compound object and then, recursively, + # inserts copies into it of the objects found in the original. + # """ + result._children = [] + + return result + + def __deepcopy__(self, md): + result = super(Composition, self).__deepcopy__(md) + + # deepcopy should have already copied the children, so only parent + # pointers need to be updated. + [c._set_parent(result) for c in result._children] + + # we also need to reconstruct the membership set of _child_lookup. + result._child_lookup.update(result._children) + + return result + + def _path_to_child(self, child): + if not isinstance(child, composable.Composable): + raise TypeError( + "An object child of 'Composable' is required," + " not type '{}'".format( + type(child) + ) + ) + + current = child + parents = [] + + while(current is not self): + try: + current = current.parent() + except AttributeError: + raise exceptions.NotAChildError( + "Item '{}' is not a child of '{}'.".format(child, self) + ) + + parents.append(current) + + return parents + + def range_of_child(self, child, reference_space=None): + """The range of the child in relation to another item + (reference_space), not trimmed based on this + composition's source_range. + + Note that reference_space must be in the same timeline as self. + + For example: + + | [-----] | seq + + [-----------------] Clip A + + If ClipA has duration 17, and seq has source_range: 5, duration 15, + seq.range_of_child(Clip A) will return (0, 17) + ignoring the source range of seq. + + To get the range of the child with the source_range applied, use the + trimmed_range_of_child() method. + """ + + if not reference_space: + reference_space = self + + parents = self._path_to_child(child) + + current = child + result_range = None + + for parent in parents: + index = parent.index(current) + parent_range = parent.range_of_child_at_index(index) + + if not result_range: + result_range = parent_range + current = parent + continue + + result_range = opentime.TimeRange( + start_time=result_range.start_time + parent_range.start_time, + duration=result_range.duration + ) + current = parent + + if reference_space is not self: + result_range = self.transformed_time_range( + result_range, + reference_space + ) + + return result_range + + def handles_of_child(self, child): + """If media beyond the ends of this child are visible due to adjacent + Transitions (only applicable in a Track) then this will return the + head and tail offsets as a tuple of RationalTime objects. If no handles + are present on either side, then None is returned instead of a + RationalTime. + + Example usage: + >>> head, tail = track.handles_of_child(clip) + >>> if head: + ... print('Do something') + >>> if tail: + ... print('Do something else') + """ + return (None, None) + + def trimmed_range_of_child(self, child, reference_space=None): + """Get range of the child in reference_space coordinates, after the + self.source_range is applied. + + Example + | [-----] | seq + [-----------------] Clip A + + If ClipA has duration 17, and seq has source_range: 5, duration 10, + seq.trimmed_range_of_child(Clip A) will return (5, 10) + Which is trimming the range according to the source_range of seq. + + To get the range of the child without the source_range applied, use the + range_of_child() method. + + Another example + | [-----] | seq source range starts on frame 4 and goes to frame 8 + [ClipA][ClipB] (each 6 frames long) + + >>> seq.range_of_child(CLipA) + 0, duration 6 + >>> seq.trimmed_range_of_child(ClipA): + 4, duration 2 + """ + + if not reference_space: + reference_space = self + + if not reference_space == self: + raise NotImplementedError + + parents = self._path_to_child(child) + + current = child + result_range = None + + for parent in parents: + index = parent.index(current) + parent_range = parent.trimmed_range_of_child_at_index(index) + + if not result_range: + result_range = parent_range + current = parent + continue + + result_range.start_time += parent_range.start_time + current = parent + + if not self.source_range or not result_range: + return result_range + + new_start_time = max( + self.source_range.start_time, + result_range.start_time + ) + + # trimmed out + if new_start_time >= result_range.end_time_exclusive(): + return None + + # compute duration + new_duration = min( + result_range.end_time_exclusive(), + self.source_range.end_time_exclusive() + ) - new_start_time + + if new_duration.value < 0: + return None + + return opentime.TimeRange(new_start_time, new_duration) + + def trim_child_range(self, child_range): + if not self.source_range: + return child_range + + # cropped out entirely + past_end_time = self.source_range.start_time >= child_range.end_time_exclusive() + before_start_time = \ + self.source_range.end_time_exclusive() <= child_range.start_time + + if past_end_time or before_start_time: + return None + + if child_range.start_time < self.source_range.start_time: + child_range = opentime.range_from_start_end_time( + self.source_range.start_time, + child_range.end_time_exclusive() + ) + + if ( + child_range.end_time_exclusive() > + self.source_range.end_time_exclusive() + ): + child_range = opentime.range_from_start_end_time( + child_range.start_time, + self.source_range.end_time_exclusive() + ) + + return child_range + + # @{ SerializableObject override. + def _update(self, d): + """Like the dictionary .update() method. + + Update the data dictionary of this SerializableObject with the .data + of d if d is a SerializableObject or if d is a dictionary, d itself. + """ + + # use the parent update function + super(Composition, self)._update(d) + + # ...except for the 'children' field, which needs to run through the + # insert method so that _parent pointers are correctly set on children. + self._children = [] + self.extend(d.get('children', [])) + # @} + + # @{ collections.MutableSequence implementation + def __getitem__(self, item): + return self._children[item] + + def _setitem_slice(self, key, value): + set_value = set(value) + + # check if any members in the new slice are repeated + if len(set_value) != len(value): + raise ValueError( + "Instancing not allowed in Compositions, {} contains repeated" + " items.".format(value) + ) + + old = self._children[key] + if old: + set_old = set(old) + set_outside_old = set(self._children).difference(set_old) + + isect = set_outside_old.intersection(set_value) + if isect: + raise ValueError( + "Attempting to insert duplicates of items {} already " + "present in container, instancing not allowed in " + "Compositions".format(isect) + ) + + # update old parent + for val in old: + val._set_parent(None) + self._child_lookup.remove(val) + + # insert into _children + self._children[key] = value + + # update new parent + if value: + for val in value: + val._set_parent(self) + self._child_lookup.add(val) + + def __setitem__(self, key, value): + # fetch the current thing at that index/slice + old = self._children[key] + + # in the case of key being a slice, old and value are both sequences + if old is value: + return + + if isinstance(key, slice): + return self._setitem_slice(key, value) + + if value in self: + raise ValueError( + "Composable {} already present in this container, instancing" + " not allowed in otio compositions.".format(value) + ) + + # unset the old child's parent and delete the membership entry. + if old is not None: + old._set_parent(None) + self._child_lookup.remove(old) + + # put it into our list of children + self._children[key] = value + + # set the new parent + if value is not None: + value._set_parent(self) + + # put it into our membership tracking set + self._child_lookup.add(value) + + def insert(self, index, item): + """Insert an item into the composition at location `index`.""" + + if not isinstance(item, self._composable_base_class): + raise TypeError( + "Not allowed to insert an object of type {0} into a {1}, only" + " objects descending from {2}. Tried to insert: {3}".format( + type(item), + type(self), + self._composable_base_class, + str(item) + ) + ) + + if item in self: + raise ValueError( + "Composable {} already present in this container, instancing" + " not allowed in otio compositions.".format(item) + ) + + # set the item's parent and add it to our membership tracking and list + # of children + item._set_parent(self) + self._child_lookup.add(item) + self._children.insert(index, item) + + def __contains__(self, item): + """Use our internal membership tracking set to speed up searches.""" + return item in self._child_lookup + + def __len__(self): + """The len() of a Composition is the # of children in it. + Note that this also means that a Composition with no children + is considered False, so take care to test for "if foo is not None" + versus just "if foo" when the difference matters.""" + return len(self._children) + + def __delitem__(self, key): + # grab the old value + old = self._children[key] + + # remove it from the membership tracking set and clear parent + if old is not None: + if isinstance(key, slice): + for val in old: + self._child_lookup.remove(val) + val._set_parent(None) + else: + self._child_lookup.remove(old) + old._set_parent(None) + + # remove it from our list of children + del self._children[key] diff --git a/openpype/vendor/python/python_2/opentimelineio/core/item.py b/openpype/vendor/python/python_2/opentimelineio/core/item.py new file mode 100644 index 0000000000..7e035a3a9e --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/item.py @@ -0,0 +1,243 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of the Item base class. OTIO Objects that contain media.""" + +import copy + +from .. import ( + opentime, + exceptions, +) + +from . import ( + serializable_object, + composable, +) + + +class Item(composable.Composable): + """An Item is a Composable that can be part of a Composition or Timeline. + + More specifically, it is a Composable that has meaningful duration. + + Can also hold effects and markers. + + Base class of: + - Composition (and children) + - Clip + - Gap + """ + + _serializable_label = "Item.1" + _class_path = "core.Item" + + def __init__( + self, + name=None, + source_range=None, + effects=None, + markers=None, + metadata=None, + ): + super(Item, self).__init__(name=name, metadata=metadata) + + self.source_range = copy.deepcopy(source_range) + self.effects = copy.deepcopy(effects) if effects else [] + self.markers = copy.deepcopy(markers) if markers else [] + + name = serializable_object.serializable_field("name", doc="Item name.") + source_range = serializable_object.serializable_field( + "source_range", + opentime.TimeRange, + doc="Range of source to trim to. Can be None or a TimeRange." + ) + + @staticmethod + def visible(): + """Return the visibility of the Item. By default True.""" + + return True + + def duration(self): + """Convience wrapper for the trimmed_range.duration of the item.""" + + return self.trimmed_range().duration + + def available_range(self): + """Implemented by child classes, available range of media.""" + + raise NotImplementedError + + def trimmed_range(self): + """The range after applying the source range.""" + if self.source_range is not None: + return copy.copy(self.source_range) + + return self.available_range() + + def visible_range(self): + """The range of this item's media visible to its parent. + Includes handles revealed by adjacent transitions (if any). + This will always be larger or equal to trimmed_range().""" + result = self.trimmed_range() + if self.parent(): + head, tail = self.parent().handles_of_child(self) + if head: + result = opentime.TimeRange( + start_time=result.start_time - head, + duration=result.duration + head + ) + if tail: + result = opentime.TimeRange( + start_time=result.start_time, + duration=result.duration + tail + ) + return result + + def trimmed_range_in_parent(self): + """Find and return the trimmed range of this item in the parent.""" + if not self.parent(): + raise exceptions.NotAChildError( + "No parent of {}, cannot compute range in parent.".format(self) + ) + + return self.parent().trimmed_range_of_child(self) + + def range_in_parent(self): + """Find and return the untrimmed range of this item in the parent.""" + if not self.parent(): + raise exceptions.NotAChildError( + "No parent of {}, cannot compute range in parent.".format(self) + ) + + return self.parent().range_of_child(self) + + def transformed_time(self, t, to_item): + """Converts time t in the coordinate system of self to coordinate + system of to_item. + + Note that self and to_item must be part of the same timeline (they must + have a common ancestor). + + Example: + + 0 20 + [------t----D----------] + [--A-][t----B---][--C--] + 100 101 110 + 101 in B = 6 in D + + t = t argument + """ + + if not isinstance(t, opentime.RationalTime): + raise ValueError( + "transformed_time only operates on RationalTime, not {}".format( + type(t) + ) + ) + + # does not operate in place + result = copy.copy(t) + + if to_item is None: + return result + + root = self._root_parent() + + # transform t to root parent's coordinate system + item = self + while item != root and item != to_item: + + parent = item.parent() + result -= item.trimmed_range().start_time + result += parent.range_of_child(item).start_time + + item = parent + + ancestor = item + + # transform from root parent's coordinate system to to_item + item = to_item + while item != root and item != ancestor: + + parent = item.parent() + result += item.trimmed_range().start_time + result -= parent.range_of_child(item).start_time + + item = parent + + assert(item is ancestor) + + return result + + def transformed_time_range(self, tr, to_item): + """Transforms the timerange tr to the range of child or self to_item.""" + + return opentime.TimeRange( + self.transformed_time(tr.start_time, to_item), + tr.duration + ) + + markers = serializable_object.serializable_field( + "markers", + doc="List of markers on this item." + ) + effects = serializable_object.serializable_field( + "effects", + doc="List of effects on this item." + ) + metadata = serializable_object.serializable_field( + "metadata", + doc="Metadata dictionary for this item." + ) + + def __repr__(self): + return ( + "otio.{}(" + "name={}, " + "source_range={}, " + "effects={}, " + "markers={}, " + "metadata={}" + ")".format( + self._class_path, + repr(self.name), + repr(self.source_range), + repr(self.effects), + repr(self.markers), + repr(self.metadata) + ) + ) + + def __str__(self): + return "{}({}, {}, {}, {}, {})".format( + self._class_path.split('.')[-1], + self.name, + str(self.source_range), + str(self.effects), + str(self.markers), + str(self.metadata) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/core/json_serializer.py b/openpype/vendor/python/python_2/opentimelineio/core/json_serializer.py new file mode 100644 index 0000000000..fee8242143 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/json_serializer.py @@ -0,0 +1,218 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Serializer for SerializableObjects to JSON + +Used for the otio_json adapter as well as for plugins and manifests. +""" + +import json + +from . import ( + SerializableObject, + type_registry, +) + +from .unknown_schema import UnknownSchema + +from .. import ( + exceptions, + opentime, +) + + +# @TODO: Handle file version drifting + + +class _SerializableObjectEncoder(json.JSONEncoder): + + """ Encoder for the SerializableObject OTIO Class and its descendents. """ + + def default(self, obj): + for typename, encfn in _ENCODER_LIST: + if isinstance(obj, typename): + return encfn(obj) + + return json.JSONEncoder.default(self, obj) + + +def serialize_json_to_string(root, indent=4): + """Serialize a tree of SerializableObject to JSON. + + Returns a JSON string. + """ + + return _SerializableObjectEncoder( + sort_keys=True, + indent=indent + ).encode(root) + + +def serialize_json_to_file(root, to_file): + """ + Serialize a tree of SerializableObject to JSON. + + Writes the result to the given file path. + """ + + content = serialize_json_to_string(root) + + with open(to_file, 'w') as file_contents: + file_contents.write(content) + +# @{ Encoders + + +def _encoded_serializable_object(input_otio): + if not input_otio._serializable_label: + raise exceptions.InvalidSerializableLabelError( + input_otio._serializable_label + ) + result = { + "OTIO_SCHEMA": input_otio._serializable_label, + } + result.update(input_otio._data) + return result + + +def _encoded_unknown_schema_object(input_otio): + orig_label = input_otio.data.get(UnknownSchema._original_label) + if not orig_label: + raise exceptions.InvalidSerializableLabelError( + orig_label + ) + # result is just a dict, not a SerializableObject + result = {} + result.update(input_otio.data) + result["OTIO_SCHEMA"] = orig_label # override the UnknownSchema label + del result[UnknownSchema._original_label] + return result + + +def _encoded_time(input_otio): + return { + "OTIO_SCHEMA": "RationalTime.1", + 'value': input_otio.value, + 'rate': input_otio.rate + } + + +def _encoded_time_range(input_otio): + return { + "OTIO_SCHEMA": "TimeRange.1", + 'start_time': _encoded_time(input_otio.start_time), + 'duration': _encoded_time(input_otio.duration) + } + + +def _encoded_transform(input_otio): + return { + "OTIO_SCHEMA": "TimeTransform.1", + 'offset': _encoded_time(input_otio.offset), + 'scale': input_otio.scale, + 'rate': input_otio.rate + } +# @} + + +# Ordered list of functions for encoding OTIO objects to JSON. +# More particular cases should precede more general cases. +_ENCODER_LIST = [ + (opentime.RationalTime, _encoded_time), + (opentime.TimeRange, _encoded_time_range), + (opentime.TimeTransform, _encoded_transform), + (UnknownSchema, _encoded_unknown_schema_object), + (SerializableObject, _encoded_serializable_object) +] + +# @{ Decoders + + +def _decoded_time(input_otio): + return opentime.RationalTime( + input_otio['value'], + input_otio['rate'] + ) + + +def _decoded_time_range(input_otio): + return opentime.TimeRange( + input_otio['start_time'], + input_otio['duration'] + ) + + +def _decoded_transform(input_otio): + return opentime.TimeTransform( + input_otio['offset'], + input_otio['scale'] + ) +# @} + + +# Map of explicit decoder functions to schema labels (for opentime) +# because opentime is implemented with no knowledge of OTIO, it doesn't use the +# same pattern as SerializableObject. +_DECODER_FUNCTION_MAP = { + 'RationalTime.1': _decoded_time, + 'TimeRange.1': _decoded_time_range, + 'TimeTransform.1': _decoded_transform, +} + + +def _as_otio(dct): + """ Specialized JSON decoder for OTIO base Objects. """ + + if "OTIO_SCHEMA" in dct: + schema_label = dct["OTIO_SCHEMA"] + + if schema_label in _DECODER_FUNCTION_MAP: + return _DECODER_FUNCTION_MAP[schema_label](dct) + + schema_name = type_registry.schema_name_from_label(schema_label) + schema_version = type_registry.schema_version_from_label(schema_label) + del dct["OTIO_SCHEMA"] + + return type_registry.instance_from_schema( + schema_name, + schema_version, + dct + ) + + return dct + + +def deserialize_json_from_string(otio_string): + """ Deserialize a string containing JSON to OTIO objects. """ + + return json.loads(otio_string, object_hook=_as_otio) + + +def deserialize_json_from_file(otio_filepath): + """ Deserialize the file at otio_filepath containing JSON to OTIO. """ + + with open(otio_filepath, 'r') as file_contents: + result = deserialize_json_from_string(file_contents.read()) + result._json_path = otio_filepath + return result diff --git a/openpype/vendor/python/python_2/opentimelineio/core/media_reference.py b/openpype/vendor/python/python_2/opentimelineio/core/media_reference.py new file mode 100644 index 0000000000..ac34852613 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/media_reference.py @@ -0,0 +1,102 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Media Reference Classes and Functions.""" + +from .. import ( + opentime, +) +from . import ( + type_registry, + serializable_object, +) + +import copy + + +@type_registry.register_type +class MediaReference(serializable_object.SerializableObject): + """Base Media Reference Class. + + Currently handles string printing the child classes, which expose interface + into its data dictionary. + + The requirement is that the schema is named so that external systems can + fetch the required information correctly. + """ + _serializable_label = "MediaReference.1" + _name = "MediaReference" + + def __init__( + self, + name=None, + available_range=None, + metadata=None + ): + super(MediaReference, self).__init__() + + self.name = name + self.available_range = copy.deepcopy(available_range) + self.metadata = copy.deepcopy(metadata) or {} + + name = serializable_object.serializable_field( + "name", + doc="Name of this media reference." + ) + available_range = serializable_object.serializable_field( + "available_range", + opentime.TimeRange, + doc="Available range of media in this media reference." + ) + metadata = serializable_object.serializable_field( + "metadata", + dict, + doc="Metadata dictionary." + ) + + @property + def is_missing_reference(self): + return False + + def __str__(self): + return "{}({}, {}, {})".format( + self._name, + repr(self.name), + repr(self.available_range), + repr(self.metadata) + ) + + def __repr__(self): + return ( + "otio.schema.{}(" + "name={}," + " available_range={}," + " metadata={}" + ")" + ).format( + self._name, + repr(self.name), + repr(self.available_range), + repr(self.metadata) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/core/serializable_object.py b/openpype/vendor/python/python_2/opentimelineio/core/serializable_object.py new file mode 100644 index 0000000000..27032569b0 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/serializable_object.py @@ -0,0 +1,219 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implements the otio.core.SerializableObject""" + +import copy + +from . import ( + type_registry, +) + + +class SerializableObject(object): + """Base object for things that can be [de]serialized to/from .otio files. + + To define a new child class of this, you inherit from it and also use the + register_type decorator. Then you use the serializable_field function + above to create attributes that can be serialized/deserialized. + + You can use the upgrade_function_for decorator to upgrade older schemas + to newer ones. + + Finally, if you're in the process of upgrading schemas and you want to + catch code that refers to old attribute names, you can use the + deprecated_field function. This raises an exception if code attempts to + read or write to that attribute. After testing and before pushing, please + remove references to deprecated_field. + + For example + + >>> import opentimelineio as otio + + >>> @otio.core.register_type + ... class ExampleChild(otio.core.SerializableObject): + ... _serializable_label = "ExampleChild.7" + ... child_data = otio.core.serializable_field("child_data", int) + + # @TODO: delete once testing shows nothing is referencing this. + >>> old_child_data_name = otio.core.deprecated_field() + + >>> @otio.core.upgrade_function_for(ExampleChild, 3) + ... def upgrade_child_to_three(_data): + ... return {"child_data" : _data["old_child_data_name"]} + """ + + # Every child must define a _serializable_label attribute. + # This attribute is a string in the form of: "SchemaName.VersionNumber" + # Where VersionNumber is an integer. + # You can use the classmethods .schema_name() and .schema_version() to + # query these fields. + _serializable_label = None + _class_path = "core.SerializableObject" + + def __init__(self): + self._data = {} + + # @{ "Reference Type" semantics for SerializableObject + # We think of the SerializableObject as a reference type - by default + # comparison is pointer comparison, but you can use 'is_equivalent_to' to + # check if the contents of the SerializableObject are the same as some + # other SerializableObject's contents. + # + # Implicitly: + # def __eq__(self, other): + # return self is other + + def is_equivalent_to(self, other): + """Returns true if the contents of self and other match.""" + + try: + if self._data == other._data: + return True + + # XXX: Gross hack takes OTIO->JSON String->Python Dictionaries + # + # using the serializer ensures that we only compare fields that are + # serializable, which is how we define equivalence. + # + # we use json.loads() to turn the string back into dictionaries + # so we can use python's equivalence for things like floating + # point numbers (ie 5.0 == 5) without having to do string + # processing. + + from . import json_serializer + import json + + lhs_str = json_serializer.serialize_json_to_string(self) + lhs = json.loads(lhs_str) + + rhs_str = json_serializer.serialize_json_to_string(other) + rhs = json.loads(rhs_str) + + return (lhs == rhs) + except AttributeError: + return False + # @} + + def _update(self, d): + """Like the dictionary .update() method. + + Update the _data dictionary of this SerializableObject with the ._data + of d if d is a SerializableObject or if d is a dictionary, d itself. + """ + + if isinstance(d, SerializableObject): + self._data.update(d._data) + else: + self._data.update(d) + + @classmethod + def schema_name(cls): + return type_registry.schema_name_from_label( + cls._serializable_label + ) + + @classmethod + def schema_version(cls): + return type_registry.schema_version_from_label( + cls._serializable_label + ) + + @property + def is_unknown_schema(self): + # in general, SerializableObject will have a known schema + # but UnknownSchema subclass will redefine this property to be True + return False + + def __copy__(self): + raise NotImplementedError( + "Shallow copying is not permitted. Use a deep copy." + ) + + def __deepcopy__(self, md): + result = type(self)() + result._data = copy.deepcopy(self._data, md) + + return result + + def deepcopy(self): + return self.__deepcopy__({}) + + +def serializable_field(name, required_type=None, doc=None): + """Create a serializable_field for child classes of SerializableObject. + + Convienence function for adding attributes to child classes of + SerializableObject in such a way that they will be serialized/deserialized + automatically. + + Use it like this: + class foo(SerializableObject): + bar = serializable_field("bar", required_type=int, doc="example") + + This would indicate that class "foo" has a serializable field "bar". So: + f = foo() + f.bar = "stuff" + + # serialize & deserialize + otio_json = otio.adapters.from_name("otio") + f2 = otio_json.read_from_string(otio_json.write_to_string(f)) + + # fields should be equal + f.bar == f2.bar + + Additionally, the "doc" field will become the documentation for the + property. + """ + + def getter(self): + return self._data[name] + + def setter(self, val): + # always allow None values regardless of value of required_type + if required_type is not None and val is not None: + if not isinstance(val, required_type): + raise TypeError( + "attribute '{}' must be an instance of '{}', not: {}".format( + name, + required_type, + type(val) + ) + ) + + self._data[name] = val + + return property(getter, setter, doc=doc) + + +def deprecated_field(): + """ For marking attributes on a SerializableObject deprecated. """ + + def getter(self): + raise DeprecationWarning + + def setter(self, val): + raise DeprecationWarning + + return property(getter, setter, doc="Deprecated field, do not use.") diff --git a/openpype/vendor/python/python_2/opentimelineio/core/type_registry.py b/openpype/vendor/python/python_2/opentimelineio/core/type_registry.py new file mode 100644 index 0000000000..de4824c42d --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/type_registry.py @@ -0,0 +1,152 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Core type registry system for registering OTIO types for serialization.""" + +from .. import ( + exceptions +) + + +# Types decorate use register_type() to insert themselves into this map +_OTIO_TYPES = {} + +# maps types to a map of versions to upgrade functions +_UPGRADE_FUNCTIONS = {} + + +def schema_name_from_label(label): + """Return the schema name from the label name.""" + + return label.split(".")[0] + + +def schema_version_from_label(label): + """Return the schema version from the label name.""" + + return int(label.split(".")[1]) + + +def schema_label_from_name_version(schema_name, schema_version): + """Return the serializeable object schema label given the name and version.""" + + return "{}.{}".format(schema_name, schema_version) + + +def register_type(classobj, schemaname=None): + """ Register a class to a Schema Label. + + Normally this is used as a decorator. However, in special cases where a + type has been renamed, you might need to register the new type to multiple + schema names. To do this: + + >>> @core.register_type + ... class MyNewClass(...): + ... pass + + >>> core.register_type(MyNewClass, "MyOldName") + + This will parse the old schema name into the new class type. You may also + need to write an upgrade function if the schema itself has changed. + """ + + if schemaname is None: + schemaname = schema_name_from_label(classobj._serializable_label) + + _OTIO_TYPES[schemaname] = classobj + + return classobj + + +def upgrade_function_for(cls, version_to_upgrade_to): + """Decorator for identifying schema class upgrade functions. + + Example + >>> @upgrade_function_for(MyClass, 5) + ... def upgrade_to_version_five(data): + ... pass + + This will get called to upgrade a schema of MyClass to version 5. My class + must be a class deriving from otio.core.SerializableObject. + + The upgrade function should take a single argument - the dictionary to + upgrade, and return a dictionary with the fields upgraded. + + Remember that you don't need to provide an upgrade function for upgrades + that add or remove fields, only for schema versions that change the field + names. + """ + + def decorator_func(func): + """ Decorator for marking upgrade functions """ + + _UPGRADE_FUNCTIONS.setdefault(cls, {})[version_to_upgrade_to] = func + + return func + + return decorator_func + + +def instance_from_schema(schema_name, schema_version, data_dict): + """Return an instance, of the schema from data in the data_dict.""" + + if schema_name not in _OTIO_TYPES: + from .unknown_schema import UnknownSchema + + # create an object of UnknownSchema type to represent the data + schema_label = schema_label_from_name_version(schema_name, schema_version) + data_dict[UnknownSchema._original_label] = schema_label + unknown_label = UnknownSchema._serializable_label + schema_name = schema_name_from_label(unknown_label) + schema_version = schema_version_from_label(unknown_label) + + cls = _OTIO_TYPES[schema_name] + + schema_version = int(schema_version) + if cls.schema_version() < schema_version: + raise exceptions.UnsupportedSchemaError( + "Schema '{}' has highest version available '{}', which is lower " + "than requested schema version '{}'".format( + schema_name, + cls.schema_version(), + schema_version + ) + ) + + if cls.schema_version() != schema_version: + # since the keys are the versions to upgrade to, sorting the keys + # before iterating through them should ensure that upgrade functions + # are called in order. + for version, upgrade_func in sorted( + _UPGRADE_FUNCTIONS[cls].items() + ): + if version < schema_version: + continue + + data_dict = upgrade_func(data_dict) + + obj = cls() + obj._update(data_dict) + + return obj diff --git a/openpype/vendor/python/python_2/opentimelineio/core/unknown_schema.py b/openpype/vendor/python/python_2/opentimelineio/core/unknown_schema.py new file mode 100644 index 0000000000..94c187710e --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/core/unknown_schema.py @@ -0,0 +1,50 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +""" +Implementation of the UnknownSchema schema. +""" + +from .serializable_object import SerializableObject +from .type_registry import register_type + + +@register_type +class UnknownSchema(SerializableObject): + """Represents an object whose schema is unknown to us.""" + + _serializable_label = "UnknownSchema.1" + _name = "UnknownSchema" + _original_label = "UnknownSchemaOriginalLabel" + + @property + def is_unknown_schema(self): + return True + + @property + def data(self): + """Exposes the data dictionary of the underlying SerializableObject + directly. + """ + return self._data diff --git a/openpype/vendor/python/python_2/opentimelineio/exceptions.py b/openpype/vendor/python/python_2/opentimelineio/exceptions.py new file mode 100644 index 0000000000..7726f2ef71 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/exceptions.py @@ -0,0 +1,89 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Exception classes for OpenTimelineIO""" + + +class OTIOError(Exception): + pass + + +class CouldNotReadFileError(OTIOError): + pass + + +class NoKnownAdapterForExtensionError(OTIOError): + pass + + +class ReadingNotSupportedError(OTIOError): + pass + + +class WritingNotSupportedError(OTIOError): + pass + + +class NotSupportedError(OTIOError): + pass + + +class InvalidSerializableLabelError(OTIOError): + pass + + +class CannotComputeAvailableRangeError(OTIOError): + pass + + +class AdapterDoesntSupportFunctionError(OTIOError): + pass + + +class UnsupportedSchemaError(OTIOError): + pass + + +class NotAChildError(OTIOError): + pass + + +class InstancingNotAllowedError(OTIOError): + pass + + +class TransitionFollowingATransitionError(OTIOError): + pass + + +class MisconfiguredPluginError(OTIOError): + pass + + +class CannotTrimTransitionsError(OTIOError): + pass + + +class NoDefaultMediaLinkerError(OTIOError): + pass diff --git a/openpype/vendor/python/python_2/opentimelineio/hooks.py b/openpype/vendor/python/python_2/opentimelineio/hooks.py new file mode 100644 index 0000000000..311154553d --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/hooks.py @@ -0,0 +1,174 @@ +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +from . import ( + plugins, + core, +) + +__doc__ = """ +HookScripts are plugins that run at defined points ("Hooks"). + +They expose a hook_function with signature: +hook_function :: otio.schema.Timeline, Dict -> otio.schema.Timeline + +Both hook scripts and the hooks they attach to are defined in the plugin +manifest. + +You can attach multiple hook scripts to a hook. They will be executed in list +order, first to last. + +They are defined by the manifests HookScripts and hooks areas. + +>>> +{ + "OTIO_SCHEMA" : "PluginManifest.1", + "hook_scripts" : [ + { + "OTIO_SCHEMA" : "HookScript.1", + "name" : "example hook", + "execution_scope" : "in process", + "filepath" : "example.py" + } + ], + "hooks" : { + "pre_adapter_write" : ["example hook"], + "post_adapter_read" : [] + } +} + +The 'hook_scripts' area loads the python modules with the 'hook_function's to +call in them. The 'hooks' area defines the hooks (and any associated +scripts). You can further query and modify these from python. + +>>> import opentimelineio as otio +... hook_list = otio.hooks.scripts_attached_to("some_hook") # -> ['a','b','c'] +... +... # to run the hook scripts: +... otio.hooks.run("some_hook", some_timeline, optional_argument_dict) + +This will pass (some_timeline, optional_argument_dict) to 'a', which will +a new timeline that will get passed into 'b' with optional_argument_dict, +etc. + +To Edit the order, change the order in the list: + +>>> hook_list[0], hook_list[2] = hook_list[2], hook_list[0] +... print hook_list # ['c','b','a'] + +Now c will run, then b, then a. + +To delete a function the list: + +>>> del hook_list[1] +""" + + +@core.register_type +class HookScript(plugins.PythonPlugin): + _serializable_label = "HookScript.1" + + def __init__( + self, + name=None, + execution_scope=None, + filepath=None, + ): + """HookScript plugin constructor.""" + + super(HookScript, self).__init__(name, execution_scope, filepath) + + def run(self, in_timeline, argument_map={}): + """Run the hook_function associated with this plugin.""" + + # @TODO: should in_timeline be passed in place? or should a copy be + # made? + return self._execute_function( + "hook_function", + in_timeline=in_timeline, + argument_map=argument_map + ) + + def __str__(self): + return "HookScript({}, {}, {})".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath) + ) + + def __repr__(self): + return ( + "otio.hooks.HookScript(" + "name={}, " + "execution_scope={}, " + "filepath={}" + ")".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath) + ) + ) + + +def names(): + """Return a list of all the registered hooks.""" + + return plugins.ActiveManifest().hooks.keys() + + +def available_hookscript_names(): + """Return the names of HookScripts that have been registered.""" + + return [hs.name for hs in plugins.ActiveManifest().hook_scripts] + + +def available_hookscripts(): + """Return the HookScripts objects that have been registered.""" + return plugins.ActiveManifest().hook_scripts + + +def scripts_attached_to(hook): + """Return an editable list of all the hook scriptss that are attached to + the specified hook, in execution order. Changing this list will change the + order that scripts run in, and deleting a script will remove it from + executing + """ + + # @TODO: Should this return a copy? + return plugins.ActiveManifest().hooks[hook] + + +def run(hook, tl, extra_args=None): + """Run all the scripts associated with hook, passing in tl and extra_args. + + Will return the return value of the last hook script. + + If no hookscripts are defined, returns tl. + """ + + hook_scripts = plugins.ActiveManifest().hooks[hook] + for name in hook_scripts: + hs = plugins.ActiveManifest().from_name(name, "hook_scripts") + tl = hs.run(tl, extra_args) + return tl diff --git a/openpype/vendor/python/python_2/opentimelineio/media_linker.py b/openpype/vendor/python/python_2/opentimelineio/media_linker.py new file mode 100644 index 0000000000..25473ac1d5 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/media_linker.py @@ -0,0 +1,169 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +""" MediaLinker plugins fire after an adapter has read a file in order to +produce MediaReferences that point at valid, site specific media. + +They expose a "link_media_reference" function with the signature: +link_media_reference :: otio.schema.Clip -> otio.core.MediaReference + +or: + def linked_media_reference(from_clip): + result = otio.core.MediaReference() # whichever subclass + # do stuff + return result + +To get context information, they can inspect the metadata on the clip and on +the media reference. The .parent() method can be used to find the containing +track if metadata is stored there. + +Please raise an instance (or child instance) of +otio.exceptions.CannotLinkMediaError() if there is a problem linking the media. + +For example: + for clip in timeline.each_clip(): + try: + new_mr = otio.media_linker.linked_media_reference(clip) + clip.media_reference = new_mr + except otio.exceptions.CannotLinkMediaError: + # or report the error + pass +""" + +import os + +from . import ( + exceptions, + plugins, + core, +) + + +# Enum describing different media linker policies +class MediaLinkingPolicy: + DoNotLinkMedia = "__do_not_link_media" + ForceDefaultLinker = "__default" + + +# @TODO: wrap this up in the plugin system somehow? automatically generate? +def available_media_linker_names(): + """Return a string list of the available media linker plugins.""" + + return [str(adp.name) for adp in plugins.ActiveManifest().media_linkers] + + +def from_name(name): + """Fetch the media linker object by the name of the adapter directly.""" + + if name == MediaLinkingPolicy.ForceDefaultLinker or not name: + name = os.environ.get("OTIO_DEFAULT_MEDIA_LINKER", None) + + if not name: + return None + + # @TODO: make this handle the enums + try: + return plugins.ActiveManifest().from_name( + name, + kind_list="media_linkers" + ) + except exceptions.NotSupportedError: + raise exceptions.NotSupportedError( + "media linker not supported: {}, available: {}".format( + name, + available_media_linker_names() + ) + ) + + +def default_media_linker(): + try: + return os.environ['OTIO_DEFAULT_MEDIA_LINKER'] + except KeyError: + raise exceptions.NoDefaultMediaLinkerError( + "No default Media Linker set in $OTIO_DEFAULT_MEDIA_LINKER" + ) + + +def linked_media_reference( + target_clip, + media_linker_name=MediaLinkingPolicy.ForceDefaultLinker, + media_linker_argument_map=None +): + media_linker = from_name(media_linker_name) + + if not media_linker: + return target_clip + + # @TODO: connect this argument map up to the function call through to the + # real linker + if not media_linker_argument_map: + media_linker_argument_map = {} + + return media_linker.link_media_reference( + target_clip, + media_linker_argument_map + ) + + +@core.register_type +class MediaLinker(plugins.PythonPlugin): + _serializable_label = "MediaLinker.1" + + def __init__( + self, + name=None, + execution_scope=None, + filepath=None, + ): + super(MediaLinker, self).__init__(name, execution_scope, filepath) + + def link_media_reference(self, in_clip, media_linker_argument_map=None): + media_linker_argument_map = media_linker_argument_map or {} + + return self._execute_function( + "link_media_reference", + in_clip=in_clip, + media_linker_argument_map=media_linker_argument_map + ) + + def __str__(self): + return "MediaLinker({}, {}, {})".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath) + ) + + def __repr__(self): + return ( + "otio.media_linker.MediaLinker(" + "name={}, " + "execution_scope={}, " + "filepath={}" + ")".format( + repr(self.name), + repr(self.execution_scope), + repr(self.filepath) + ) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/opentime.py b/openpype/vendor/python/python_2/opentimelineio/opentime.py new file mode 100644 index 0000000000..e7e58b9475 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/opentime.py @@ -0,0 +1,856 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Library for expressing and transforming time. + +NOTE: This module is written specifically with a future port to C in mind. +When ported to C, Time will be a struct and these functions should be very +simple. +""" + +import math +import copy + + +VALID_NON_DROPFRAME_TIMECODE_RATES = ( + 1, + 12, + 23.976, + 23.98, + (24000 / 1001.0), + 24, + 25, + 30, + 29.97, + (30000 / 1001.0), + 48, + 50, + 59.94, + (60000 / 1001.0), + 60, +) + +VALID_DROPFRAME_TIMECODE_RATES = ( + 29.97, + (30000 / 1001.0), + 59.94, + (60000 / 1001.0), +) + +VALID_TIMECODE_RATES = ( + VALID_NON_DROPFRAME_TIMECODE_RATES + VALID_DROPFRAME_TIMECODE_RATES) + +_fn_cache = object.__setattr__ + + +class RationalTime(object): + """ Represents an instantaneous point in time, value * (1/rate) seconds + from time 0seconds. + """ + + # Locks RationalTime instances to only these attributes + __slots__ = ['value', 'rate'] + + def __init__(self, value=0.0, rate=1.0): + _fn_cache(self, "value", value) + _fn_cache(self, "rate", rate) + + def __setattr__(self, key, val): + """Enforces immutability """ + raise AttributeError("RationalTime is Immutable.") + + def __copy__(self, memodict=None): + return RationalTime(self.value, self.rate) + + # Always deepcopy, since we want this class to behave like a value type + __deepcopy__ = __copy__ + + def rescaled_to(self, new_rate): + """Returns the time for this time converted to new_rate""" + + try: + new_rate = new_rate.rate + except AttributeError: + pass + + if self.rate == new_rate: + return copy.copy(self) + + return RationalTime( + self.value_rescaled_to(new_rate), + new_rate + ) + + def value_rescaled_to(self, new_rate): + """Returns the time value for self converted to new_rate""" + + try: + new_rate = new_rate.rate + except AttributeError: + pass + + if new_rate == self.rate: + return self.value + + # TODO: This math probably needs some overrun protection + try: + return float(self.value) * float(new_rate) / float(self.rate) + except (AttributeError, TypeError, ValueError): + raise TypeError( + "Sorry, RationalTime cannot be rescaled to a value of type " + "'{}', only RationalTime and numbers are supported.".format( + type(new_rate) + ) + ) + + def almost_equal(self, other, delta=0.0): + try: + rescaled_value = self.value_rescaled_to(other.rate) + return abs(rescaled_value - other.value) <= delta + + except AttributeError: + return False + + def __add__(self, other): + """Returns a RationalTime object that is the sum of self and other. + + If self and other have differing time rates, the result will have the + have the rate of the faster time. + """ + + try: + if self.rate == other.rate: + return RationalTime(self.value + other.value, self.rate) + except AttributeError: + if not isinstance(other, RationalTime): + raise TypeError( + "RationalTime may only be added to other objects of type " + "RationalTime, not {}.".format(type(other)) + ) + raise + + if self.rate > other.rate: + scale = self.rate + value = self.value + other.value_rescaled_to(scale) + else: + scale = other.rate + value = self.value_rescaled_to(scale) + other.value + + return RationalTime(value, scale) + + # because RationalTime is immutable, += is sugar around + + __iadd__ = __add__ + + def __sub__(self, other): + """Returns a RationalTime object that is self - other. + + If self and other have differing time rates, the result will have the + have the rate of the faster time. + """ + + try: + if self.rate == other.rate: + return RationalTime(self.value - other.value, self.rate) + except AttributeError: + if not isinstance(other, RationalTime): + raise TypeError( + "RationalTime may only be added to other objects of type " + "RationalTime, not {}.".format(type(other)) + ) + raise + + if self.rate > other.rate: + scale = self.rate + value = self.value - other.value_rescaled_to(scale) + else: + scale = other.rate + value = self.value_rescaled_to(scale) - other.value + + return RationalTime(value=value, rate=scale) + + def _comparable_floats(self, other): + """Returns a tuple of two floats, (self, other), which are suitable + for comparison. + + If other is not of a type that can be compared, TypeError is raised + """ + try: + return ( + float(self.value) / self.rate, + float(other.value) / other.rate + ) + except AttributeError: + if not isinstance(other, RationalTime): + raise TypeError( + "RationalTime can only be compared to other objects of type " + "RationalTime, not {}".format(type(other)) + ) + raise + + def __gt__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self > f_other + + def __lt__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self < f_other + + def __le__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self <= f_other + + def __ge__(self, other): + f_self, f_other = self._comparable_floats(other) + return f_self >= f_other + + def __repr__(self): + return ( + "otio.opentime.RationalTime(value={value}," + " rate={rate})".format( + value=repr(self.value), + rate=repr(self.rate), + ) + ) + + def __str__(self): + return "RationalTime({}, {})".format( + str(self.value), + str(self.rate) + ) + + def __eq__(self, other): + try: + return self.value_rescaled_to(other.rate) == other.value + except AttributeError: + return False + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash((self.value, self.rate)) + + +class TimeTransform(object): + """1D Transform for RationalTime. Has offset and scale.""" + + def __init__(self, offset=RationalTime(), scale=1.0, rate=None): + self.offset = copy.copy(offset) + self.scale = float(scale) + self.rate = float(rate) if rate else None + + def applied_to(self, other): + if isinstance(other, TimeRange): + return range_from_start_end_time( + start_time=self.applied_to(other.start_time), + end_time_exclusive=self.applied_to(other.end_time_exclusive()) + ) + + target_rate = self.rate if self.rate is not None else other.rate + if isinstance(other, TimeTransform): + return TimeTransform( + offset=self.offset + other.offset, + scale=self.scale * other.scale, + rate=target_rate + ) + elif isinstance(other, RationalTime): + value = other.value * self.scale + result = RationalTime(value, other.rate) + self.offset + if target_rate is not None: + result = result.rescaled_to(target_rate) + + return result + else: + raise TypeError( + "TimeTransform can only be applied to a TimeTransform or " + "RationalTime, not a {}".format(type(other)) + ) + + def __repr__(self): + return ( + "otio.opentime.TimeTransform(offset={}, scale={}, rate={})".format( + repr(self.offset), + repr(self.scale), + repr(self.rate) + ) + ) + + def __str__(self): + return ( + "TimeTransform({}, {}, {})".format( + str(self.offset), + str(self.scale), + str(self.rate) + ) + ) + + def __eq__(self, other): + try: + return ( + (self.offset, self.scale, self.rate) == + (other.offset, other.scale, self.rate) + ) + except AttributeError: + return False + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash((self.offset, self.scale, self.rate)) + + +class BoundStrategy(object): + """Different bounding strategies for TimeRange """ + + Free = 1 + Clamp = 2 + + +class TimeRange(object): + """Contains a range of time, starting (and including) start_time and + lasting duration.value * (1/duration.rate) seconds. + + A 0 duration TimeRange is the same as a RationalTime, and contains only the + start_time of the TimeRange. + """ + + __slots__ = ['start_time', 'duration'] + + def __init__(self, start_time=None, duration=None): + if not isinstance(start_time, RationalTime) and start_time is not None: + raise TypeError( + "start_time must be a RationalTime, not " + "'{}'".format(start_time) + ) + if ( + duration is not None and ( + not isinstance(duration, RationalTime) + or duration.value < 0.0 + ) + ): + raise TypeError( + "duration must be a RationalTime with value >= 0, not " + "'{}'".format(duration) + ) + + # if the start time has not been passed in + if not start_time: + if duration: + # ...get the rate from the duration + start_time = RationalTime(rate=duration.rate) + else: + # otherwise use the default + start_time = RationalTime() + _fn_cache(self, "start_time", copy.copy(start_time)) + + if not duration: + # ...get the rate from the start_time + duration = RationalTime(rate=start_time.rate) + _fn_cache(self, "duration", copy.copy(duration)) + + def __setattr__(self, key, val): + raise AttributeError("TimeRange is Immutable.") + + def __copy__(self, memodict=None): + # Construct a new one directly to avoid the overhead of deepcopy + return TimeRange( + copy.copy(self.start_time), + copy.copy(self.duration) + ) + + # Always deepcopy, since we want this class to behave like a value type + __deepcopy__ = __copy__ + + def end_time_inclusive(self): + """The time of the last sample that contains data in the TimeRange. + + If the TimeRange goes from (0, 24) w/ duration (10, 24), this will be + (9, 24) + + If the TimeRange goes from (0, 24) w/ duration (10.5, 24): + (10, 24) + + In other words, the last frame with data (however fractional). + """ + + if ( + self.end_time_exclusive() - self.start_time.rescaled_to(self.duration) + ).value > 1: + + result = ( + self.end_time_exclusive() - RationalTime(1, self.start_time.rate) + ) + + # if the duration's value has a fractional component + if self.duration.value != math.floor(self.duration.value): + result = RationalTime( + math.floor(self.end_time_exclusive().value), + result.rate + ) + + return result + else: + return copy.deepcopy(self.start_time) + + def end_time_exclusive(self): + """"Time of the first sample outside the time range. + + If Start Frame is 10 and duration is 5, then end_time_exclusive is 15, + even though the last time with data in this range is 14. + + If Start Frame is 10 and duration is 5.5, then end_time_exclusive is + 15.5, even though the last time with data in this range is 15. + """ + + return self.duration + self.start_time.rescaled_to(self.duration) + + def extended_by(self, other): + """Construct a new TimeRange that is this one extended by another.""" + + if not isinstance(other, TimeRange): + raise TypeError( + "extended_by requires rtime be a TimeRange, not a '{}'".format( + type(other) + ) + ) + + start_time = min(self.start_time, other.start_time) + new_end_time = max( + self.end_time_exclusive(), + other.end_time_exclusive() + ) + duration = duration_from_start_end_time(start_time, new_end_time) + return TimeRange(start_time, duration) + + # @TODO: remove? + def clamped( + self, + other, + start_bound=BoundStrategy.Free, + end_bound=BoundStrategy.Free + ): + """Clamp 'other' (either a RationalTime or a TimeRange), according to + self.start_time/end_time_exclusive and the bound arguments. + """ + + if isinstance(other, RationalTime): + if start_bound == BoundStrategy.Clamp: + other = max(other, self.start_time) + if end_bound == BoundStrategy.Clamp: + # @TODO: this should probably be the end_time_inclusive, + # not exclusive + other = min(other, self.end_time_exclusive()) + return other + elif isinstance(other, TimeRange): + start_time = other.start_time + end = other.end_time_exclusive() + if start_bound == BoundStrategy.Clamp: + start_time = max(other.start_time, self.start_time) + if end_bound == BoundStrategy.Clamp: + end = min(self.end_time_exclusive(), end) + duration = duration_from_start_end_time(start_time, end) + return TimeRange(start_time, duration) + else: + raise TypeError( + "TimeRange can only be applied to RationalTime objects, not " + "{}".format(type(other)) + ) + return self + + def contains(self, other): + """Return true if self completely contains other. + + (RationalTime or TimeRange) + """ + + if isinstance(other, RationalTime): + return ( + self.start_time <= other and other < self.end_time_exclusive()) + elif isinstance(other, TimeRange): + return ( + self.start_time <= other.start_time and + self.end_time_exclusive() >= other.end_time_exclusive() + ) + raise TypeError( + "contains only accepts on otio.opentime.RationalTime or " + "otio.opentime.TimeRange, not {}".format(type(other)) + ) + + def overlaps(self, other): + """Return true if self overlaps any part of other. + + (RationalTime or TimeRange) + """ + + if isinstance(other, RationalTime): + return self.contains(other) + elif isinstance(other, TimeRange): + return ( + ( + self.start_time < other.end_time_exclusive() and + other.start_time < self.end_time_exclusive() + ) + ) + raise TypeError( + "overlaps only accepts on otio.opentime.RationalTime or " + "otio.opentime.TimeRange, not {}".format(type(other)) + ) + + def __hash__(self): + return hash((self.start_time, self.duration)) + + def __eq__(self, rhs): + try: + return ( + (self.start_time, self.duration) == + (rhs.start_time, rhs.duration) + ) + except AttributeError: + return False + + def __ne__(self, rhs): + return not (self == rhs) + + def __repr__(self): + return ( + "otio.opentime.TimeRange(start_time={}, duration={})".format( + repr(self.start_time), + repr(self.duration), + ) + ) + + def __str__(self): + return ( + "TimeRange({}, {})".format( + str(self.start_time), + str(self.duration), + ) + ) + + +def from_frames(frame, fps): + """Turn a frame number and fps into a time object. + :param frame: (:class:`int`) Frame number. + :param fps: (:class:`float`) Frame-rate for the (:class:`RationalTime`) instance. + + :return: (:class:`RationalTime`) Instance for the frame and fps provided. + """ + + return RationalTime(int(frame), fps) + + +def to_frames(time_obj, fps=None): + """Turn a RationalTime into a frame number.""" + + if not fps or time_obj.rate == fps: + return int(time_obj.value) + + return int(time_obj.value_rescaled_to(fps)) + + +def validate_timecode_rate(rate): + """Check if rate is of valid type and value. + Raises (:class:`TypeError` for wrong type of rate. + Raises (:class:`VaueError`) for invalid rate value. + + :param rate: (:class:`int`) or (:class:`float`) The frame rate in question + """ + if not isinstance(rate, (int, float)): + raise TypeError( + "rate must be or not {t}".format(t=type(rate))) + + if rate not in VALID_TIMECODE_RATES: + raise ValueError( + '{rate} is not a valid frame rate, ' + 'Please use one of these: {valid}'.format( + rate=rate, + valid=VALID_TIMECODE_RATES)) + + +def from_timecode(timecode_str, rate): + """Convert a timecode string into a RationalTime. + + :param timecode_str: (:class:`str`) A colon-delimited timecode. + :param rate: (:class:`float`) The frame-rate to calculate timecode in + terms of. + + :return: (:class:`RationalTime`) Instance for the timecode provided. + """ + # Validate rate + validate_timecode_rate(rate) + + # Check if rate is drop frame + rate_is_dropframe = rate in VALID_DROPFRAME_TIMECODE_RATES + + # Make sure only DF timecodes are treated as such + treat_as_df = rate_is_dropframe and ';' in timecode_str + + # Check if timecode indicates drop frame + if ';' in timecode_str: + if not rate_is_dropframe: + raise ValueError( + 'Timecode "{}" indicates drop-frame rate ' + 'due to the ";" frame divider. ' + 'Passed rate ({}) is of non-drop-frame rate. ' + 'Valid drop-frame rates are: {}'.format( + timecode_str, + rate, + VALID_DROPFRAME_TIMECODE_RATES)) + else: + timecode_str = timecode_str.replace(';', ':') + + hours, minutes, seconds, frames = timecode_str.split(":") + + # Timecode is declared in terms of nominal fps + nominal_fps = int(math.ceil(rate)) + + if int(frames) >= nominal_fps: + raise ValueError( + 'Frame rate mismatch. Timecode "{}" has frames beyond {}.'.format( + timecode_str, nominal_fps - 1)) + + dropframes = 0 + if treat_as_df: + if rate == 29.97: + dropframes = 2 + + elif rate == 59.94: + dropframes = 4 + + # To use for drop frame compensation + total_minutes = int(hours) * 60 + int(minutes) + + # convert to frames + value = ( + ((total_minutes * 60) + int(seconds)) * nominal_fps + int(frames)) - \ + (dropframes * (total_minutes - (total_minutes // 10))) + + return RationalTime(value, rate) + + +def to_timecode(time_obj, rate=None, drop_frame=None): + """Convert a RationalTime into a timecode string. + + :param time_obj: (:class:`RationalTime`) instance to express as timecode. + :param rate: (:class:`float`) The frame-rate to calculate timecode in + terms of. (Default time_obj.rate) + :param drop_frame: (:class:`bool`) ``True`` to make drop-frame timecode, + ``False`` for non-drop. If left ``None``, a format will be guessed + based on rate. + + :return: (:class:`str`) The timecode. + """ + if time_obj is None: + return None + + rate = rate or time_obj.rate + + # Validate rate + validate_timecode_rate(rate) + + # Check if rate is drop frame + rate_is_dropframe = rate in VALID_DROPFRAME_TIMECODE_RATES + if drop_frame and not rate_is_dropframe: + raise ValueError( + "Invalid rate for drop-frame timecode {}".format(time_obj.rate) + ) + + # if in auto-detect for DFTC, use the rate to decide + if drop_frame is None: + drop_frame = rate_is_dropframe + + dropframes = 0 + if drop_frame: + if rate in (29.97, (30000 / 1001.0)): + dropframes = 2 + + elif rate == 59.94: + dropframes = 4 + + # For non-dftc, use the integral frame rate + if not drop_frame: + rate = round(rate) + + # Number of frames in an hour + frames_per_hour = int(round(rate * 60 * 60)) + # Number of frames in a day - timecode rolls over after 24 hours + frames_per_24_hours = frames_per_hour * 24 + # Number of frames per ten minutes + frames_per_10_minutes = int(round(rate * 60 * 10)) + # Number of frames per minute is the round of the framerate * 60 minus + # the number of dropped frames + frames_per_minute = int(round(rate) * 60) - dropframes + + value = time_obj.value + + if value < 0: + raise ValueError( + "Negative values are not supported for converting to timecode.") + + # If frame_number is greater than 24 hrs, next operation will rollover + # clock + value %= frames_per_24_hours + + if drop_frame: + d = value // frames_per_10_minutes + m = value % frames_per_10_minutes + if m > dropframes: + value += (dropframes * 9 * d) + \ + dropframes * ((m - dropframes) // frames_per_minute) + else: + value += dropframes * 9 * d + + nominal_fps = int(math.ceil(rate)) + + frames = value % nominal_fps + seconds = (value // nominal_fps) % 60 + minutes = ((value // nominal_fps) // 60) % 60 + hours = (((value // nominal_fps) // 60) // 60) + + tc = "{HH:02d}:{MM:02d}:{SS:02d}{div}{FF:02d}" + + return tc.format( + HH=int(hours), + MM=int(minutes), + SS=int(seconds), + div=drop_frame and ";" or ":", + FF=int(frames)) + + +def from_time_string(time_str, rate): + """Convert a time with microseconds string into a RationalTime. + + :param time_str: (:class:`str`) A HH:MM:ss.ms time. + :param rate: (:class:`float`) The frame-rate to calculate timecode in + terms of. + + :return: (:class:`RationalTime`) Instance for the timecode provided. + """ + + if ';' in time_str: + raise ValueError('Drop-Frame timecodes not supported.') + + hours, minutes, seconds = time_str.split(":") + microseconds = "0" + if '.' in seconds: + seconds, microseconds = str(seconds).split('.') + microseconds = microseconds[0:6] + seconds = '.'.join([seconds, microseconds]) + time_obj = from_seconds( + float(seconds) + + (int(minutes) * 60) + + (int(hours) * 60 * 60) + ) + return time_obj.rescaled_to(rate) + + +def to_time_string(time_obj): + """ + Convert this timecode to time with microsecond, as formated in FFMPEG + + :return: Number formated string of time + """ + if time_obj is None: + return None + # convert time object to seconds + seconds = to_seconds(time_obj) + + # reformat in time string + time_units_per_minute = 60 + time_units_per_hour = time_units_per_minute * 60 + time_units_per_day = time_units_per_hour * 24 + + days, hour_units = divmod(seconds, time_units_per_day) + hours, minute_units = divmod(hour_units, time_units_per_hour) + minutes, seconds = divmod(minute_units, time_units_per_minute) + microseconds = "0" + seconds = str(seconds) + if '.' in seconds: + seconds, microseconds = str(seconds).split('.') + + # TODO: There are some rollover policy issues for days and hours, + # We need to research these + + return "{hours}:{minutes}:{seconds}.{microseconds}".format( + hours="{n:0{width}d}".format(n=int(hours), width=2), + minutes="{n:0{width}d}".format(n=int(minutes), width=2), + seconds="{n:0{width}d}".format(n=int(seconds), width=2), + microseconds=microseconds[0:6] + ) + + +def from_seconds(seconds): + """Convert a number of seconds into RationalTime""" + + # Note: in the future we may consider adding a preferred rate arg + time_obj = RationalTime(value=seconds, rate=1) + + return time_obj + + +def to_seconds(time_obj): + """ Convert a RationalTime into float seconds """ + return time_obj.value_rescaled_to(1) + + +def from_footage(footage): + raise NotImplementedError + + +def to_footage(time_obj): + raise NotImplementedError + + +def duration_from_start_end_time(start_time, end_time_exclusive): + """Compute duration of samples from first to last. This is not the same as + distance. For example, the duration of a clip from frame 10 to frame 15 + is 6 frames. Result in the rate of start_time. + """ + + # @TODO: what to do when start_time > end_time_exclusive? + + if start_time.rate == end_time_exclusive.rate: + return RationalTime( + end_time_exclusive.value - start_time.value, + start_time.rate + ) + else: + return RationalTime( + ( + end_time_exclusive.value_rescaled_to(start_time) + - start_time.value + ), + start_time.rate + ) + + +# @TODO: create range from start/end [in,ex]clusive +def range_from_start_end_time(start_time, end_time_exclusive): + """Create a TimeRange from start and end RationalTimes.""" + + return TimeRange( + start_time, + duration=duration_from_start_end_time(start_time, end_time_exclusive) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/plugins/__init__.py b/openpype/vendor/python/python_2/opentimelineio/plugins/__init__.py new file mode 100644 index 0000000000..dedb3da37e --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/plugins/__init__.py @@ -0,0 +1,33 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Plugin system for OTIO""" + +# flake8: noqa + +from .python_plugin import PythonPlugin +from .manifest import ( + manifest_from_file, + ActiveManifest, +) diff --git a/openpype/vendor/python/python_2/opentimelineio/plugins/manifest.py b/openpype/vendor/python/python_2/opentimelineio/plugins/manifest.py new file mode 100644 index 0000000000..2a769effec --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/plugins/manifest.py @@ -0,0 +1,282 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of an adapter registry system for OTIO.""" + +import inspect +import logging +import os + +# on some python interpreters, pkg_resources is not available +try: + import pkg_resources +except ImportError: + pkg_resources = None + +from .. import ( + core, + exceptions, +) + + +def manifest_from_file(filepath): + """Read the .json file at filepath into a Manifest object.""" + + result = core.deserialize_json_from_file(filepath) + result.source_files.append(filepath) + result._update_plugin_source(filepath) + return result + + +def manifest_from_string(input_string): + """Deserialize the json string into a manifest object.""" + + result = core.deserialize_json_from_string(input_string) + + # try and get the caller's name + name = "unknown" + stack = inspect.stack() + if len(stack) > 1 and len(stack[1]) > 3: + # filename function name + name = "{}:{}".format(stack[1][1], stack[1][3]) + + # set the value in the manifest + src_string = "call to manifest_from_string() in " + name + result.source_files.append(src_string) + result._update_plugin_source(src_string) + + return result + + +@core.register_type +class Manifest(core.SerializableObject): + """Defines an OTIO plugin Manifest. + + This is an internal OTIO implementation detail. A manifest tracks a + collection of adapters and allows finding specific adapters by suffix + + For writing your own adapters, consult: + https://opentimelineio.readthedocs.io/en/latest/tutorials/write-an-adapter.html# + """ + _serializable_label = "PluginManifest.1" + + def __init__(self): + super(Manifest, self).__init__() + self.adapters = [] + self.schemadefs = [] + self.media_linkers = [] + self.source_files = [] + + # hook system stuff + self.hooks = {} + self.hook_scripts = [] + + adapters = core.serializable_field( + "adapters", + type([]), + "Adapters this manifest describes." + ) + schemadefs = core.serializable_field( + "schemadefs", + type([]), + "Schemadefs this manifest describes." + ) + media_linkers = core.serializable_field( + "media_linkers", + type([]), + "Media Linkers this manifest describes." + ) + hooks = core.serializable_field( + "hooks", + type({}), + "Hooks that hooks scripts can be attached to." + ) + hook_scripts = core.serializable_field( + "hook_scripts", + type([]), + "Scripts that can be attached to hooks." + ) + + def extend(self, another_manifest): + """ + Extend the adapters, schemadefs, and media_linkers lists of this manifest + by appending the contents of the corresponding lists of another_manifest. + """ + if another_manifest: + self.adapters.extend(another_manifest.adapters) + self.schemadefs.extend(another_manifest.schemadefs) + self.media_linkers.extend(another_manifest.media_linkers) + self.hook_scripts.extend(another_manifest.hook_scripts) + + for trigger_name, hooks in another_manifest.hooks.items(): + if trigger_name in self.hooks: + self.hooks[trigger_name].extend(hooks) + + def _update_plugin_source(self, path): + """Track the source .json for a given adapter.""" + + for thing in (self.adapters + self.schemadefs + + self.media_linkers + self.hook_scripts): + thing._json_path = path + + def from_filepath(self, suffix): + """Return the adapter object associated with a given file suffix.""" + + for adapter in self.adapters: + if suffix.lower() in adapter.suffixes: + return adapter + raise exceptions.NoKnownAdapterForExtensionError(suffix) + + def adapter_module_from_suffix(self, suffix): + """Return the adapter module associated with a given file suffix.""" + + adp = self.from_filepath(suffix) + return adp.module() + + def from_name(self, name, kind_list="adapters"): + """Return the adapter object associated with a given adapter name.""" + + for thing in getattr(self, kind_list): + if name == thing.name: + return thing + + raise exceptions.NotSupportedError( + "Could not find plugin: '{}' in kind_list: '{}'." + " options: {}".format( + name, + kind_list, + getattr(self, kind_list) + ) + ) + + def adapter_module_from_name(self, name): + """Return the adapter module associated with a given adapter name.""" + + adp = self.from_name(name) + return adp.module() + + def schemadef_module_from_name(self, name): + """Return the schemadef module associated with a given schemadef name.""" + + adp = self.from_name(name, kind_list="schemadefs") + return adp.module() + + +_MANIFEST = None + + +def load_manifest(): + # build the manifest of adapters, starting with builtin adapters + result = manifest_from_file( + os.path.join( + os.path.dirname(os.path.dirname(inspect.getsourcefile(core))), + "adapters", + "builtin_adapters.plugin_manifest.json" + ) + ) + + # layer contrib plugins after built in ones + try: + import opentimelineio_contrib as otio_c + + contrib_manifest = manifest_from_file( + os.path.join( + os.path.dirname(inspect.getsourcefile(otio_c)), + "adapters", + "contrib_adapters.plugin_manifest.json" + ) + ) + result.extend(contrib_manifest) + except ImportError: + pass + + # Discover setuptools-based plugins + if pkg_resources: + for plugin in pkg_resources.iter_entry_points( + "opentimelineio.plugins" + ): + plugin_name = plugin.name + try: + plugin_entry_point = plugin.load() + try: + plugin_manifest = plugin_entry_point.plugin_manifest() + except AttributeError: + if not pkg_resources.resource_exists( + plugin.module_name, + 'plugin_manifest.json' + ): + raise + manifest_stream = pkg_resources.resource_stream( + plugin.module_name, + 'plugin_manifest.json' + ) + plugin_manifest = core.deserialize_json_from_string( + manifest_stream.read().decode('utf-8') + ) + manifest_stream.close() + filepath = pkg_resources.resource_filename( + plugin.module_name, + 'plugin_manifest.json' + ) + plugin_manifest._update_plugin_source(filepath) + + except Exception: + logging.exception( + "could not load plugin: {}".format(plugin_name) + ) + continue + + result.extend(plugin_manifest) + else: + # XXX: Should we print some kind of warning that pkg_resources isn't + # available? + pass + + # read local adapter manifests, if they exist + _local_manifest_path = os.environ.get("OTIO_PLUGIN_MANIFEST_PATH", None) + if _local_manifest_path is not None: + for json_path in _local_manifest_path.split(":"): + if not os.path.exists(json_path): + # XXX: In case error reporting is requested + # print( + # "Warning: OpenTimelineIO cannot access path '{}' from " + # "$OTIO_PLUGIN_MANIFEST_PATH".format(json_path) + # ) + continue + + LOCAL_MANIFEST = manifest_from_file(json_path) + result.extend(LOCAL_MANIFEST) + + # force the schemadefs to load and add to schemadef module namespace + for s in result.schemadefs: + s.module() + return result + + +def ActiveManifest(force_reload=False): + global _MANIFEST + if not _MANIFEST or force_reload: + _MANIFEST = load_manifest() + + return _MANIFEST diff --git a/openpype/vendor/python/python_2/opentimelineio/plugins/python_plugin.py b/openpype/vendor/python/python_2/opentimelineio/plugins/python_plugin.py new file mode 100644 index 0000000000..c749bd5f9d --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/plugins/python_plugin.py @@ -0,0 +1,128 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Base class for OTIO plugins that are exposed by manifests.""" + +import os +import imp + +from .. import ( + core, + exceptions, +) + + +class PythonPlugin(core.SerializableObject): + """A class of plugin that is encoded in a python module, exposed via a + manifest. + """ + + _serializable_label = "PythonPlugin.1" + + def __init__( + self, + name=None, + execution_scope=None, + filepath=None, + ): + super(PythonPlugin, self).__init__() + self.name = name + self.execution_scope = execution_scope + self.filepath = filepath + self._json_path = None + self._module = None + + name = core.serializable_field("name", doc="Adapter name.") + execution_scope = core.serializable_field( + "execution_scope", + str, + doc=( + "Describes whether this adapter is executed in the current python" + " process or in a subshell. Options are: " + "['in process', 'out of process']." + ) + ) + filepath = core.serializable_field( + "filepath", + str, + doc=( + "Absolute path or relative path to adapter module from location of" + " json." + ) + ) + + def module_abs_path(self): + """Return an absolute path to the module implementing this adapter.""" + + filepath = self.filepath + if not os.path.isabs(filepath): + if not self._json_path: + raise exceptions.MisconfiguredPluginError( + "{} plugin is misconfigured, missing json path. " + "plugin: {}".format( + self.name, + repr(self) + ) + ) + + filepath = os.path.join(os.path.dirname(self._json_path), filepath) + + return filepath + + def _imported_module(self, namespace): + """Load the module this plugin points at.""" + + pyname = os.path.splitext(os.path.basename(self.module_abs_path()))[0] + pydir = os.path.dirname(self.module_abs_path()) + + (file_obj, pathname, description) = imp.find_module(pyname, [pydir]) + + with file_obj: + # this will reload the module if it has already been loaded. + mod = imp.load_module( + "opentimelineio.{}.{}".format(namespace, self.name), + file_obj, + pathname, + description + ) + + return mod + + def module(self): + """Return the module object for this adapter. """ + + if not self._module: + self._module = self._imported_module("adapters") + + return self._module + + def _execute_function(self, func_name, **kwargs): + """Execute func_name on this adapter with error checking.""" + + # collects the error handling into a common place. + if not hasattr(self.module(), func_name): + raise exceptions.AdapterDoesntSupportFunctionError( + "Sorry, {} doesn't support {}.".format(self.name, func_name) + ) + return (getattr(self.module(), func_name)(**kwargs)) diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/__init__.py b/openpype/vendor/python/python_2/opentimelineio/schema/__init__.py new file mode 100644 index 0000000000..419f337bf6 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/__init__.py @@ -0,0 +1,75 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +# flake8: noqa + +"""User facing classes.""" + +from .missing_reference import ( + MissingReference +) +from .external_reference import ( + ExternalReference +) +from .clip import ( + Clip, +) +from .track import ( + Track, + TrackKind, + NeighborGapPolicy, +) +from .stack import ( + Stack, +) +from .timeline import ( + Timeline, + timeline_from_clips, +) +from .marker import ( + Marker, + MarkerColor, +) +from .gap import ( + Gap, +) +from .effect import ( + Effect, + TimeEffect, + LinearTimeWarp, + FreezeFrame, +) +from .transition import ( + Transition, + TransitionTypes, +) +from .serializable_collection import ( + SerializableCollection +) +from .generator_reference import ( + GeneratorReference +) +from .schemadef import ( + SchemaDef +) diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/clip.py b/openpype/vendor/python/python_2/opentimelineio/schema/clip.py new file mode 100644 index 0000000000..44d38dfcf1 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/clip.py @@ -0,0 +1,130 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of the Clip class, for pointing at media.""" + +import copy + +from .. import ( + core, + exceptions, +) +from . import ( + missing_reference +) + + +@core.register_type +class Clip(core.Item): + """The base editable object in OTIO. + + Contains a media reference and a trim on that media reference. + """ + + _serializable_label = "Clip.1" + + def __init__( + self, + name=None, + media_reference=None, + source_range=None, + markers=[], + effects=[], + metadata=None, + ): + core.Item.__init__( + self, + name=name, + source_range=source_range, + markers=markers, + effects=effects, + metadata=metadata + ) + + if not media_reference: + media_reference = missing_reference.MissingReference() + self._media_reference = copy.deepcopy(media_reference) + + name = core.serializable_field("name", doc="Name of this clip.") + transform = core.deprecated_field() + _media_reference = core.serializable_field( + "media_reference", + core.MediaReference, + "Media reference to the media this clip represents." + ) + + @property + def media_reference(self): + if self._media_reference is None: + self._media_reference = missing_reference.MissingReference() + return self._media_reference + + @media_reference.setter + def media_reference(self, val): + if val is None: + val = missing_reference.MissingReference() + self._media_reference = val + + def available_range(self): + if not self.media_reference: + raise exceptions.CannotComputeAvailableRangeError( + "No media reference set on clip: {}".format(self) + ) + + if not self.media_reference.available_range: + raise exceptions.CannotComputeAvailableRangeError( + "No available_range set on media reference on clip: {}".format( + self + ) + ) + + return copy.copy(self.media_reference.available_range) + + def __str__(self): + return 'Clip("{}", {}, {}, {})'.format( + self.name, + self.media_reference, + self.source_range, + self.metadata + ) + + def __repr__(self): + return ( + 'otio.schema.Clip(' + 'name={}, ' + 'media_reference={}, ' + 'source_range={}, ' + 'metadata={}' + ')'.format( + repr(self.name), + repr(self.media_reference), + repr(self.source_range), + repr(self.metadata), + ) + ) + + def each_clip(self, search_range=None): + """Yields self.""" + + yield self diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/effect.py b/openpype/vendor/python/python_2/opentimelineio/schema/effect.py new file mode 100644 index 0000000000..61eb4204fa --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/effect.py @@ -0,0 +1,130 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of Effect OTIO class.""" + +from .. import ( + core +) + +import copy + + +@core.register_type +class Effect(core.SerializableObject): + _serializable_label = "Effect.1" + + def __init__( + self, + name=None, + effect_name=None, + metadata=None + ): + super(Effect, self).__init__() + self.name = name + self.effect_name = effect_name + self.metadata = copy.deepcopy(metadata) if metadata else {} + + name = core.serializable_field( + "name", + doc="Name of this effect object. Example: 'BlurByHalfEffect'." + ) + effect_name = core.serializable_field( + "effect_name", + doc="Name of the kind of effect (example: 'Blur', 'Crop', 'Flip')." + ) + metadata = core.serializable_field( + "metadata", + dict, + doc="Metadata dictionary." + ) + + def __str__(self): + return ( + "Effect(" + "{}, " + "{}, " + "{}" + ")".format( + str(self.name), + str(self.effect_name), + str(self.metadata), + ) + ) + + def __repr__(self): + return ( + "otio.schema.Effect(" + "name={}, " + "effect_name={}, " + "metadata={}" + ")".format( + repr(self.name), + repr(self.effect_name), + repr(self.metadata), + ) + ) + + +@core.register_type +class TimeEffect(Effect): + "Base Time Effect Class" + _serializable_label = "TimeEffect.1" + pass + + +@core.register_type +class LinearTimeWarp(TimeEffect): + "A time warp that applies a linear scale across the entire clip" + _serializable_label = "LinearTimeWarp.1" + + def __init__(self, name=None, time_scalar=1, metadata=None): + Effect.__init__( + self, + name=name, + effect_name="LinearTimeWarp", + metadata=metadata + ) + self.time_scalar = time_scalar + + time_scalar = core.serializable_field( + "time_scalar", + doc="Linear time scalar applied to clip. " + "2.0 = double speed, 0.5 = half speed." + ) + + +@core.register_type +class FreezeFrame(LinearTimeWarp): + "Hold the first frame of the clip for the duration of the clip." + _serializable_label = "FreezeFrame.1" + + def __init__(self, name=None, metadata=None): + LinearTimeWarp.__init__( + self, + name=name, + time_scalar=0, + metadata=metadata + ) + self.effect_name = "FreezeFrame" diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/external_reference.py b/openpype/vendor/python/python_2/opentimelineio/schema/external_reference.py new file mode 100644 index 0000000000..87db4d4652 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/external_reference.py @@ -0,0 +1,69 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +""" +Implementation of the ExternalReference media reference schema. +""" + +from .. import ( + core, +) + + +@core.register_type +class ExternalReference(core.MediaReference): + """Reference to media via a url, for example "file:///var/tmp/foo.mov" """ + + _serializable_label = "ExternalReference.1" + _name = "ExternalReference" + + def __init__( + self, + target_url=None, + available_range=None, + metadata=None, + ): + core.MediaReference.__init__( + self, + available_range=available_range, + metadata=metadata + ) + + self.target_url = target_url + + target_url = core.serializable_field( + "target_url", + doc=( + "URL at which this media lives. For local references, use the " + "'file://' format." + ) + ) + + def __str__(self): + return 'ExternalReference("{}")'.format(self.target_url) + + def __repr__(self): + return 'otio.schema.ExternalReference(target_url={})'.format( + repr(self.target_url) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/gap.py b/openpype/vendor/python/python_2/opentimelineio/schema/gap.py new file mode 100644 index 0000000000..4c8165db8f --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/gap.py @@ -0,0 +1,82 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +from .. import ( + core, + opentime, +) + +"""Gap Item - represents a transparent gap in content.""" + + +@core.register_type +class Gap(core.Item): + _serializable_label = "Gap.1" + _class_path = "schema.Gap" + + def __init__( + self, + name=None, + # note - only one of the following two arguments is accepted + # if neither is provided, source_range will be set to an empty + # TimeRange + # Duration is provided as a convienence for creating a gap of a certain + # length. IE: Gap(duration=otio.opentime.RationalTime(300, 24)) + duration=None, + source_range=None, + effects=None, + markers=None, + metadata=None, + ): + if duration and source_range: + raise RuntimeError( + "Cannot instantiate with both a source range and a duration." + ) + + if duration: + source_range = opentime.TimeRange( + opentime.RationalTime(0, duration.rate), + duration + ) + elif source_range is None: + # if neither is provided, seed TimeRange as an empty Source Range. + source_range = opentime.TimeRange() + + core.Item.__init__( + self, + name=name, + source_range=source_range, + effects=effects, + markers=markers, + metadata=metadata + ) + + @staticmethod + def visible(): + return False + + +# the original name for "gap" was "filler" - this will turn "Filler" found in +# OTIO files into Gap automatically. +core.register_type(Gap, "Filler") diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/generator_reference.py b/openpype/vendor/python/python_2/opentimelineio/schema/generator_reference.py new file mode 100644 index 0000000000..ef1dde836e --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/generator_reference.py @@ -0,0 +1,76 @@ +""" +Generators are media references that _produce_ media rather than refer to it. +""" + +from .. import ( + core, +) + + +@core.register_type +class GeneratorReference(core.MediaReference): + """ + Base class for Generators. + + Generators are media references that become "generators" in editorial + systems. For example, color bars or a solid color. + """ + + _serializable_label = "GeneratorReference.1" + _name = "GeneratorReference" + + def __init__( + self, + name=None, + generator_kind=None, + available_range=None, + parameters=None, + metadata=None + ): + super(GeneratorReference, self).__init__( + name, + available_range, + metadata + ) + + if parameters is None: + parameters = {} + self.parameters = parameters + self.generator_kind = generator_kind + + parameters = core.serializable_field( + "parameters", + dict, + doc="Dictionary of parameters for generator." + ) + generator_kind = core.serializable_field( + "generator_kind", + required_type=type(""), + # @TODO: need to clarify if this also has an enum of supported types + # / generic + doc="Kind of generator reference, as defined by the " + "schema.generator_reference.GeneratorReferenceTypes enum." + ) + + def __str__(self): + return 'GeneratorReference("{}", "{}", {}, {})'.format( + self.name, + self.generator_kind, + self.parameters, + self.metadata + ) + + def __repr__(self): + return ( + 'otio.schema.GeneratorReference(' + 'name={}, ' + 'generator_kind={}, ' + 'parameters={}, ' + 'metadata={}' + ')'.format( + repr(self.name), + repr(self.generator_kind), + repr(self.parameters), + repr(self.metadata), + ) + ) diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/marker.py b/openpype/vendor/python/python_2/opentimelineio/schema/marker.py new file mode 100644 index 0000000000..d8b6f1c272 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/marker.py @@ -0,0 +1,128 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Marker class. Holds metadata over regions of time.""" + +from .. import ( + core, + opentime, +) + + +class MarkerColor: + """ Enum encoding colors of markers as strings. """ + + PINK = "PINK" + RED = "RED" + ORANGE = "ORANGE" + YELLOW = "YELLOW" + GREEN = "GREEN" + CYAN = "CYAN" + BLUE = "BLUE" + PURPLE = "PURPLE" + MAGENTA = "MAGENTA" + BLACK = "BLACK" + WHITE = "WHITE" + + +@core.register_type +class Marker(core.SerializableObject): + + """ Holds metadata over time on a timeline """ + + _serializable_label = "Marker.2" + _class_path = "marker.Marker" + + def __init__( + self, + name=None, + marked_range=None, + color=MarkerColor.RED, + metadata=None, + ): + core.SerializableObject.__init__( + self, + ) + self.name = name + self.marked_range = marked_range + self.color = color + self.metadata = metadata or {} + + name = core.serializable_field("name", doc="Name of this marker.") + + marked_range = core.serializable_field( + "marked_range", + opentime.TimeRange, + "Range this marker applies to, relative to the Item this marker is " + "attached to (e.g. the Clip or Track that owns this marker)." + ) + + color = core.serializable_field( + "color", + required_type=type(MarkerColor.RED), + doc="Color string for this marker (for example: 'RED'), based on the " + "otio.schema.marker.MarkerColor enum." + ) + + # old name + range = core.deprecated_field() + + metadata = core.serializable_field( + "metadata", + dict, + "Metadata dictionary." + ) + + def __repr__(self): + return ( + "otio.schema.Marker(" + "name={}, " + "marked_range={}, " + "metadata={}" + ")".format( + repr(self.name), + repr(self.marked_range), + repr(self.metadata), + ) + ) + + def __str__(self): + return ( + "Marker(" + "{}, " + "{}, " + "{}" + ")".format( + str(self.name), + str(self.marked_range), + str(self.metadata), + ) + ) + + +@core.upgrade_function_for(Marker, 2) +def _version_one_to_two(data): + data["marked_range"] = data["range"] + del data["range"] + return data diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/missing_reference.py b/openpype/vendor/python/python_2/opentimelineio/schema/missing_reference.py new file mode 100644 index 0000000000..88bc1862fc --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/missing_reference.py @@ -0,0 +1,43 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +""" +Implementation of the MissingReference media reference schema. +""" + +from .. import ( + core, +) + + +@core.register_type +class MissingReference(core.MediaReference): + """Represents media for which a concrete reference is missing.""" + + _serializable_label = "MissingReference.1" + _name = "MissingReference" + + @property + def is_missing_reference(self): + return True diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/schemadef.py b/openpype/vendor/python/python_2/opentimelineio/schema/schemadef.py new file mode 100644 index 0000000000..5fb4e05abd --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/schemadef.py @@ -0,0 +1,65 @@ + +from .. import ( + core, + exceptions, + plugins, + schemadef +) + + +@core.register_type +class SchemaDef(plugins.PythonPlugin): + _serializable_label = "SchemaDef.1" + + def __init__( + self, + name=None, + execution_scope=None, + filepath=None, + ): + super(SchemaDef, self).__init__(name, execution_scope, filepath) + + def module(self): + """ + Return the module object for this schemadef plugin. + If the module hasn't already been imported, it is imported and + injected into the otio.schemadefs namespace as a side-effect. + (redefines PythonPlugin.module()) + """ + + if not self._module: + self._module = self._imported_module("schemadef") + if self.name: + schemadef._add_schemadef_module(self.name, self._module) + + return self._module + + +def available_schemadef_names(): + """Return a string list of the available schemadefs.""" + + return [str(sd.name) for sd in plugins.ActiveManifest().schemadefs] + + +def from_name(name): + """Fetch the schemadef plugin object by the name of the schema directly.""" + + try: + return plugins.ActiveManifest().from_name(name, kind_list="schemadefs") + except exceptions.NotSupportedError: + raise exceptions.NotSupportedError( + "schemadef not supported: {}, available: {}".format( + name, + available_schemadef_names() + ) + ) + + +def module_from_name(name): + """Fetch the plugin's module by the name of the schemadef. + + Will load the plugin if it has not already been loaded. Reading a file that + contains the schemadef will also trigger a load of the plugin. + """ + plugin = from_name(name) + return plugin.module() diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/serializable_collection.py b/openpype/vendor/python/python_2/opentimelineio/schema/serializable_collection.py new file mode 100644 index 0000000000..523ea77ddb --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/serializable_collection.py @@ -0,0 +1,149 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""A serializable collection of SerializableObjects.""" + +import collections +import copy + +from .. import ( + core +) + +from . import ( + clip +) + + +@core.register_type +class SerializableCollection( + core.SerializableObject, + collections.MutableSequence +): + """A kind of composition which can hold any serializable object. + + This composition approximates the concept of a `bin` - a collection of + SerializableObjects that do not have any compositing meaning, but can + serialize to/from OTIO correctly, with metadata and a named collection. + """ + + _serializable_label = "SerializableCollection.1" + _class_path = "schema.SerializableCollection" + + def __init__( + self, + name=None, + children=None, + metadata=None, + ): + super(SerializableCollection, self).__init__() + + self.name = name + self._children = children or [] + self.metadata = copy.deepcopy(metadata) if metadata else {} + + name = core.serializable_field( + "name", + doc="SerializableCollection name." + ) + _children = core.serializable_field( + "children", + list, + "SerializableObject contained by this container." + ) + metadata = core.serializable_field( + "metadata", + dict, + doc="Metadata dictionary for this SerializableCollection." + ) + + # @{ Stringification + def __str__(self): + return "SerializableCollection({}, {}, {})".format( + str(self.name), + str(self._children), + str(self.metadata) + ) + + def __repr__(self): + return ( + "otio.{}(" + "name={}, " + "children={}, " + "metadata={}" + ")".format( + self._class_path, + repr(self.name), + repr(self._children), + repr(self.metadata) + ) + ) + # @} + + # @{ collections.MutableSequence implementation + def __getitem__(self, item): + return self._children[item] + + def __setitem__(self, key, value): + self._children[key] = value + + def insert(self, index, item): + self._children.insert(index, item) + + def __len__(self): + return len(self._children) + + def __delitem__(self, item): + del self._children[item] + # @} + + def each_child( + self, + search_range=None, + descended_from_type=core.composable.Composable + ): + for i, child in enumerate(self._children): + # filter out children who are not descended from the specified type + is_descendant = descended_from_type == core.composable.Composable + if is_descendant or isinstance(child, descended_from_type): + yield child + + # for children that are compositions, recurse into their children + if hasattr(child, "each_child"): + for valid_child in ( + c for c in child.each_child( + search_range, + descended_from_type + ) + ): + yield valid_child + + def each_clip(self, search_range=None): + return self.each_child(search_range, clip.Clip) + + +# the original name for "SerializableCollection" was "SerializeableCollection" +# this will turn this misspelling found in OTIO files into the correct instance +# automatically. +core.register_type(SerializableCollection, 'SerializeableCollection') diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/stack.py b/openpype/vendor/python/python_2/opentimelineio/schema/stack.py new file mode 100644 index 0000000000..bf67158dc0 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/stack.py @@ -0,0 +1,120 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""A stack represents a series of composable.Composables that are arranged such +that their start times are at the same point. + +Most commonly, this would be a series of schema.Track objects that then +contain clips. The 0 time of those tracks would be coincide with the 0-time of +the stack. + +Stacks are in compositing order, with later children obscuring earlier +children. In other words, from bottom to top. If a stack has three children, +[A, B, C], C is above B which is above A. + +A stack is the length of its longest child. If a child ends before the other +children, then an earlier index child would be visible before it. +""" + +from .. import ( + core, + opentime, + exceptions +) + +from . import ( + clip +) + + +@core.register_type +class Stack(core.Composition): + _serializable_label = "Stack.1" + _composition_kind = "Stack" + _modname = "schema" + + def __init__( + self, + name=None, + children=None, + source_range=None, + markers=None, + effects=None, + metadata=None + ): + core.Composition.__init__( + self, + name=name, + children=children, + source_range=source_range, + markers=markers, + effects=effects, + metadata=metadata + ) + + def range_of_child_at_index(self, index): + try: + child = self[index] + except IndexError: + raise exceptions.NoSuchChildAtIndex(index) + + dur = child.duration() + + return opentime.TimeRange( + start_time=opentime.RationalTime(0, dur.rate), + duration=dur + ) + + def each_clip(self, search_range=None): + return self.each_child(search_range, clip.Clip) + + def available_range(self): + if len(self) == 0: + return opentime.TimeRange() + + duration = max(child.duration() for child in self) + + return opentime.TimeRange( + opentime.RationalTime(0, duration.rate), + duration=duration + ) + + def range_of_all_children(self): + child_map = {} + for i, c in enumerate(self._children): + child_map[c] = self.range_of_child_at_index(i) + return child_map + + def trimmed_range_of_child_at_index(self, index, reference_space=None): + range = self.range_of_child_at_index(index) + + if not self.source_range: + return range + + range = opentime.TimeRange( + start_time=self.source_range.start_time, + duration=min(range.duration, self.source_range.duration) + ) + + return range diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/timeline.py b/openpype/vendor/python/python_2/opentimelineio/schema/timeline.py new file mode 100644 index 0000000000..fe7d6952ab --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/timeline.py @@ -0,0 +1,133 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implementation of the OTIO built in schema, Timeline object.""" + +import copy + +from .. import ( + core, + opentime, +) + +from . import stack, track + + +@core.register_type +class Timeline(core.SerializableObject): + _serializable_label = "Timeline.1" + + def __init__( + self, + name=None, + tracks=None, + global_start_time=None, + metadata=None, + ): + super(Timeline, self).__init__() + self.name = name + self.global_start_time = copy.deepcopy(global_start_time) + + if tracks is None: + tracks = [] + self.tracks = stack.Stack(name="tracks", children=tracks) + + self.metadata = copy.deepcopy(metadata) if metadata else {} + + name = core.serializable_field("name", doc="Name of this timeline.") + tracks = core.serializable_field( + "tracks", + core.Composition, + doc="Stack of tracks containing items." + ) + metadata = core.serializable_field( + "metadata", + dict, + "Metadata dictionary." + ) + global_start_time = core.serializable_field( + "global_start_time", + opentime.RationalTime, + doc="Global starting time value and rate of the timeline." + ) + + def __str__(self): + return 'Timeline("{}", {})'.format(str(self.name), str(self.tracks)) + + def __repr__(self): + return ( + "otio.schema.Timeline(name={}, tracks={})".format( + repr(self.name), + repr(self.tracks) + ) + ) + + def each_child(self, search_range=None, descended_from_type=core.Composable): + return self.tracks.each_child(search_range, descended_from_type) + + def each_clip(self, search_range=None): + """Return a flat list of each clip, limited to the search_range.""" + + return self.tracks.each_clip(search_range) + + def duration(self): + """Duration of this timeline.""" + + return self.tracks.duration() + + def range_of_child(self, child): + """Range of the child object contained in this timeline.""" + + return self.tracks.range_of_child(child) + + def video_tracks(self): + """ + This convenience method returns a list of the top-level video tracks in + this timeline. + """ + return [ + trck for trck + in self.tracks + if (isinstance(trck, track.Track) and + trck.kind == track.TrackKind.Video) + ] + + def audio_tracks(self): + """ + This convenience method returns a list of the top-level audio tracks in + this timeline. + """ + return [ + trck for trck + in self.tracks + if (isinstance(trck, track.Track) and + trck.kind == track.TrackKind.Audio) + ] + + +def timeline_from_clips(clips): + """Convenience for making a single track timeline from a list of clips.""" + + trck = track.Track(children=clips) + return Timeline(tracks=[trck]) diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/track.py b/openpype/vendor/python/python_2/opentimelineio/schema/track.py new file mode 100644 index 0000000000..29b0e7f1ae --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/track.py @@ -0,0 +1,242 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Implement Track sublcass of composition.""" + +import collections + +from .. import ( + core, + opentime, +) + +from . import ( + gap, + transition, + clip, +) + + +class TrackKind: + Video = "Video" + Audio = "Audio" + + +class NeighborGapPolicy: + """ enum for deciding how to add gap when asking for neighbors """ + never = 0 + around_transitions = 1 + + +@core.register_type +class Track(core.Composition): + _serializable_label = "Track.1" + _composition_kind = "Track" + _modname = "schema" + + def __init__( + self, + name=None, + children=None, + kind=TrackKind.Video, + source_range=None, + markers=None, + effects=None, + metadata=None, + ): + core.Composition.__init__( + self, + name=name, + children=children, + source_range=source_range, + markers=markers, + effects=effects, + metadata=metadata + ) + self.kind = kind + + kind = core.serializable_field( + "kind", + doc="Composition kind (Stack, Track)" + ) + + def range_of_child_at_index(self, index): + child = self[index] + + # sum the durations of all the children leading up to the chosen one + start_time = sum( + ( + o_c.duration() + for o_c in (c for c in self[:index] if not c.overlapping()) + ), + opentime.RationalTime(value=0, rate=child.duration().rate) + ) + if isinstance(child, transition.Transition): + start_time -= child.in_offset + + return opentime.TimeRange(start_time, child.duration()) + + def trimmed_range_of_child_at_index(self, index, reference_space=None): + child_range = self.range_of_child_at_index(index) + + return self.trim_child_range(child_range) + + def handles_of_child(self, child): + """If media beyond the ends of this child are visible due to adjacent + Transitions (only applicable in a Track) then this will return the + head and tail offsets as a tuple of RationalTime objects. If no handles + are present on either side, then None is returned instead of a + RationalTime. + + Example usage + + >>> head, tail = track.handles_of_child(clip) + >>> if head: + ... print('do something') + >>> if tail: + ... print('do something else') + """ + head, tail = None, None + before, after = self.neighbors_of(child) + if isinstance(before, transition.Transition): + head = before.in_offset + if isinstance(after, transition.Transition): + tail = after.out_offset + + return head, tail + + def available_range(self): + # Sum up our child items' durations + duration = sum( + (c.duration() for c in self if isinstance(c, core.Item)), + opentime.RationalTime() + ) + + # Add the implicit gap when a Transition is at the start/end + if self and isinstance(self[0], transition.Transition): + duration += self[0].in_offset + if self and isinstance(self[-1], transition.Transition): + duration += self[-1].out_offset + + result = opentime.TimeRange( + start_time=opentime.RationalTime(0, duration.rate), + duration=duration + ) + + return result + + def each_clip(self, search_range=None, shallow_search=False): + return self.each_child(search_range, clip.Clip, shallow_search) + + def neighbors_of(self, item, insert_gap=NeighborGapPolicy.never): + """Returns the neighbors of the item as a namedtuple, (previous, next). + + Can optionally fill in gaps when transitions have no gaps next to them. + + with insert_gap == NeighborGapPolicy.never: + [A, B, C] :: neighbors_of(B) -> (A, C) + [A, B, C] :: neighbors_of(A) -> (None, B) + [A, B, C] :: neighbors_of(C) -> (B, None) + [A] :: neighbors_of(A) -> (None, None) + + with insert_gap == NeighborGapPolicy.around_transitions: + (assuming A and C are transitions) + [A, B, C] :: neighbors_of(B) -> (A, C) + [A, B, C] :: neighbors_of(A) -> (Gap, B) + [A, B, C] :: neighbors_of(C) -> (B, Gap) + [A] :: neighbors_of(A) -> (Gap, Gap) + """ + + try: + index = self.index(item) + except ValueError: + raise ValueError( + "item: {} is not in composition: {}".format( + item, + self + ) + ) + + previous, next_item = None, None + + # look before index + if index == 0: + if insert_gap == NeighborGapPolicy.around_transitions: + if isinstance(item, transition.Transition): + previous = gap.Gap( + source_range=opentime.TimeRange(duration=item.in_offset)) + elif index > 0: + previous = self[index - 1] + + if index == len(self) - 1: + if insert_gap == NeighborGapPolicy.around_transitions: + if isinstance(item, transition.Transition): + next_item = gap.Gap( + source_range=opentime.TimeRange(duration=item.out_offset)) + elif index < len(self) - 1: + next_item = self[index + 1] + + return collections.namedtuple('neighbors', ('previous', 'next'))( + previous, + next_item + ) + + def range_of_all_children(self): + """Return a dict mapping children to their range in this track.""" + + if not self._children: + return {} + + result_map = {} + + # Heuristic to guess what the rate should be set to based on the first + # thing in the track. + first_thing = self._children[0] + if isinstance(first_thing, transition.Transition): + rate = first_thing.in_offset.rate + else: + rate = first_thing.trimmed_range().duration.rate + + last_end_time = opentime.RationalTime(0, rate) + + for thing in self._children: + if isinstance(thing, transition.Transition): + result_map[thing] = opentime.TimeRange( + last_end_time - thing.in_offset, + thing.out_offset + thing.in_offset, + ) + else: + last_range = opentime.TimeRange( + last_end_time, + thing.trimmed_range().duration + ) + result_map[thing] = last_range + last_end_time = last_range.end_time_exclusive() + + return result_map + + +# the original name for "track" was "sequence" - this will turn "Sequence" +# found in OTIO files into Track automatically. +core.register_type(Track, "Sequence") diff --git a/openpype/vendor/python/python_2/opentimelineio/schema/transition.py b/openpype/vendor/python/python_2/opentimelineio/schema/transition.py new file mode 100644 index 0000000000..93b54ab1ab --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schema/transition.py @@ -0,0 +1,159 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Transition base class""" + +from .. import ( + opentime, + core, + exceptions, +) + +import copy + + +class TransitionTypes: + """Enum encoding types of transitions. + + This is for representing "Dissolves" and "Wipes" defined by the + multi-source effect as defined by SMPTE 258M-2004 7.6.3.2 + + Other effects are handled by the `schema.Effect` class. + """ + + # @{ SMPTE transitions. + SMPTE_Dissolve = "SMPTE_Dissolve" + # SMPTE_Wipe = "SMPTE_Wipe" -- @TODO + # @} + + # Non SMPTE transitions. + Custom = "Custom_Transition" + + +@core.register_type +class Transition(core.Composable): + """Represents a transition between two items.""" + + _serializable_label = "Transition.1" + + def __init__( + self, + name=None, + transition_type=None, + # @TODO: parameters will be added later as needed (SMPTE_Wipe will + # probably require it) + # parameters=None, + in_offset=None, + out_offset=None, + metadata=None + ): + core.Composable.__init__( + self, + name=name, + metadata=metadata + ) + + # init everything as None first, so that we will catch uninitialized + # values via exceptions + # if parameters is None: + # parameters = {} + # self.parameters = parameters + self.transition_type = transition_type + self.in_offset = copy.deepcopy(in_offset) + self.out_offset = copy.deepcopy(out_offset) + + transition_type = core.serializable_field( + "transition_type", + required_type=type(TransitionTypes.SMPTE_Dissolve), + doc="Kind of transition, as defined by the " + "schema.transition.TransitionTypes enum." + ) + # parameters = core.serializable_field( + # "parameters", + # doc="Parameters of the transition." + # ) + in_offset = core.serializable_field( + "in_offset", + required_type=opentime.RationalTime, + doc="Amount of the previous clip this transition overlaps, exclusive." + ) + out_offset = core.serializable_field( + "out_offset", + required_type=opentime.RationalTime, + doc="Amount of the next clip this transition overlaps, exclusive." + ) + + def __str__(self): + return 'Transition("{}", "{}", {}, {}, {})'.format( + self.name, + self.transition_type, + self.in_offset, + self.out_offset, + # self.parameters, + self.metadata + ) + + def __repr__(self): + return ( + 'otio.schema.Transition(' + 'name={}, ' + 'transition_type={}, ' + 'in_offset={}, ' + 'out_offset={}, ' + # 'parameters={}, ' + 'metadata={}' + ')'.format( + repr(self.name), + repr(self.transition_type), + repr(self.in_offset), + repr(self.out_offset), + # repr(self.parameters), + repr(self.metadata), + ) + ) + + @staticmethod + def overlapping(): + return True + + def duration(self): + return self.in_offset + self.out_offset + + def range_in_parent(self): + """Find and return the range of this item in the parent.""" + if not self.parent(): + raise exceptions.NotAChildError( + "No parent of {}, cannot compute range in parent.".format(self) + ) + + return self.parent().range_of_child(self) + + def trimmed_range_in_parent(self): + """Find and return the timmed range of this item in the parent.""" + if not self.parent(): + raise exceptions.NotAChildError( + "No parent of {}, cannot compute range in parent.".format(self) + ) + + return self.parent().trimmed_range_of_child(self) diff --git a/openpype/vendor/python/python_2/opentimelineio/schemadef/__init__.py b/openpype/vendor/python/python_2/opentimelineio/schemadef/__init__.py new file mode 100644 index 0000000000..568b3eaaa7 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/schemadef/__init__.py @@ -0,0 +1,5 @@ + +def _add_schemadef_module(name, mod): + """Insert a new module name and module object into schemadef namespace.""" + ns = globals() # the namespace dict of the schemadef package + ns[name] = mod diff --git a/openpype/vendor/python/python_2/opentimelineio/test_utils.py b/openpype/vendor/python/python_2/opentimelineio/test_utils.py new file mode 100644 index 0000000000..e173275ff5 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio/test_utils.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Utility assertions for OTIO Unit tests.""" + +import re + +from . import ( + adapters +) + + +class OTIOAssertions(object): + def assertJsonEqual(self, known, test_result): + """Convert to json and compare that (more readable).""" + self.maxDiff = None + + known_str = adapters.write_to_string(known, 'otio_json') + test_str = adapters.write_to_string(test_result, 'otio_json') + + def strip_trailing_decimal_zero(s): + return re.sub(r'"(value|rate)": (\d+)\.0', r'"\1": \2', s) + + self.assertMultiLineEqual( + strip_trailing_decimal_zero(known_str), + strip_trailing_decimal_zero(test_str) + ) + + def assertIsOTIOEquivalentTo(self, known, test_result): + """Test using the 'is equivalent to' method on SerializableObject""" + + self.assertTrue(known.is_equivalent_to(test_result)) diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/__init__.py b/openpype/vendor/python/python_2/opentimelineio_contrib/__init__.py new file mode 100644 index 0000000000..7f7a82f46a --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/__init__.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Unsupported contrib code for OpenTimelineIO.""" + +# flake8: noqa + +from . import ( + adapters +) + +__version__ = "0.11.0" +__author__ = "Pixar Animation Studios" +__author_email__ = "opentimelineio@pixar.com" +__license__ = "Modified Apache 2.0 License" diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/__init__.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/__init__.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/__init__.py rename to openpype/vendor/python/python_2/opentimelineio_contrib/adapters/__init__.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/__init__.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/aaf_adapter/__init__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/__init__.py rename to openpype/vendor/python/python_2/opentimelineio_contrib/adapters/aaf_adapter/__init__.py diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/aaf_adapter/aaf_writer.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/aaf_adapter/aaf_writer.py new file mode 100644 index 0000000000..9e283d3747 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/aaf_adapter/aaf_writer.py @@ -0,0 +1,764 @@ +# +# Copyright 2019 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""AAF Adapter Transcriber + +Specifies how to transcribe an OpenTimelineIO file into an AAF file. +""" + +import aaf2 +import abc +import uuid +import opentimelineio as otio +import os +import copy +import re + + +AAF_PARAMETERDEF_PAN = aaf2.auid.AUID("e4962322-2267-11d3-8a4c-0050040ef7d2") +AAF_OPERATIONDEF_MONOAUDIOPAN = aaf2.auid.AUID("9d2ea893-0968-11d3-8a38-0050040ef7d2") +AAF_PARAMETERDEF_AVIDPARAMETERBYTEORDER = uuid.UUID( + "c0038672-a8cf-11d3-a05b-006094eb75cb") +AAF_PARAMETERDEF_AVIDEFFECTID = uuid.UUID( + "93994bd6-a81d-11d3-a05b-006094eb75cb") +AAF_PARAMETERDEF_AFX_FG_KEY_OPACITY_U = uuid.UUID( + "8d56813d-847e-11d5-935a-50f857c10000") +AAF_PARAMETERDEF_LEVEL = uuid.UUID("e4962320-2267-11d3-8a4c-0050040ef7d2") +AAF_VVAL_EXTRAPOLATION_ID = uuid.UUID("0e24dd54-66cd-4f1a-b0a0-670ac3a7a0b3") +AAF_OPERATIONDEF_SUBMASTER = uuid.UUID("f1db0f3d-8d64-11d3-80df-006008143e6f") + + +class AAFAdapterError(otio.exceptions.OTIOError): + pass + + +class AAFValidationError(AAFAdapterError): + pass + + +class AAFFileTranscriber(object): + """ + AAFFileTranscriber + + AAFFileTranscriber manages the file-level knowledge during a conversion from + otio to aaf. This includes keeping track of unique tapemobs and mastermobs. + """ + + def __init__(self, input_otio, aaf_file, **kwargs): + """ + AAFFileTranscriber requires an input timeline and an output pyaaf2 file handle. + + Args: + input_otio: an input OpenTimelineIO timeline + aaf_file: a pyaaf2 file handle to an output file + """ + self.aaf_file = aaf_file + self.compositionmob = self.aaf_file.create.CompositionMob() + self.compositionmob.name = input_otio.name + self.compositionmob.usage = "Usage_TopLevel" + self.aaf_file.content.mobs.append(self.compositionmob) + self._unique_mastermobs = {} + self._unique_tapemobs = {} + self._clip_mob_ids_map = _gather_clip_mob_ids(input_otio, **kwargs) + + def _unique_mastermob(self, otio_clip): + """Get a unique mastermob, identified by clip metadata mob id.""" + mob_id = self._clip_mob_ids_map.get(otio_clip) + mastermob = self._unique_mastermobs.get(mob_id) + if not mastermob: + mastermob = self.aaf_file.create.MasterMob() + mastermob.name = otio_clip.name + mastermob.mob_id = aaf2.mobid.MobID(mob_id) + self.aaf_file.content.mobs.append(mastermob) + self._unique_mastermobs[mob_id] = mastermob + return mastermob + + def _unique_tapemob(self, otio_clip): + """Get a unique tapemob, identified by clip metadata mob id.""" + mob_id = self._clip_mob_ids_map.get(otio_clip) + tapemob = self._unique_tapemobs.get(mob_id) + if not tapemob: + tapemob = self.aaf_file.create.SourceMob() + tapemob.name = otio_clip.name + tapemob.descriptor = self.aaf_file.create.ImportDescriptor() + # If the edit_rate is not an integer, we need + # to use drop frame with a nominal integer fps. + edit_rate = otio_clip.visible_range().duration.rate + timecode_fps = round(edit_rate) + tape_timecode_slot = tapemob.create_timecode_slot( + edit_rate=edit_rate, + timecode_fps=timecode_fps, + drop_frame=(edit_rate != timecode_fps) + ) + timecode_start = ( + otio_clip.media_reference.available_range.start_time.value) + timecode_length = ( + otio_clip.media_reference.available_range.duration.value) + + tape_timecode_slot.segment.start = timecode_start + tape_timecode_slot.segment.length = timecode_length + self.aaf_file.content.mobs.append(tapemob) + self._unique_tapemobs[mob_id] = tapemob + return tapemob + + def track_transcriber(self, otio_track): + """Return an appropriate _TrackTranscriber given an otio track.""" + if otio_track.kind == otio.schema.TrackKind.Video: + transcriber = VideoTrackTranscriber(self, otio_track) + elif otio_track.kind == otio.schema.TrackKind.Audio: + transcriber = AudioTrackTranscriber(self, otio_track) + else: + raise otio.exceptions.NotSupportedError( + "Unsupported track kind: {}".format(otio_track.kind)) + return transcriber + + +def validate_metadata(timeline): + """Print a check of necessary metadata requirements for an otio timeline.""" + + all_checks = [__check(timeline, "duration().rate")] + edit_rate = __check(timeline, "duration().rate").value + + for child in timeline.each_child(): + checks = [] + if isinstance(child, otio.schema.Gap): + checks = [ + __check(child, "duration().rate").equals(edit_rate) + ] + if isinstance(child, otio.schema.Clip): + checks = [ + __check(child, "duration().rate").equals(edit_rate), + __check(child, "media_reference.available_range.duration.rate" + ).equals(edit_rate), + __check(child, "media_reference.available_range.start_time.rate" + ).equals(edit_rate) + ] + if isinstance(child, otio.schema.Transition): + checks = [ + __check(child, "duration().rate").equals(edit_rate), + __check(child, "metadata['AAF']['PointList']"), + __check(child, "metadata['AAF']['OperationGroup']['Operation']" + "['DataDefinition']['Name']"), + __check(child, "metadata['AAF']['OperationGroup']['Operation']" + "['Description']"), + __check(child, "metadata['AAF']['OperationGroup']['Operation']" + "['Name']"), + __check(child, "metadata['AAF']['CutPoint']") + ] + all_checks.extend(checks) + + if any(check.errors for check in all_checks): + raise AAFValidationError("\n" + "\n".join( + sum([check.errors for check in all_checks], []))) + + +def _gather_clip_mob_ids(input_otio, + prefer_file_mob_id=False, + use_empty_mob_ids=False, + **kwargs): + """ + Create dictionary of otio clips with their corresponding mob ids. + """ + + def _from_clip_metadata(clip): + """Get the MobID from the clip.metadata.""" + return clip.metadata.get("AAF", {}).get("SourceID") + + def _from_media_reference_metadata(clip): + """Get the MobID from the media_reference.metadata.""" + return (clip.media_reference.metadata.get("AAF", {}).get("MobID") or + clip.media_reference.metadata.get("AAF", {}).get("SourceID")) + + def _from_aaf_file(clip): + """ Get the MobID from the AAF file itself.""" + mob_id = None + target_url = clip.media_reference.target_url + if os.path.isfile(target_url) and target_url.endswith("aaf"): + with aaf2.open(clip.media_reference.target_url) as aaf_file: + mastermobs = list(aaf_file.content.mastermobs()) + if len(mastermobs) == 1: + mob_id = mastermobs[0].mob_id + return mob_id + + def _generate_empty_mobid(clip): + """Generate a meaningless MobID.""" + return aaf2.mobid.MobID.new() + + strategies = [ + _from_clip_metadata, + _from_media_reference_metadata, + _from_aaf_file + ] + + if prefer_file_mob_id: + strategies.remove(_from_aaf_file) + strategies.insert(0, _from_aaf_file) + + if use_empty_mob_ids: + strategies.append(_generate_empty_mobid) + + clip_mob_ids = {} + + for otio_clip in input_otio.each_clip(): + for strategy in strategies: + mob_id = strategy(otio_clip) + if mob_id: + clip_mob_ids[otio_clip] = mob_id + break + else: + raise AAFAdapterError("Cannot find mob ID for clip {}".format(otio_clip)) + + return clip_mob_ids + + +def _stackify_nested_groups(timeline): + """ + Ensure that all nesting in a given timeline is in a stack container. + This conforms with how AAF thinks about nesting, there needs + to be an outer container, even if it's just one object. + """ + copied = copy.deepcopy(timeline) + for track in copied.tracks: + for i, child in enumerate(track.each_child()): + is_nested = isinstance(child, otio.schema.Track) + is_parent_in_stack = isinstance(child.parent(), otio.schema.Stack) + if is_nested and not is_parent_in_stack: + stack = otio.schema.Stack() + track.remove(child) + stack.append(child) + track.insert(i, stack) + return copied + + +class _TrackTranscriber(object): + """ + _TrackTranscriber is the base class for the conversion of a given otio track. + + _TrackTranscriber is not meant to be used by itself. It provides the common + functionality to inherit from. We need an abstract base class because Audio and + Video are handled differently. + """ + __metaclass__ = abc.ABCMeta + + def __init__(self, root_file_transcriber, otio_track): + """ + _TrackTranscriber + + Args: + root_file_transcriber: the corresponding 'parent' AAFFileTranscriber object + otio_track: the given otio_track to convert + """ + self.root_file_transcriber = root_file_transcriber + self.compositionmob = root_file_transcriber.compositionmob + self.aaf_file = root_file_transcriber.aaf_file + self.otio_track = otio_track + self.edit_rate = next(self.otio_track.each_child()).duration().rate + self.timeline_mobslot, self.sequence = self._create_timeline_mobslot() + self.timeline_mobslot.name = self.otio_track.name + + def transcribe(self, otio_child): + """Transcribe otio child to corresponding AAF object""" + if isinstance(otio_child, otio.schema.Gap): + filler = self.aaf_filler(otio_child) + return filler + elif isinstance(otio_child, otio.schema.Transition): + transition = self.aaf_transition(otio_child) + return transition + elif isinstance(otio_child, otio.schema.Clip): + source_clip = self.aaf_sourceclip(otio_child) + return source_clip + elif isinstance(otio_child, otio.schema.Track): + sequence = self.aaf_sequence(otio_child) + return sequence + elif isinstance(otio_child, otio.schema.Stack): + operation_group = self.aaf_operation_group(otio_child) + return operation_group + else: + raise otio.exceptions.NotSupportedError( + "Unsupported otio child type: {}".format(type(otio_child))) + + @property + @abc.abstractmethod + def media_kind(self): + """Return the string for what kind of track this is.""" + pass + + @property + @abc.abstractmethod + def _master_mob_slot_id(self): + """ + Return the MasterMob Slot ID for the corresponding track media kind + """ + # MasterMob's and MasterMob slots have to be unique. We handle unique + # MasterMob's with _unique_mastermob(). We also need to protect against + # duplicate MasterMob slots. As of now, we mandate all picture clips to + # be created in MasterMob slot 1 and all sound clips to be created in + # MasterMob slot 2. While this is a little inadequate, it works for now + pass + + @abc.abstractmethod + def _create_timeline_mobslot(self): + """ + Return a timeline_mobslot and sequence for this track. + + In AAF, a TimelineMobSlot is a container for the Sequence. A Sequence is + analogous to an otio track. + + Returns: + Returns a tuple of (TimelineMobSlot, Sequence) + """ + pass + + @abc.abstractmethod + def default_descriptor(self, otio_clip): + pass + + @abc.abstractmethod + def _transition_parameters(self): + pass + + def aaf_filler(self, otio_gap): + """Convert an otio Gap into an aaf Filler""" + length = otio_gap.visible_range().duration.value + filler = self.aaf_file.create.Filler(self.media_kind, length) + return filler + + def aaf_sourceclip(self, otio_clip): + """Convert an otio Clip into an aaf SourceClip""" + tapemob, tapemob_slot = self._create_tapemob(otio_clip) + filemob, filemob_slot = self._create_filemob(otio_clip, tapemob, tapemob_slot) + mastermob, mastermob_slot = self._create_mastermob(otio_clip, + filemob, + filemob_slot) + + # We need both `start_time` and `duration` + # Here `start` is the offset between `first` and `in` values. + + offset = (otio_clip.visible_range().start_time - + otio_clip.available_range().start_time) + start = offset.value + length = otio_clip.visible_range().duration.value + + compmob_clip = self.compositionmob.create_source_clip( + slot_id=self.timeline_mobslot.slot_id, + start=start, + length=length, + media_kind=self.media_kind) + compmob_clip.mob = mastermob + compmob_clip.slot = mastermob_slot + compmob_clip.slot_id = mastermob_slot.slot_id + return compmob_clip + + def aaf_transition(self, otio_transition): + """Convert an otio Transition into an aaf Transition""" + if (otio_transition.transition_type != + otio.schema.transition.TransitionTypes.SMPTE_Dissolve): + print( + "Unsupported transition type: {}".format( + otio_transition.transition_type)) + return None + + transition_params, varying_value = self._transition_parameters() + + interpolation_def = self.aaf_file.create.InterpolationDef( + aaf2.misc.LinearInterp, "LinearInterp", "Linear keyframe interpolation") + self.aaf_file.dictionary.register_def(interpolation_def) + varying_value["Interpolation"].value = ( + self.aaf_file.dictionary.lookup_interperlationdef("LinearInterp")) + + pointlist = otio_transition.metadata["AAF"]["PointList"] + + c1 = self.aaf_file.create.ControlPoint() + c1["EditHint"].value = "Proportional" + c1.value = pointlist[0]["Value"] + c1.time = pointlist[0]["Time"] + + c2 = self.aaf_file.create.ControlPoint() + c2["EditHint"].value = "Proportional" + c2.value = pointlist[1]["Value"] + c2.time = pointlist[1]["Time"] + + varying_value["PointList"].extend([c1, c2]) + + op_group_metadata = otio_transition.metadata["AAF"]["OperationGroup"] + effect_id = op_group_metadata["Operation"].get("Identification") + is_time_warp = op_group_metadata["Operation"].get("IsTimeWarp") + by_pass = op_group_metadata["Operation"].get("Bypass") + number_inputs = op_group_metadata["Operation"].get("NumberInputs") + operation_category = op_group_metadata["Operation"].get("OperationCategory") + data_def_name = op_group_metadata["Operation"]["DataDefinition"]["Name"] + data_def = self.aaf_file.dictionary.lookup_datadef(str(data_def_name)) + description = op_group_metadata["Operation"]["Description"] + op_def_name = otio_transition.metadata["AAF"][ + "OperationGroup" + ]["Operation"]["Name"] + + # Create OperationDefinition + op_def = self.aaf_file.create.OperationDef(uuid.UUID(effect_id), op_def_name) + self.aaf_file.dictionary.register_def(op_def) + op_def.media_kind = self.media_kind + datadef = self.aaf_file.dictionary.lookup_datadef(self.media_kind) + op_def["IsTimeWarp"].value = is_time_warp + op_def["Bypass"].value = by_pass + op_def["NumberInputs"].value = number_inputs + op_def["OperationCategory"].value = str(operation_category) + op_def["ParametersDefined"].extend(transition_params) + op_def["DataDefinition"].value = data_def + op_def["Description"].value = str(description) + + # Create OperationGroup + length = otio_transition.duration().value + operation_group = self.aaf_file.create.OperationGroup(op_def, length) + operation_group["DataDefinition"].value = datadef + operation_group["Parameters"].append(varying_value) + + # Create Transition + transition = self.aaf_file.create.Transition(self.media_kind, length) + transition["OperationGroup"].value = operation_group + transition["CutPoint"].value = otio_transition.metadata["AAF"]["CutPoint"] + transition["DataDefinition"].value = datadef + return transition + + def aaf_sequence(self, otio_track): + """Convert an otio Track into an aaf Sequence""" + sequence = self.aaf_file.create.Sequence(media_kind=self.media_kind) + length = 0 + for nested_otio_child in otio_track: + result = self.transcribe(nested_otio_child) + length += result.length + sequence.components.append(result) + sequence.length = length + return sequence + + def aaf_operation_group(self, otio_stack): + """ + Create and return an OperationGroup which will contain other AAF objects + to support OTIO nesting + """ + # Create OperationDefinition + op_def = self.aaf_file.create.OperationDef(AAF_OPERATIONDEF_SUBMASTER, + "Submaster") + self.aaf_file.dictionary.register_def(op_def) + op_def.media_kind = self.media_kind + datadef = self.aaf_file.dictionary.lookup_datadef(self.media_kind) + + # These values are necessary for pyaaf2 OperationDefinitions + op_def["IsTimeWarp"].value = False + op_def["Bypass"].value = 0 + op_def["NumberInputs"].value = -1 + op_def["OperationCategory"].value = "OperationCategory_Effect" + op_def["DataDefinition"].value = datadef + + # Create OperationGroup + operation_group = self.aaf_file.create.OperationGroup(op_def) + operation_group.media_kind = self.media_kind + operation_group["DataDefinition"].value = datadef + + length = 0 + for nested_otio_child in otio_stack: + result = self.transcribe(nested_otio_child) + length += result.length + operation_group.segments.append(result) + operation_group.length = length + return operation_group + + def _create_tapemob(self, otio_clip): + """ + Return a physical sourcemob for an otio Clip based on the MobID. + + Returns: + Returns a tuple of (TapeMob, TapeMobSlot) + """ + tapemob = self.root_file_transcriber._unique_tapemob(otio_clip) + tapemob_slot = tapemob.create_empty_slot(self.edit_rate, self.media_kind) + tapemob_slot.segment.length = ( + otio_clip.media_reference.available_range.duration.value) + return tapemob, tapemob_slot + + def _create_filemob(self, otio_clip, tapemob, tapemob_slot): + """ + Return a file sourcemob for an otio Clip. Needs a tapemob and tapemob slot. + + Returns: + Returns a tuple of (FileMob, FileMobSlot) + """ + filemob = self.aaf_file.create.SourceMob() + self.aaf_file.content.mobs.append(filemob) + + filemob.descriptor = self.default_descriptor(otio_clip) + filemob_slot = filemob.create_timeline_slot(self.edit_rate) + filemob_clip = filemob.create_source_clip( + slot_id=filemob_slot.slot_id, + length=tapemob_slot.segment.length, + media_kind=tapemob_slot.segment.media_kind) + filemob_clip.mob = tapemob + filemob_clip.slot = tapemob_slot + filemob_clip.slot_id = tapemob_slot.slot_id + filemob_slot.segment = filemob_clip + return filemob, filemob_slot + + def _create_mastermob(self, otio_clip, filemob, filemob_slot): + """ + Return a mastermob for an otio Clip. Needs a filemob and filemob slot. + + Returns: + Returns a tuple of (MasterMob, MasterMobSlot) + """ + mastermob = self.root_file_transcriber._unique_mastermob(otio_clip) + timecode_length = otio_clip.media_reference.available_range.duration.value + + try: + mastermob_slot = mastermob.slot_at(self._master_mob_slot_id) + except IndexError: + mastermob_slot = ( + mastermob.create_timeline_slot(edit_rate=self.edit_rate, + slot_id=self._master_mob_slot_id)) + mastermob_clip = mastermob.create_source_clip( + slot_id=mastermob_slot.slot_id, + length=timecode_length, + media_kind=self.media_kind) + mastermob_clip.mob = filemob + mastermob_clip.slot = filemob_slot + mastermob_clip.slot_id = filemob_slot.slot_id + mastermob_slot.segment = mastermob_clip + return mastermob, mastermob_slot + + +class VideoTrackTranscriber(_TrackTranscriber): + """Video track kind specialization of TrackTranscriber.""" + + @property + def media_kind(self): + return "picture" + + @property + def _master_mob_slot_id(self): + return 1 + + def _create_timeline_mobslot(self): + """ + Create a Sequence container (TimelineMobSlot) and Sequence. + + TimelineMobSlot --> Sequence + """ + timeline_mobslot = self.compositionmob.create_timeline_slot( + edit_rate=self.edit_rate) + sequence = self.aaf_file.create.Sequence(media_kind=self.media_kind) + timeline_mobslot.segment = sequence + return timeline_mobslot, sequence + + def default_descriptor(self, otio_clip): + # TODO: Determine if these values are the correct, and if so, + # maybe they should be in the AAF metadata + descriptor = self.aaf_file.create.CDCIDescriptor() + descriptor["ComponentWidth"].value = 8 + descriptor["HorizontalSubsampling"].value = 2 + descriptor["ImageAspectRatio"].value = "16/9" + descriptor["StoredWidth"].value = 1920 + descriptor["StoredHeight"].value = 1080 + descriptor["FrameLayout"].value = "FullFrame" + descriptor["VideoLineMap"].value = [42, 0] + descriptor["SampleRate"].value = 24 + descriptor["Length"].value = 1 + return descriptor + + def _transition_parameters(self): + """ + Return video transition parameters + """ + # Create ParameterDef for AvidParameterByteOrder + byteorder_typedef = self.aaf_file.dictionary.lookup_typedef("aafUInt16") + param_byteorder = self.aaf_file.create.ParameterDef( + AAF_PARAMETERDEF_AVIDPARAMETERBYTEORDER, + "AvidParameterByteOrder", + "", + byteorder_typedef) + self.aaf_file.dictionary.register_def(param_byteorder) + + # Create ParameterDef for AvidEffectID + avid_effect_typdef = self.aaf_file.dictionary.lookup_typedef("AvidBagOfBits") + param_effect_id = self.aaf_file.create.ParameterDef( + AAF_PARAMETERDEF_AVIDEFFECTID, + "AvidEffectID", + "", + avid_effect_typdef) + self.aaf_file.dictionary.register_def(param_effect_id) + + # Create ParameterDef for AFX_FG_KEY_OPACITY_U + opacity_param_def = self.aaf_file.dictionary.lookup_typedef("Rational") + opacity_param = self.aaf_file.create.ParameterDef( + AAF_PARAMETERDEF_AFX_FG_KEY_OPACITY_U, + "AFX_FG_KEY_OPACITY_U", + "", + opacity_param_def) + self.aaf_file.dictionary.register_def(opacity_param) + + # Create VaryingValue + opacity_u = self.aaf_file.create.VaryingValue() + opacity_u.parameterdef = self.aaf_file.dictionary.lookup_parameterdef( + "AFX_FG_KEY_OPACITY_U") + opacity_u["VVal_Extrapolation"].value = AAF_VVAL_EXTRAPOLATION_ID + opacity_u["VVal_FieldCount"].value = 1 + + return [param_byteorder, param_effect_id], opacity_u + + +class AudioTrackTranscriber(_TrackTranscriber): + """Audio track kind specialization of TrackTranscriber.""" + + @property + def media_kind(self): + return "sound" + + @property + def _master_mob_slot_id(self): + return 2 + + def aaf_sourceclip(self, otio_clip): + # Parameter Definition + typedef = self.aaf_file.dictionary.lookup_typedef("Rational") + param_def = self.aaf_file.create.ParameterDef(AAF_PARAMETERDEF_PAN, + "Pan", + "Pan", + typedef) + self.aaf_file.dictionary.register_def(param_def) + interp_def = self.aaf_file.create.InterpolationDef(aaf2.misc.LinearInterp, + "LinearInterp", + "LinearInterp") + self.aaf_file.dictionary.register_def(interp_def) + # PointList + length = otio_clip.duration().value + c1 = self.aaf_file.create.ControlPoint() + c1["ControlPointSource"].value = 2 + c1["Time"].value = aaf2.rational.AAFRational("0/{}".format(length)) + c1["Value"].value = 0 + c2 = self.aaf_file.create.ControlPoint() + c2["ControlPointSource"].value = 2 + c2["Time"].value = aaf2.rational.AAFRational("{}/{}".format(length - 1, length)) + c2["Value"].value = 0 + varying_value = self.aaf_file.create.VaryingValue() + varying_value.parameterdef = param_def + varying_value["Interpolation"].value = interp_def + varying_value["PointList"].extend([c1, c2]) + opgroup = self.timeline_mobslot.segment + opgroup.parameters.append(varying_value) + + return super(AudioTrackTranscriber, self).aaf_sourceclip(otio_clip) + + def _create_timeline_mobslot(self): + """ + Create a Sequence container (TimelineMobSlot) and Sequence. + Sequence needs to be in an OperationGroup. + + TimelineMobSlot --> OperationGroup --> Sequence + """ + # TimelineMobSlot + timeline_mobslot = self.compositionmob.create_sound_slot( + edit_rate=self.edit_rate) + # OperationDefinition + opdef = self.aaf_file.create.OperationDef(AAF_OPERATIONDEF_MONOAUDIOPAN, + "Audio Pan") + opdef.media_kind = self.media_kind + opdef["NumberInputs"].value = 1 + self.aaf_file.dictionary.register_def(opdef) + # OperationGroup + total_length = sum([t.duration().value for t in self.otio_track]) + opgroup = self.aaf_file.create.OperationGroup(opdef) + opgroup.media_kind = self.media_kind + opgroup.length = total_length + timeline_mobslot.segment = opgroup + # Sequence + sequence = self.aaf_file.create.Sequence(media_kind=self.media_kind) + sequence.length = total_length + opgroup.segments.append(sequence) + return timeline_mobslot, sequence + + def default_descriptor(self, otio_clip): + descriptor = self.aaf_file.create.PCMDescriptor() + descriptor["AverageBPS"].value = 96000 + descriptor["BlockAlign"].value = 2 + descriptor["QuantizationBits"].value = 16 + descriptor["AudioSamplingRate"].value = 48000 + descriptor["Channels"].value = 1 + descriptor["SampleRate"].value = 48000 + descriptor["Length"].value = ( + otio_clip.media_reference.available_range.duration.value) + return descriptor + + def _transition_parameters(self): + """ + Return audio transition parameters + """ + # Create ParameterDef for ParameterDef_Level + def_level_typedef = self.aaf_file.dictionary.lookup_typedef("Rational") + param_def_level = self.aaf_file.create.ParameterDef(AAF_PARAMETERDEF_LEVEL, + "ParameterDef_Level", + "", + def_level_typedef) + self.aaf_file.dictionary.register_def(param_def_level) + + # Create VaryingValue + level = self.aaf_file.create.VaryingValue() + level.parameterdef = ( + self.aaf_file.dictionary.lookup_parameterdef("ParameterDef_Level")) + + return [param_def_level], level + + +class __check(object): + """ + __check is a private helper class that safely gets values given to check + for existence and equality + """ + + def __init__(self, obj, tokenpath): + self.orig = obj + self.value = obj + self.errors = [] + self.tokenpath = tokenpath + try: + for token in re.split(r"[\.\[]", tokenpath): + if token.endswith("()"): + self.value = getattr(self.value, token.replace("()", ""))() + elif "]" in token: + self.value = self.value[token.strip("[]'\"")] + else: + self.value = getattr(self.value, token) + except Exception as e: + self.value = None + self.errors.append("{}{} {}.{} does not exist, {}".format( + self.orig.name if hasattr(self.orig, "name") else "", + type(self.orig), + type(self.orig).__name__, + self.tokenpath, e)) + + def equals(self, val): + """Check if the retrieved value is equal to a given value.""" + if self.value is not None and self.value != val: + self.errors.append( + "{}{} {}.{} not equal to {} (expected) != {} (actual)".format( + self.orig.name if hasattr(self.orig, "name") else "", + type(self.orig), + type(self.orig).__name__, self.tokenpath, val, self.value)) + return self diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/advanced_authoring_format.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/advanced_authoring_format.py new file mode 100644 index 0000000000..6c21ea3e55 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/advanced_authoring_format.py @@ -0,0 +1,979 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO Advanced Authoring Format (AAF) Adapter + +Depending on if/where PyAAF is installed, you may need to set this env var: + OTIO_AAF_PYTHON_LIB - should point at the PyAAF module. +""" + +import os +import sys +import numbers +import copy +from collections import Iterable +import opentimelineio as otio + +lib_path = os.environ.get("OTIO_AAF_PYTHON_LIB") +if lib_path and lib_path not in sys.path: + sys.path.insert(0, lib_path) + +import aaf2 # noqa: E402 +import aaf2.content # noqa: E402 +import aaf2.mobs # noqa: E402 +import aaf2.components # noqa: E402 +import aaf2.core # noqa: E402 +from opentimelineio_contrib.adapters.aaf_adapter import aaf_writer # noqa: E402 + + +debug = False +__names = set() + + +def _get_parameter(item, parameter_name): + values = dict((value.name, value) for value in item.parameters.value) + return values.get(parameter_name) + + +def _get_name(item): + if isinstance(item, aaf2.components.SourceClip): + try: + return item.mob.name or "Untitled SourceClip" + except AttributeError: + # Some AAFs produce this error: + # RuntimeError: failed with [-2146303738]: mob not found + return "SourceClip Missing Mob?" + if hasattr(item, 'name'): + name = item.name + if name: + return name + return _get_class_name(item) + + +def _get_class_name(item): + if hasattr(item, "class_name"): + return item.class_name + else: + return item.__class__.__name__ + + +def _transcribe_property(prop): + # XXX: The unicode type doesn't exist in Python 3 (all strings are unicode) + # so we have to use type(u"") which works in both Python 2 and 3. + if isinstance(prop, (str, type(u""), numbers.Integral, float)): + return prop + + elif isinstance(prop, list): + result = {} + for child in prop: + if hasattr(child, "name") and hasattr(child, "value"): + result[child.name] = _transcribe_property(child.value) + else: + # @TODO: There may be more properties that we might want also. + # If you want to see what is being skipped, turn on debug. + if debug: + debug_message = \ + "Skipping unrecognized property: {} of parent {}" + print(debug_message.format(child, prop)) + return result + elif hasattr(prop, "properties"): + result = {} + for child in prop.properties(): + result[child.name] = _transcribe_property(child.value) + return result + else: + return str(prop) + + +def _find_timecode_mobs(item): + mobs = [item.mob] + + for c in item.walk(): + if isinstance(c, aaf2.components.SourceClip): + mob = c.mob + if mob: + mobs.append(mob) + else: + continue + else: + # This could be 'EssenceGroup', 'Pulldown' or other segment + # subclasses + # See also: https://jira.pixar.com/browse/SE-3457 + # For example: + # An EssenceGroup is a Segment that has one or more + # alternate choices, each of which represent different variations + # of one actual piece of content. + # According to the AAF Object Specification and Edit Protocol + # documents: + # "Typically the different representations vary in essence format, + # compression, or frame size. The application is responsible for + # choosing the appropriate implementation of the essence." + # It also says they should all have the same length, but + # there might be nested Sequences inside which we're not attempting + # to handle here (yet). We'll need a concrete example to ensure + # we're doing the right thing. + # TODO: Is the Timecode for an EssenceGroup correct? + # TODO: Try CountChoices() and ChoiceAt(i) + # For now, lets just skip it. + continue + + return mobs + + +def _extract_timecode_info(mob): + """Given a mob with a single timecode slot, return the timecode and length + in that slot as a tuple + """ + timecodes = [slot.segment for slot in mob.slots + if isinstance(slot.segment, aaf2.components.Timecode)] + + if len(timecodes) == 1: + timecode = timecodes[0] + timecode_start = timecode.getvalue('Start') + timecode_length = timecode.getvalue('Length') + + if timecode_start is None or timecode_length is None: + raise otio.exceptions.NotSupportedError( + "Unexpected timecode value(s) in mob named: `{}`." + " `Start`: {}, `Length`: {}".format(mob.name, + timecode_start, + timecode_length) + ) + + return timecode_start, timecode_length + elif len(timecodes) > 1: + raise otio.exceptions.NotSupportedError( + "Error: mob has more than one timecode slots, this is not" + " currently supported by the AAF adapter. found: {} slots, " + " mob name is: '{}'".format(len(timecodes), mob.name) + ) + else: + return None + + +def _add_child(parent, child, source): + if child is None: + if debug: + print("Adding null child? {}".format(source)) + elif isinstance(child, otio.schema.Marker): + parent.markers.append(child) + else: + parent.append(child) + + +def _transcribe(item, parents, editRate, masterMobs): + result = None + metadata = {} + + # First lets grab some standard properties that are present on + # many types of AAF objects... + metadata["Name"] = _get_name(item) + metadata["ClassName"] = _get_class_name(item) + + # Some AAF objects (like TimelineMobSlot) have an edit rate + # which should be used for all of the object's children. + # We will pass it on to any recursive calls to _transcribe() + if hasattr(item, "edit_rate"): + editRate = float(item.edit_rate) + + if isinstance(item, aaf2.components.Component): + metadata["Length"] = item.length + + if isinstance(item, aaf2.core.AAFObject): + for prop in item.properties(): + if hasattr(prop, 'name') and hasattr(prop, 'value'): + key = str(prop.name) + value = prop.value + metadata[key] = _transcribe_property(value) + + # Now we will use the item's class to determine which OTIO type + # to transcribe into. Note that the order of this if/elif/... chain + # is important, because the class hierarchy of AAF objects is more + # complex than OTIO. + + if isinstance(item, aaf2.content.ContentStorage): + result = otio.schema.SerializableCollection() + + # Gather all the Master Mobs, so we can find them later by MobID + # when we parse the SourceClips in the composition + if masterMobs is None: + masterMobs = {} + for mob in item.mastermobs(): + child = _transcribe(mob, parents + [item], editRate, masterMobs) + if child is not None: + mobID = child.metadata.get("AAF", {}).get("MobID") + masterMobs[mobID] = child + + for mob in item.compositionmobs(): + child = _transcribe(mob, parents + [item], editRate, masterMobs) + _add_child(result, child, mob) + + elif isinstance(item, aaf2.mobs.Mob): + result = otio.schema.Timeline() + + for slot in item.slots: + track = _transcribe(slot, parents + [item], editRate, masterMobs) + _add_child(result.tracks, track, slot) + + # Use a heuristic to find the starting timecode from + # this track and use it for the Timeline's global_start_time + start_time = _find_timecode_track_start(track) + if start_time: + result.global_start_time = start_time + + elif isinstance(item, aaf2.components.SourceClip): + result = otio.schema.Clip() + + # Evidently the last mob is the one with the timecode + mobs = _find_timecode_mobs(item) + # Get the Timecode start and length values + last_mob = mobs[-1] if mobs else None + timecode_info = _extract_timecode_info(last_mob) if last_mob else None + + source_start = int(metadata.get("StartTime", "0")) + source_length = item.length + media_start = source_start + media_length = item.length + + if timecode_info: + media_start, media_length = timecode_info + source_start += media_start + + # The goal here is to find a source range. Actual editorial opinions are found on SourceClips in the + # CompositionMobs. To figure out whether this clip is directly in the CompositionMob, we detect if our + # parent mobs are only CompositionMobs. If they were anything else - a MasterMob, a SourceMob, we would + # know that this is in some indirect relationship. + parent_mobs = filter(lambda parent: isinstance(parent, aaf2.mobs.Mob), parents) + is_directly_in_composition = all(isinstance(mob, aaf2.mobs.CompositionMob) for mob in parent_mobs) + if is_directly_in_composition: + result.source_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(source_start, editRate), + otio.opentime.RationalTime(source_length, editRate) + ) + + # The goal here is to find an available range. Media ranges are stored in the related MasterMob, and there + # should only be one - hence the name "Master" mob. Somewhere down our chain (either a child or our parents) + # is a MasterMob. For SourceClips in the CompositionMob, it is our child. For everything else, it is a + # previously encountered parent. Find the MasterMob in our chain, and then extract the information from that. + child_mastermob = item.mob if isinstance(item.mob, aaf2.mobs.MasterMob) else None + parent_mastermobs = [parent for parent in parents if isinstance(parent, aaf2.mobs.MasterMob)] + parent_mastermob = parent_mastermobs[0] if len(parent_mastermobs) > 1 else None + mastermob = child_mastermob or parent_mastermob or None + + if mastermob: + media = otio.schema.MissingReference() + media.available_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(media_start, editRate), + otio.opentime.RationalTime(media_length, editRate) + ) + # copy the metadata from the master into the media_reference + mastermob_child = masterMobs.get(str(mastermob.mob_id)) + media.metadata["AAF"] = mastermob_child.metadata.get("AAF", {}) + result.media_reference = media + + elif isinstance(item, aaf2.components.Transition): + result = otio.schema.Transition() + + # Does AAF support anything else? + result.transition_type = otio.schema.TransitionTypes.SMPTE_Dissolve + + # Extract value and time attributes of both ControlPoints used for + # creating AAF Transition objects + varying_value = None + for param in item.getvalue('OperationGroup').parameters: + if isinstance(param, aaf2.misc.VaryingValue): + varying_value = param + break + + if varying_value is not None: + for control_point in varying_value.getvalue('PointList'): + value = control_point.value + time = control_point.time + metadata.setdefault('PointList', []).append({'Value': value, + 'Time': time}) + + in_offset = int(metadata.get("CutPoint", "0")) + out_offset = item.length - in_offset + result.in_offset = otio.opentime.RationalTime(in_offset, editRate) + result.out_offset = otio.opentime.RationalTime(out_offset, editRate) + + elif isinstance(item, aaf2.components.Filler): + result = otio.schema.Gap() + + length = item.length + result.source_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(0, editRate), + otio.opentime.RationalTime(length, editRate) + ) + + elif isinstance(item, aaf2.components.NestedScope): + # TODO: Is this the right class? + result = otio.schema.Stack() + + for slot in item.slots: + child = _transcribe(slot, parents + [item], editRate, masterMobs) + _add_child(result, child, slot) + + elif isinstance(item, aaf2.components.Sequence): + result = otio.schema.Track() + + for component in item.components: + child = _transcribe(component, parents + [item], editRate, masterMobs) + _add_child(result, child, component) + + elif isinstance(item, aaf2.components.OperationGroup): + result = _transcribe_operation_group( + item, parents, metadata, editRate, masterMobs + ) + + elif isinstance(item, aaf2.mobslots.TimelineMobSlot): + result = otio.schema.Track() + + child = _transcribe(item.segment, parents + [item], editRate, masterMobs) + _add_child(result, child, item.segment) + + elif isinstance(item, aaf2.mobslots.MobSlot): + result = otio.schema.Track() + + child = _transcribe(item.segment, parents + [item], editRate, masterMobs) + _add_child(result, child, item.segment) + + elif isinstance(item, aaf2.components.Timecode): + pass + + elif isinstance(item, aaf2.components.Pulldown): + pass + + elif isinstance(item, aaf2.components.EdgeCode): + pass + + elif isinstance(item, aaf2.components.ScopeReference): + # TODO: is this like FILLER? + + result = otio.schema.Gap() + + length = item.length + result.source_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(0, editRate), + otio.opentime.RationalTime(length, editRate) + ) + + elif isinstance(item, aaf2.components.DescriptiveMarker): + + # Markers come in on their own separate Track. + # TODO: We should consolidate them onto the same track(s) as the clips + # result = otio.schema.Marker() + pass + + elif isinstance(item, aaf2.components.Selector): + # If you mute a clip in media composer, it becomes one of these in the + # AAF. + result = _transcribe( + item.getvalue("Selected"), + parents + [item], + editRate, + masterMobs + ) + + alternates = [ + _transcribe(alt, parents + [item], editRate, masterMobs) + for alt in item.getvalue("Alternates") + ] + + # muted case -- if there is only one item its muted, otherwise its + # a multi cam thing + if alternates and len(alternates) == 1: + metadata['muted_clip'] = True + result.name = str(alternates[0].name) + "_MUTED" + + metadata['alternates'] = alternates + + # @TODO: There are a bunch of other AAF object types that we will + # likely need to add support for. I'm leaving this code here to help + # future efforts to extract the useful information out of these. + + # elif isinstance(item, aaf.storage.File): + # self.extendChildItems([item.header]) + + # elif isinstance(item, aaf.storage.Header): + # self.extendChildItems([item.storage()]) + # self.extendChildItems([item.dictionary()]) + + # elif isinstance(item, aaf.dictionary.Dictionary): + # l = [] + # l.append(DummyItem(list(item.class_defs()), 'ClassDefs')) + # l.append(DummyItem(list(item.codec_defs()), 'CodecDefs')) + # l.append(DummyItem(list(item.container_defs()), 'ContainerDefs')) + # l.append(DummyItem(list(item.data_defs()), 'DataDefs')) + # l.append(DummyItem(list(item.interpolation_defs()), + # 'InterpolationDefs')) + # l.append(DummyItem(list(item.klvdata_defs()), 'KLVDataDefs')) + # l.append(DummyItem(list(item.operation_defs()), 'OperationDefs')) + # l.append(DummyItem(list(item.parameter_defs()), 'ParameterDefs')) + # l.append(DummyItem(list(item.plugin_defs()), 'PluginDefs')) + # l.append(DummyItem(list(item.taggedvalue_defs()), 'TaggedValueDefs')) + # l.append(DummyItem(list(item.type_defs()), 'TypeDefs')) + # self.extendChildItems(l) + # + # elif isinstance(item, pyaaf.AxSelector): + # self.extendChildItems(list(item.EnumAlternateSegments())) + # + # elif isinstance(item, pyaaf.AxScopeReference): + # #print item, item.GetRelativeScope(),item.GetRelativeSlot() + # pass + # + # elif isinstance(item, pyaaf.AxEssenceGroup): + # segments = [] + # + # for i in xrange(item.CountChoices()): + # choice = item.GetChoiceAt(i) + # segments.append(choice) + # self.extendChildItems(segments) + # + # elif isinstance(item, pyaaf.AxProperty): + # self.properties['Value'] = str(item.GetValue()) + + elif isinstance(item, Iterable): + result = otio.schema.SerializableCollection() + for child in item: + result.append( + _transcribe( + child, + parents + [item], + editRate, + masterMobs + ) + ) + else: + # For everything else, we just ignore it. + # To see what is being ignored, turn on the debug flag + if debug: + print("SKIPPING: {}: {} -- {}".format(type(item), item, result)) + + # Did we get anything? If not, we're done + if result is None: + return None + + # Okay, now we've turned the AAF thing into an OTIO result + # There's a bit more we can do before we're ready to return the result. + + # If we didn't get a name yet, use the one we have in metadata + if result.name is None: + result.name = metadata["Name"] + + # Attach the AAF metadata + if not result.metadata: + result.metadata = {} + result.metadata["AAF"] = metadata + + # Double check that we got the length we expected + if isinstance(result, otio.core.Item): + length = metadata.get("Length") + if ( + length + and result.source_range is not None + and result.source_range.duration.value != length + ): + raise otio.exceptions.OTIOError( + "Wrong duration? {} should be {} in {}".format( + result.source_range.duration.value, + length, + result + ) + ) + + # Did we find a Track? + if isinstance(result, otio.schema.Track): + # Try to figure out the kind of Track it is + if hasattr(item, 'media_kind'): + media_kind = str(item.media_kind) + result.metadata["AAF"]["MediaKind"] = media_kind + if media_kind == "Picture": + result.kind = otio.schema.TrackKind.Video + elif media_kind in ("SoundMasterTrack", "Sound"): + result.kind = otio.schema.TrackKind.Audio + else: + # Timecode, Edgecode, others? + result.kind = None + + # Done! + return result + + +def _find_timecode_track_start(track): + # See if we can find a starting timecode in here... + aaf_metadata = track.metadata.get("AAF", {}) + + # Is this a Timecode track? + if aaf_metadata.get("MediaKind") == "Timecode": + edit_rate = aaf_metadata.get("EditRate", "0") + fps = aaf_metadata.get("Segment", {}).get("FPS", 0) + start = aaf_metadata.get("Segment", {}).get("Start", "0") + + # Often times there are several timecode tracks, so + # we use a heuristic to only pay attention to Timecode + # tracks with a FPS that matches the edit rate. + if edit_rate == str(fps): + return otio.opentime.RationalTime( + value=int(start), + rate=float(edit_rate) + ) + + # We didn't find anything useful + return None + + +def _transcribe_linear_timewarp(item, parameters): + # this is a linear time warp + effect = otio.schema.LinearTimeWarp() + + offset_map = _get_parameter(item, 'PARAM_SPEED_OFFSET_MAP_U') + + # If we have a LinearInterp with just 2 control points, then + # we can compute the time_scalar. Note that the SpeedRatio is + # NOT correct in many AAFs - we aren't sure why, but luckily we + # can compute the correct value this way. + points = offset_map.get("PointList") + if len(points) > 2: + # This is something complicated... try the fancy version + return _transcribe_fancy_timewarp(item, parameters) + elif ( + len(points) == 2 + and float(points[0].time) == 0 + and float(points[0].value) == 0 + ): + # With just two points, we can compute the slope + effect.time_scalar = float(points[1].value) / float(points[1].time) + else: + # Fall back to the SpeedRatio if we didn't understand the points + ratio = parameters.get("SpeedRatio") + if ratio == str(item.length): + # If the SpeedRatio == the length, this is a freeze frame + effect.time_scalar = 0 + elif '/' in ratio: + numerator, denominator = map(float, ratio.split('/')) + # OTIO time_scalar is 1/x from AAF's SpeedRatio + effect.time_scalar = denominator / numerator + else: + effect.time_scalar = 1.0 / float(ratio) + + # Is this is a freeze frame? + if effect.time_scalar == 0: + # Note: we might end up here if any of the code paths above + # produced a 0 time_scalar. + # Use the FreezeFrame class instead of LinearTimeWarp + effect = otio.schema.FreezeFrame() + + return effect + + +def _transcribe_fancy_timewarp(item, parameters): + + # For now, this is an unsupported time effect... + effect = otio.schema.TimeEffect() + effect.effect_name = None # Unsupported + effect.name = item.get("Name") + + return effect + + # TODO: Here is some sample code that pulls out the full + # details of a non-linear speed map. + + # speed_map = item.parameter['PARAM_SPEED_MAP_U'] + # offset_map = item.parameter['PARAM_SPEED_OFFSET_MAP_U'] + # Also? PARAM_OFFSET_MAP_U (without the word "SPEED" in it?) + # print(speed_map['PointList'].value) + # print(speed_map.count()) + # print(speed_map.interpolation_def().name) + # + # for p in speed_map.points(): + # print(" ", float(p.time), float(p.value), p.edit_hint) + # for prop in p.point_properties(): + # print(" ", prop.name, prop.value, float(prop.value)) + # + # print(offset_map.interpolation_def().name) + # for p in offset_map.points(): + # edit_hint = p.edit_hint + # time = p.time + # value = p.value + # + # pass + # # print " ", float(p.time), float(p.value) + # + # for i in range(100): + # float(offset_map.value_at("%i/100" % i)) + # + # # Test file PARAM_SPEED_MAP_U is AvidBezierInterpolator + # # currently no implement for value_at + # try: + # speed_map.value_at(.25) + # except NotImplementedError: + # pass + # else: + # raise + + +def _transcribe_operation_group(item, parents, metadata, editRate, masterMobs): + result = otio.schema.Stack() + + operation = metadata.get("Operation", {}) + parameters = metadata.get("Parameters", {}) + result.name = operation.get("Name") + + # Trust the length that is specified in the AAF + length = metadata.get("Length") + result.source_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(0, editRate), + otio.opentime.RationalTime(length, editRate) + ) + + # Look for speed effects... + effect = None + if operation.get("IsTimeWarp"): + if operation.get("Name") == "Motion Control": + + offset_map = _get_parameter(item, 'PARAM_SPEED_OFFSET_MAP_U') + # TODO: We should also check the PARAM_OFFSET_MAP_U which has + # an interpolation_def().name as well. + if offset_map is not None: + interpolation = offset_map.interpolation.name + else: + interpolation = None + + if interpolation == "LinearInterp": + effect = _transcribe_linear_timewarp(item, parameters) + else: + effect = _transcribe_fancy_timewarp(item, parameters) + + else: + # Unsupported time effect + effect = otio.schema.TimeEffect() + effect.effect_name = None # Unsupported + effect.name = operation.get("Name") + else: + # Unsupported effect + effect = otio.schema.Effect() + effect.effect_name = None # Unsupported + effect.name = operation.get("Name") + + if effect is not None: + result.effects.append(effect) + effect.metadata = { + "AAF": { + "Operation": operation, + "Parameters": parameters + } + } + + for segment in item.getvalue("InputSegments"): + child = _transcribe(segment, parents + [item], editRate, masterMobs) + if child: + _add_child(result, child, segment) + + return result + + +def _fix_transitions(thing): + if isinstance(thing, otio.schema.Timeline): + _fix_transitions(thing.tracks) + elif ( + isinstance(thing, otio.core.Composition) + or isinstance(thing, otio.schema.SerializableCollection) + ): + if isinstance(thing, otio.schema.Track): + for c, child in enumerate(thing): + + # Don't touch the Transitions themselves, + # only the Clips & Gaps next to them. + if not isinstance(child, otio.core.Item): + continue + + # Was the item before us a Transition? + if c > 0 and isinstance( + thing[c - 1], + otio.schema.Transition + ): + pre_trans = thing[c - 1] + + if child.source_range is None: + child.source_range = child.trimmed_range() + csr = child.source_range + child.source_range = otio.opentime.TimeRange( + start_time=csr.start_time + pre_trans.in_offset, + duration=csr.duration - pre_trans.in_offset + ) + + # Is the item after us a Transition? + if c < len(thing) - 1 and isinstance( + thing[c + 1], + otio.schema.Transition + ): + post_trans = thing[c + 1] + + if child.source_range is None: + child.source_range = child.trimmed_range() + csr = child.source_range + child.source_range = otio.opentime.TimeRange( + start_time=csr.start_time, + duration=csr.duration - post_trans.out_offset + ) + + for child in thing: + _fix_transitions(child) + + +def _simplify(thing): + if isinstance(thing, otio.schema.SerializableCollection): + if len(thing) == 1: + return _simplify(thing[0]) + else: + for c, child in enumerate(thing): + thing[c] = _simplify(child) + return thing + + elif isinstance(thing, otio.schema.Timeline): + result = _simplify(thing.tracks) + + # Only replace the Timeline's stack if the simplified result + # was also a Stack. Otherwise leave it (the contents will have + # been simplified in place). + if isinstance(result, otio.schema.Stack): + thing.tracks = result + + return thing + + elif isinstance(thing, otio.core.Composition): + # simplify our children + for c, child in enumerate(thing): + thing[c] = _simplify(child) + + # remove empty children of Stacks + if isinstance(thing, otio.schema.Stack): + for c in reversed(range(len(thing))): + child = thing[c] + if not _contains_something_valuable(child): + # TODO: We're discarding metadata... should we retain it? + del thing[c] + + # Look for Stacks within Stacks + c = len(thing) - 1 + while c >= 0: + child = thing[c] + # Is my child a Stack also? (with no effects) + if ( + not _has_effects(child) + and + ( + isinstance(child, otio.schema.Stack) + or ( + isinstance(child, otio.schema.Track) + and len(child) == 1 + and isinstance(child[0], otio.schema.Stack) + and child[0] + and isinstance(child[0][0], otio.schema.Track) + ) + ) + ): + if isinstance(child, otio.schema.Track): + child = child[0] + + # Pull the child's children into the parent + num = len(child) + children_of_child = child[:] + # clear out the ownership of 'child' + del child[:] + thing[c:c + 1] = children_of_child + + # TODO: We may be discarding metadata, should we merge it? + # TODO: Do we need to offset the markers in time? + thing.markers.extend(child.markers) + # Note: we don't merge effects, because we already made + # sure the child had no effects in the if statement above. + + c = c + num + c = c - 1 + + # skip redundant containers + if _is_redundant_container(thing): + # TODO: We may be discarding metadata here, should we merge it? + result = thing[0].deepcopy() + # TODO: Do we need to offset the markers in time? + result.markers.extend(thing.markers) + # TODO: The order of the effects is probably important... + # should they be added to the end or the front? + # Intuitively it seems like the child's effects should come before + # the parent's effects. This will need to be solidified when we + # add more effects support. + result.effects.extend(thing.effects) + # Keep the parent's length, if it has one + if thing.source_range: + # make sure it has a source_range first + if not result.source_range: + try: + result.source_range = result.trimmed_range() + except otio.exceptions.CannotComputeAvailableRangeError: + result.source_range = copy.copy(thing.source_range) + # modify the duration, but leave the start_time as is + result.source_range = otio.opentime.TimeRange( + result.source_range.start_time, + thing.source_range.duration + ) + return result + + # if thing is the top level stack, all of its children must be in tracks + if isinstance(thing, otio.schema.Stack) and thing.parent() is None: + children_needing_tracks = [] + for child in thing: + if isinstance(child, otio.schema.Track): + continue + children_needing_tracks.append(child) + + for child in children_needing_tracks: + orig_index = thing.index(child) + del thing[orig_index] + new_track = otio.schema.Track() + new_track.append(child) + thing.insert(orig_index, new_track) + + return thing + + +def _has_effects(thing): + if isinstance(thing, otio.core.Item): + if len(thing.effects) > 0: + return True + + +def _is_redundant_container(thing): + + is_composition = isinstance(thing, otio.core.Composition) + if not is_composition: + return False + + has_one_child = len(thing) == 1 + if not has_one_child: + return False + + am_top_level_track = ( + type(thing) is otio.schema.Track + and type(thing.parent()) is otio.schema.Stack + and thing.parent().parent() is None + ) + + return ( + not am_top_level_track + # am a top level track but my only child is a track + or ( + type(thing) is otio.schema.Track + and type(thing[0]) is otio.schema.Track + ) + ) + + +def _contains_something_valuable(thing): + if isinstance(thing, otio.core.Item): + if len(thing.effects) > 0 or len(thing.markers) > 0: + return True + + if isinstance(thing, otio.core.Composition): + + if len(thing) == 0: + # NOT valuable because it is empty + return False + + for child in thing: + if _contains_something_valuable(child): + # valuable because this child is valuable + return True + + # none of the children were valuable, so thing is NOT valuable + return False + + if isinstance(thing, otio.schema.Gap): + # TODO: Are there other valuable things we should look for on a Gap? + return False + + # anything else is presumed to be valuable + return True + + +def read_from_file(filepath, simplify=True): + + with aaf2.open(filepath) as aaf_file: + + storage = aaf_file.content + + # Note: We're skipping: f.header + # Is there something valuable in there? + + __names.clear() + masterMobs = {} + + result = _transcribe(storage, parents=list(), editRate=None, masterMobs=masterMobs) + top = storage.toplevel() + if top: + # re-transcribe just the top-level mobs + # but use all the master mobs we found in the 1st pass + __names.clear() # reset the names back to 0 + result = _transcribe(top, parents=list(), editRate=None, masterMobs=masterMobs) + + # AAF is typically more deeply nested than OTIO. + # Lets try to simplify the structure by collapsing or removing + # unnecessary stuff. + if simplify: + result = _simplify(result) + + # OTIO represents transitions a bit different than AAF, so + # we need to iterate over them and modify the items on either side. + # Note that we do this *after* simplifying, since the structure + # may change during simplification. + _fix_transitions(result) + + return result + + +def write_to_file(input_otio, filepath, **kwargs): + with aaf2.open(filepath, "w") as f: + + timeline = aaf_writer._stackify_nested_groups(input_otio) + + aaf_writer.validate_metadata(timeline) + + otio2aaf = aaf_writer.AAFFileTranscriber(timeline, f, **kwargs) + + if not isinstance(timeline, otio.schema.Timeline): + raise otio.exceptions.NotSupportedError( + "Currently only supporting top level Timeline") + + for otio_track in timeline.tracks: + # Ensure track must have clip to get the edit_rate + if len(otio_track) == 0: + continue + + transcriber = otio2aaf.track_transcriber(otio_track) + + for otio_child in otio_track: + result = transcriber.transcribe(otio_child) + if result: + transcriber.sequence.components.append(result) diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ale.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ale.py new file mode 100644 index 0000000000..150ed6d93d --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ale.py @@ -0,0 +1,318 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO Avid Log Exchange (ALE) Adapter""" +import re +import opentimelineio as otio + +DEFAULT_VIDEO_FORMAT = '1080' + + +def AVID_VIDEO_FORMAT_FROM_WIDTH_HEIGHT(width, height): + """Utility function to map a width and height to an Avid Project Format""" + + format_map = { + '1080': "1080", + '720': "720", + '576': "PAL", + '486': "NTSC", + } + mapped = format_map.get(str(height), "CUSTOM") + # check for the 2K DCI 1080 format + if mapped == '1080' and width > 1920: + mapped = "CUSTOM" + return mapped + + +class ALEParseError(otio.exceptions.OTIOError): + pass + + +def _parse_data_line(line, columns, fps): + row = line.split("\t") + + if len(row) < len(columns): + # Fill in blanks for any missing fields in this row + row.extend([""] * (len(columns) - len(row))) + + if len(row) > len(columns): + raise ALEParseError("Too many values on row: " + line) + + try: + + # Gather all the columns into a dictionary + # For expected columns, like Name, Start, etc. we will pop (remove) + # those from metadata, leaving the rest alone. + metadata = dict(zip(columns, row)) + + clip = otio.schema.Clip() + clip.name = metadata.pop("Name", None) + + # When looking for Start, Duration and End, they might be missing + # or blank. Treat None and "" as the same via: get(k,"")!="" + # To have a valid source range, you need Start and either Duration + # or End. If all three are provided, we check to make sure they match. + if metadata.get("Start", "") != "": + value = metadata.pop("Start") + try: + start = otio.opentime.from_timecode(value, fps) + except (ValueError, TypeError): + raise ALEParseError("Invalid Start timecode: {}".format(value)) + duration = None + end = None + if metadata.get("Duration", "") != "": + value = metadata.pop("Duration") + try: + duration = otio.opentime.from_timecode(value, fps) + except (ValueError, TypeError): + raise ALEParseError("Invalid Duration timecode: {}".format( + value + )) + if metadata.get("End", "") != "": + value = metadata.pop("End") + try: + end = otio.opentime.from_timecode(value, fps) + except (ValueError, TypeError): + raise ALEParseError("Invalid End timecode: {}".format( + value + )) + if duration is None: + duration = end - start + if end is None: + end = start + duration + if end != start + duration: + raise ALEParseError( + "Inconsistent Start, End, Duration: " + line + ) + clip.source_range = otio.opentime.TimeRange( + start, + duration + ) + + if metadata.get("Source File"): + source = metadata.pop("Source File") + clip.media_reference = otio.schema.ExternalReference( + target_url=source + ) + + # We've pulled out the key/value pairs that we treat specially. + # Put the remaining key/values into clip.metadata["ALE"] + clip.metadata["ALE"] = metadata + + return clip + except Exception as ex: + raise ALEParseError("Error parsing line: {}\n{}".format( + line, repr(ex) + )) + + +def _video_format_from_metadata(clips): + # Look for clips with Image Size metadata set + max_height = 0 + max_width = 0 + for clip in clips: + fields = clip.metadata.get("ALE", {}) + res = fields.get("Image Size", "") + m = re.search(r'([0-9]{1,})\s*[xX]\s*([0-9]{1,})', res) + if m and len(m.groups()) >= 2: + width = int(m.group(1)) + height = int(m.group(2)) + if height > max_height: + max_height = height + if width > max_width: + max_width = width + + # We don't have any image size information, use the defaut + if max_height == 0: + return DEFAULT_VIDEO_FORMAT + else: + return AVID_VIDEO_FORMAT_FROM_WIDTH_HEIGHT(max_width, max_height) + + +def read_from_string(input_str, fps=24): + + collection = otio.schema.SerializableCollection() + header = {} + columns = [] + + def nextline(lines): + return lines.pop(0) + + lines = input_str.splitlines() + while len(lines): + line = nextline(lines) + + # skip blank lines + if line.strip() == "": + continue + + if line.strip() == "Heading": + while len(lines): + line = nextline(lines) + + if line.strip() == "": + break + + if "\t" not in line: + raise ALEParseError("Invalid Heading line: " + line) + + segments = line.split("\t") + while len(segments) >= 2: + key, val = segments.pop(0), segments.pop(0) + header[key] = val + if len(segments) != 0: + raise ALEParseError("Invalid Heading line: " + line) + + if "FPS" in header: + fps = float(header["FPS"]) + + if line.strip() == "Column": + if len(lines) == 0: + raise ALEParseError("Unexpected end of file after: " + line) + + line = nextline(lines) + columns = line.split("\t") + + if line.strip() == "Data": + while len(lines): + line = nextline(lines) + + if line.strip() == "": + continue + + clip = _parse_data_line(line, columns, fps) + + collection.append(clip) + + collection.metadata["ALE"] = { + "header": header, + "columns": columns + } + + return collection + + +def write_to_string(input_otio, columns=None, fps=None, video_format=None): + + # Get all the clips we're going to export + clips = list(input_otio.each_clip()) + + result = "" + + result += "Heading\n" + header = dict(input_otio.metadata.get("ALE", {}).get("header", {})) + + # Force this, since we've hard coded tab delimiters + header["FIELD_DELIM"] = "TABS" + + if fps is None: + # If we weren't given a FPS, is there one in the header metadata? + if "FPS" in header: + fps = float(header["FPS"]) + else: + # Would it be better to infer this by inspecting the input clips? + fps = 24 + header["FPS"] = str(fps) + else: + # Put the value we were given into the header + header["FPS"] = str(fps) + + # Check if we have been supplied a VIDEO_FORMAT, if not lets set one + if video_format is None: + # Do we already have it in the header? If so, lets leave that as is + if "VIDEO_FORMAT" not in header: + header["VIDEO_FORMAT"] = _video_format_from_metadata(clips) + else: + header["VIDEO_FORMAT"] = str(video_format) + + headers = list(header.items()) + headers.sort() # make the output predictable + for key, val in headers: + result += "{}\t{}\n".format(key, val) + + # If the caller passed in a list of columns, use that, otherwise + # we need to discover the columns that should be output. + if columns is None: + # Is there a hint about the columns we want (and column ordering) + # at the top level? + columns = input_otio.metadata.get("ALE", {}).get("columns", []) + + # Scan all the clips for any extra columns + for clip in clips: + fields = clip.metadata.get("ALE", {}) + for key in fields.keys(): + if key not in columns: + columns.append(key) + + # Always output these + for c in ["Duration", "End", "Start", "Name", "Source File"]: + if c not in columns: + columns.insert(0, c) + + result += "\nColumn\n{}\n".format("\t".join(columns)) + + result += "\nData\n" + + def val_for_column(column, clip): + if column == "Name": + return clip.name + elif column == "Source File": + if ( + clip.media_reference and + hasattr(clip.media_reference, 'target_url') and + clip.media_reference.target_url + ): + return clip.media_reference.target_url + else: + return "" + elif column == "Start": + if not clip.source_range: + return "" + return otio.opentime.to_timecode( + clip.source_range.start_time, fps + ) + elif column == "Duration": + if not clip.source_range: + return "" + return otio.opentime.to_timecode( + clip.source_range.duration, fps + ) + elif column == "End": + if not clip.source_range: + return "" + return otio.opentime.to_timecode( + clip.source_range.end_time_exclusive(), fps + ) + else: + return clip.metadata.get("ALE", {}).get(column) + + for clip in clips: + row = [] + for column in columns: + val = str(val_for_column(column, clip) or "") + val.replace("\t", " ") # don't allow tabs inside a value + row.append(val) + result += "\t".join(row) + "\n" + + return result diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/burnins.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/burnins.py new file mode 100644 index 0000000000..93741bbb14 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/burnins.py @@ -0,0 +1,93 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# +"""FFMPEG Burnins Adapter""" +import os +import sys + + +def build_burnins(input_otio): + """ + Generates the burnin objects for each clip within the otio container + + :param input_otio: OTIO container + :rtype: [ffmpeg_burnins.Burnins(), ...] + """ + + if os.path.dirname(__file__) not in sys.path: + sys.path.append(os.path.dirname(__file__)) + + import ffmpeg_burnins + key = 'burnins' + + burnins = [] + for clip in input_otio.each_clip(): + + # per clip burnin data + burnin_data = clip.media_reference.metadata.get(key) + if not burnin_data: + # otherwise default to global burnin + burnin_data = input_otio.metadata.get(key) + + if not burnin_data: + continue + + media = clip.media_reference.target_url + if media.startswith('file://'): + media = media[7:] + streams = burnin_data.get('streams') + burnins.append(ffmpeg_burnins.Burnins(media, + streams=streams)) + burnins[-1].otio_media = media + burnins[-1].otio_overwrite = burnin_data.get('overwrite') + burnins[-1].otio_args = burnin_data.get('args') + + for burnin in burnin_data.get('burnins', []): + align = burnin.pop('align') + function = burnin.pop('function') + if function == 'text': + text = burnin.pop('text') + options = ffmpeg_burnins.TextOptions() + options.update(burnin) + burnins[-1].add_text(text, align, options=options) + elif function == 'frame_number': + options = ffmpeg_burnins.FrameNumberOptions() + options.update(burnin) + burnins[-1].add_frame_numbers(align, options=options) + elif function == 'timecode': + options = ffmpeg_burnins.TimeCodeOptions() + options.update(burnin) + burnins[-1].add_timecode(align, options=options) + else: + raise RuntimeError("Unknown function '%s'" % function) + + return burnins + + +def write_to_file(input_otio, filepath): + """required OTIO function hook""" + + for burnin in build_burnins(input_otio): + burnin.render(os.path.join(filepath, burnin.otio_media), + args=burnin.otio_args, + overwrite=burnin.otio_overwrite) diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/contrib_adapters.plugin_manifest.json b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/contrib_adapters.plugin_manifest.json new file mode 100644 index 0000000000..ceaf0a3067 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/contrib_adapters.plugin_manifest.json @@ -0,0 +1,61 @@ +{ + "OTIO_SCHEMA" : "PluginManifest.1", + "adapters": [ + { + "OTIO_SCHEMA": "Adapter.1", + "name": "fcpx_xml", + "execution_scope": "in process", + "filepath": "fcpx_xml.py", + "suffixes": ["fcpxml"] + }, + { + "OTIO_SCHEMA": "Adapter.1", + "name": "hls_playlist", + "execution_scope": "in process", + "filepath": "hls_playlist.py", + "suffixes": ["m3u8"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "rv_session", + "execution_scope" : "in process", + "filepath" : "rv.py", + "suffixes" : ["rv"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "maya_sequencer", + "execution_scope" : "in process", + "filepath" : "maya_sequencer.py", + "suffixes" : ["ma","mb"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "ale", + "execution_scope" : "in process", + "filepath" : "ale.py", + "suffixes" : ["ale"] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "burnins", + "execution_scope" : "in process", + "filepath" : "burnins.py", + "suffixes" : [] + }, + { + "OTIO_SCHEMA" : "Adapter.1", + "name" : "AAF", + "execution_scope" : "in process", + "filepath" : "advanced_authoring_format.py", + "suffixes" : ["aaf"] + }, + { + "OTIO_SCHEMA": "Adapter.1", + "name": "xges", + "execution_scope": "in process", + "filepath": "xges.py", + "suffixes": ["xges"] + } + ] +} diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_maya_sequencer.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_maya_sequencer.py new file mode 100644 index 0000000000..45d77976cf --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_maya_sequencer.py @@ -0,0 +1,261 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +import os +import sys + +# deal with renaming of default library from python 2 / 3 +try: + import urlparse as urllib_parse +except ImportError: + import urllib.parse as urllib_parse + +# import maya and handle standalone mode +from maya import cmds + +try: + cmds.ls +except AttributeError: + from maya import standalone + standalone.initialize(name='python') + +import opentimelineio as otio + +# Mapping of Maya FPS Enum to rate. +FPS = { + 'game': 15, + 'film': 24, + 'pal': 25, + 'ntsc': 30, + 'show': 48, + 'palf': 50, + 'ntscf': 60 +} + + +def _url_to_path(url): + if url is None: + return None + + return urllib_parse.urlparse(url).path + + +def _video_url_for_shot(shot): + current_file = os.path.normpath(cmds.file(q=True, sn=True)) + return os.path.join( + os.path.dirname(current_file), + 'playblasts', + '{base_name}_{shot_name}.mov'.format( + base_name=os.path.basename(os.path.splitext(current_file)[0]), + shot_name=cmds.shot(shot, q=True, shotName=True) + ) + ) + + +def _match_existing_shot(item, existing_shots): + if existing_shots is None: + return None + + if item.media_reference.is_missing_reference: + return None + + url_path = _url_to_path(item.media_reference.target_url) + return next( + ( + shot for shot in existing_shots + if _video_url_for_shot(shot) == url_path + ), + None + ) + + +# ------------------------ +# building single track +# ------------------------ + +def _build_shot(item, track_no, track_range, existing_shot=None): + camera = None + if existing_shot is None: + camera = cmds.camera(name=item.name.split('.')[0] + '_cam')[0] + cmds.shot( + existing_shot or item.name.split('.')[0], + e=existing_shot is not None, + shotName=item.name, + track=track_no, + currentCamera=camera, + startTime=item.trimmed_range().start_time.value, + endTime=item.trimmed_range().end_time_inclusive().value, + sequenceStartTime=track_range.start_time.value, + sequenceEndTime=track_range.end_time_inclusive().value + ) + + +def _build_track(track, track_no, existing_shots=None): + for n, item in enumerate(track): + if not isinstance(item, otio.schema.Clip): + continue + + track_range = track.range_of_child_at_index(n) + if existing_shots is not None: + existing_shot = _match_existing_shot(item, existing_shots) + else: + existing_shot = None + + _build_shot(item, track_no, track_range, existing_shot) + + +def build_sequence(timeline, clean=False): + existing_shots = cmds.ls(type='shot') or [] + if clean: + cmds.delete(existing_shots) + existing_shots = [] + + tracks = [ + track for track in timeline.tracks + if track.kind == otio.schema.TrackKind.Video + ] + + for track_no, track in enumerate(reversed(tracks)): + _build_track(track, track_no, existing_shots=existing_shots) + + +def read_from_file(path, clean=True): + timeline = otio.adapters.read_from_file(path) + build_sequence(timeline, clean=clean) + + +# ----------------------- +# parsing single track +# ----------------------- + +def _get_gap(duration): + rate = FPS.get(cmds.currentUnit(q=True, time=True), 25) + gap_range = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime(duration, rate) + ) + return otio.schema.Gap(source_range=gap_range) + + +def _read_shot(shot): + rate = FPS.get(cmds.currentUnit(q=True, time=True), 25) + start = int(cmds.shot(shot, q=True, startTime=True)) + end = int(cmds.shot(shot, q=True, endTime=True)) + 1 + + video_reference = otio.schema.ExternalReference( + target_url=_video_url_for_shot(shot), + available_range=otio.opentime.TimeRange( + otio.opentime.RationalTime(value=start, rate=rate), + otio.opentime.RationalTime(value=end - start, rate=rate) + ) + ) + + return otio.schema.Clip( + name=cmds.shot(shot, q=True, shotName=True), + media_reference=video_reference, + source_range=otio.opentime.TimeRange( + otio.opentime.RationalTime(value=start, rate=rate), + otio.opentime.RationalTime(value=end - start, rate=rate) + ) + ) + + +def _read_track(shots): + v = otio.schema.Track(kind=otio.schema.track.TrackKind.Video) + + last_clip_end = 0 + for shot in shots: + seq_start = int(cmds.shot(shot, q=True, sequenceStartTime=True)) + seq_end = int(cmds.shot(shot, q=True, sequenceEndTime=True)) + + # add gap if necessary + fill_time = seq_start - last_clip_end + last_clip_end = seq_end + 1 + if fill_time: + v.append(_get_gap(fill_time)) + + # add clip + v.append(_read_shot(shot)) + + return v + + +def read_sequence(): + rate = FPS.get(cmds.currentUnit(q=True, time=True), 25) + shots = cmds.ls(type='shot') or [] + per_track = {} + + for shot in shots: + track_no = cmds.shot(shot, q=True, track=True) + if track_no not in per_track: + per_track[track_no] = [] + per_track[track_no].append(shot) + + timeline = otio.schema.Timeline() + timeline.global_start_time = otio.opentime.RationalTime(0, rate) + + for track_no in reversed(sorted(per_track.keys())): + track_shots = per_track[track_no] + timeline.tracks.append(_read_track(track_shots)) + + return timeline + + +def write_to_file(path): + timeline = read_sequence() + otio.adapters.write_to_file(timeline, path) + + +def main(): + read_write_arg = sys.argv[1] + filepath = sys.argv[2] + + write = False + if read_write_arg == "write": + write = True + + if write: + # read the input OTIO off stdin + input_otio = otio.adapters.read_from_string( + sys.stdin.read(), + 'otio_json' + ) + build_sequence(input_otio, clean=True) + cmds.file(rename=filepath) + cmds.file(save=True, type="mayaAscii") + else: + cmds.file(filepath, o=True) + sys.stdout.write( + "\nOTIO_JSON_BEGIN\n" + + otio.adapters.write_to_string( + read_sequence(), + "otio_json" + ) + + "\nOTIO_JSON_END\n" + ) + + cmds.quit(force=True) + + +if __name__ == "__main__": + main() diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_rv.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_rv.py new file mode 100644 index 0000000000..f11295bb60 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/extern_rv.py @@ -0,0 +1,327 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""RV External Adapter component. + +Because the rv adapter requires being run from within the RV py-interp to take +advantage of modules inside of RV, this script gets shelled out to from the +RV OTIO adapter. + +Requires that you set the environment variables: + OTIO_RV_PYTHON_LIB - should point at the parent directory of rvSession + OTIO_RV_PYTHON_BIN - should point at py-interp from within rv +""" + +# python +import sys +import os + +# otio +import opentimelineio as otio + +# rv import +sys.path += [os.path.join(os.environ["OTIO_RV_PYTHON_LIB"], "rvSession")] +import rvSession # noqa + + +def main(): + """ entry point, should be called from the rv adapter in otio """ + + session_file = rvSession.Session() + + output_fname = sys.argv[1] + + # read the input OTIO off stdin + input_otio = otio.adapters.read_from_string(sys.stdin.read(), 'otio_json') + + result = write_otio(input_otio, session_file) + session_file.setViewNode(result) + session_file.write(output_fname) + + +# exception class @{ +class NoMappingForOtioTypeError(otio.exceptions.OTIOError): + pass +# @} + + +def write_otio(otio_obj, to_session, track_kind=None): + WRITE_TYPE_MAP = { + otio.schema.Timeline: _write_timeline, + otio.schema.Stack: _write_stack, + otio.schema.Track: _write_track, + otio.schema.Clip: _write_item, + otio.schema.Gap: _write_item, + otio.schema.Transition: _write_transition, + otio.schema.SerializableCollection: _write_collection, + } + + if type(otio_obj) in WRITE_TYPE_MAP: + return WRITE_TYPE_MAP[type(otio_obj)](otio_obj, to_session, track_kind) + + raise NoMappingForOtioTypeError( + str(type(otio_obj)) + " on object: {}".format(otio_obj) + ) + + +def _write_dissolve(pre_item, in_dissolve, post_item, to_session, track_kind=None): + rv_trx = to_session.newNode("CrossDissolve", str(in_dissolve.name)) + + rate = pre_item.trimmed_range().duration.rate + rv_trx.setProperty( + "CrossDissolve", + "", + "parameters", + "startFrame", + rvSession.gto.FLOAT, + 1.0 + ) + rv_trx.setProperty( + "CrossDissolve", + "", + "parameters", + "numFrames", + rvSession.gto.FLOAT, + int( + ( + in_dissolve.in_offset + + in_dissolve.out_offset + ).rescaled_to(rate).value + ) + ) + + rv_trx.setProperty( + "CrossDissolve", + "", + "output", + "fps", + rvSession.gto.FLOAT, + rate + ) + + pre_item_rv = write_otio(pre_item, to_session, track_kind) + rv_trx.addInput(pre_item_rv) + + post_item_rv = write_otio(post_item, to_session, track_kind) + + node_to_insert = post_item_rv + + if ( + hasattr(pre_item, "media_reference") + and pre_item.media_reference + and pre_item.media_reference.available_range + and hasattr(post_item, "media_reference") + and post_item.media_reference + and post_item.media_reference.available_range + and ( + post_item.media_reference.available_range.start_time.rate != + pre_item.media_reference.available_range.start_time.rate + ) + ): + # write a retime to make sure post_item is in the timebase of pre_item + rt_node = to_session.newNode("Retime", "transition_retime") + rt_node.setTargetFps( + pre_item.media_reference.available_range.start_time.rate + ) + + post_item_rv = write_otio(post_item, to_session, track_kind) + + rt_node.addInput(post_item_rv) + node_to_insert = rt_node + + rv_trx.addInput(node_to_insert) + + return rv_trx + + +def _write_transition( + pre_item, + in_trx, + post_item, + to_session, + track_kind=None +): + trx_map = { + otio.schema.TransitionTypes.SMPTE_Dissolve: _write_dissolve, + } + + if in_trx.transition_type not in trx_map: + return + + return trx_map[in_trx.transition_type]( + pre_item, + in_trx, + post_item, + to_session, + track_kind + ) + + +def _write_stack(in_stack, to_session, track_kind=None): + new_stack = to_session.newNode("Stack", str(in_stack.name) or "tracks") + + for seq in in_stack: + result = write_otio(seq, to_session, track_kind) + if result: + new_stack.addInput(result) + + return new_stack + + +def _write_track(in_seq, to_session, _=None): + new_seq = to_session.newNode("Sequence", str(in_seq.name) or "track") + + items_to_serialize = otio.algorithms.track_with_expanded_transitions( + in_seq + ) + + track_kind = in_seq.kind + + for thing in items_to_serialize: + if isinstance(thing, tuple): + result = _write_transition(*thing, to_session=to_session, + track_kind=track_kind) + elif thing.duration().value == 0: + continue + else: + result = write_otio(thing, to_session, track_kind) + + if result: + new_seq.addInput(result) + + return new_seq + + +def _write_timeline(tl, to_session, _=None): + result = write_otio(tl.tracks, to_session) + return result + + +def _write_collection(collection, to_session, track_kind=None): + results = [] + for item in collection: + result = write_otio(item, to_session, track_kind) + if result: + results.append(result) + + if results: + return results[0] + + +def _create_media_reference(item, src, track_kind=None): + if hasattr(item, "media_reference") and item.media_reference: + if isinstance(item.media_reference, otio.schema.ExternalReference): + media = [str(item.media_reference.target_url)] + + if track_kind == otio.schema.TrackKind.Audio: + # Create blank video media to accompany audio for valid source + blank = "{},start={},end={},fps={}.movieproc".format( + "blank", + item.available_range().start_time.value, + item.available_range().end_time_inclusive().value, + item.available_range().duration.rate + ) + # Inserting blank media here forces all content to only + # produce audio. We do it twice in case we look at this in + # stereo + media = [blank, blank] + media + + src.setMedia(media) + return True + + elif isinstance(item.media_reference, otio.schema.GeneratorReference): + if item.media_reference.generator_kind == "SMPTEBars": + kind = "smptebars" + src.setMedia( + [ + "{},start={},end={},fps={}.movieproc".format( + kind, + item.available_range().start_time.value, + item.available_range().end_time_inclusive().value, + item.available_range().duration.rate + ) + ] + ) + return True + + return False + + +def _write_item(it, to_session, track_kind=None): + src = to_session.newNode("Source", str(it.name) or "clip") + + src.setProperty( + "RVSourceGroup", + "source", + "attributes", + "otio_metadata", + rvSession.gto.STRING, str(it.metadata) + ) + + range_to_read = it.trimmed_range() + + if not range_to_read: + raise otio.exceptions.OTIOError( + "No valid range on clip: {0}.".format( + str(it) + ) + ) + + # because OTIO has no global concept of FPS, the rate of the duration is + # used as the rate for the range of the source. + # RationalTime.value_rescaled_to returns the time value of the object in + # time rate of the argument. + src.setCutIn( + range_to_read.start_time.value_rescaled_to( + range_to_read.duration + ) + ) + src.setCutOut( + range_to_read.end_time_inclusive().value_rescaled_to( + range_to_read.duration + ) + ) + src.setFPS(range_to_read.duration.rate) + + # if the media reference is missing + if not _create_media_reference(it, src, track_kind): + kind = "smptebars" + if isinstance(it, otio.schema.Gap): + kind = "blank" + src.setMedia( + [ + "{},start={},end={},fps={}.movieproc".format( + kind, + range_to_read.start_time.value, + range_to_read.end_time_inclusive().value, + range_to_read.duration.rate + ) + ] + ) + + return src + + +if __name__ == "__main__": + main() diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/fcpx_xml.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/fcpx_xml.py new file mode 100644 index 0000000000..e219b58a1a --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/fcpx_xml.py @@ -0,0 +1,1182 @@ +# +# Copyright 2018 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO Final Cut Pro X XML Adapter. """ +import os +import subprocess +from xml.etree import cElementTree +from xml.dom import minidom +from fractions import Fraction +from datetime import date + +try: + from urllib import unquote +except ImportError: + from urllib.parse import unquote + +import opentimelineio as otio + +META_NAMESPACE = "fcpx_xml" + +COMPOSABLE_ELEMENTS = ("video", "audio", "ref-clip", "asset-clip") + +FRAMERATE_FRAMEDURATION = {23.98: "1001/24000s", + 24: "25/600s", + 25: "1/25s", + 29.97: "1001/30000s", + 30: "100/3000s", + 50: "1/50s", + 59.94: "1001/60000s", + 60: "1/60s"} + + +def format_name(frame_rate, path): + """ + Helper to get the formatName used in FCP X XML format elements. This + uses ffprobe to get the frame size of the the clip at the provided path. + + Args: + frame_rate (int): The frame rate of the clip at the provided path + path (str): The path to the clip to probe + + Returns: + str: The format name. If empty, then ffprobe couldn't find the item + """ + + path = path.replace("file://", "") + path = unquote(path) + if not os.path.exists(path): + return "" + + try: + frame_size = subprocess.check_output( + [ + "ffprobe", + "-v", + "error", + "-select_streams", + "v:0", + "-show_entries", + "stream=height,width", + "-of", + "csv=s=x:p=0", + path + ] + ) + except (subprocess.CalledProcessError, OSError): + frame_size = "" + + if not frame_size: + return "" + + frame_size = frame_size.rstrip() + + if "1920" in frame_size: + frame_size = "1080" + + if frame_size.endswith("1280"): + frame_size = "720" + + return "FFVideoFormat{}p{}".format(frame_size, frame_rate) + + +def to_rational_time(rational_number, fps): + """ + This converts a rational number value to an otio RationalTime object + + Args: + rational_number (str): This is a rational number from an FCP X XML + fps (int): The frame rate to use for calculating the rational time + + Returns: + RationalTime: A RationalTime object + """ + + if rational_number == "0s" or rational_number is None: + frames = 0 + else: + parts = rational_number.split("/") + if len(parts) > 1: + frames = int( + float(parts[0]) / float(parts[1].replace("s", "")) * float(fps) + ) + else: + frames = int(float(parts[0].replace("s", "")) * float(fps)) + + return otio.opentime.RationalTime(frames, int(fps)) + + +def from_rational_time(rational_time): + """ + This converts a RationalTime object to a rational number as a string + + Args: + rational_time (RationalTime): a rational time object + + Returns: + str: A rational number as a string + """ + + if int(rational_time.value) == 0: + return "0s" + result = Fraction( + float(rational_time.value) / float(rational_time.rate) + ).limit_denominator() + if str(result.denominator) == "1": + return "{}s".format(result.numerator) + return "{}/{}s".format(result.numerator, result.denominator) + + +class FcpxOtio(object): + """ + This object is responsible for knowing how to convert an otio into an + FCP X XML + """ + + def __init__(self, otio_timeline): + self.otio_timeline = otio_timeline + self.fcpx_xml = cElementTree.Element("fcpxml", version="1.8") + self.resource_element = cElementTree.SubElement( + self.fcpx_xml, + "resources" + ) + if self.otio_timeline.schema_name() == "Timeline": + self.timelines = [self.otio_timeline] + else: + self.timelines = list( + self.otio_timeline.each_child( + descended_from_type=otio.schema.Timeline + ) + ) + + if len(self.timelines) > 1: + self.event_resource = cElementTree.SubElement( + self.fcpx_xml, + "event", + {"name": self._event_name()} + ) + else: + self.event_resource = self.fcpx_xml + + self.resource_count = 0 + + def to_xml(self): + """ + Convert an otio to an FCP X XML + + Returns: + str: FCPX XML content + """ + + for project in self.timelines: + top_sequence = self._stack_to_sequence(project.tracks) + + project_element = cElementTree.Element( + "project", + { + "name": project.name, + "uid": project.metadata.get("fcpx", {}).get("uid", "") + } + ) + project_element.append(top_sequence) + self.event_resource.append(project_element) + + if not self.timelines: + for clip in self._clips(): + if not clip.parent(): + self._add_asset(clip) + + for stack in self._stacks(): + ref_element = self._element_for_item( + stack, + None, + ref_only=True, + compound=True + ) + self.event_resource.append(ref_element) + child_parent_map = {c: p for p in self.fcpx_xml.iter() for c in p} + + for marker in [marker for marker in self.fcpx_xml.iter("marker")]: + parent = child_parent_map.get(marker) + marker_attribs = marker.attrib.copy() + parent.remove(marker) + cElementTree.SubElement( + parent, + "marker", + marker_attribs + ) + + xml = cElementTree.tostring( + self.fcpx_xml, + encoding="UTF-8", + method="xml" + ) + dom = minidom.parseString(xml) + pretty = dom.toprettyxml(indent=" ") + return pretty.replace( + '', + '\n\n' + ) + + def _stack_to_sequence(self, stack, compound_clip=False): + format_element = self._find_or_create_format_from(stack) + sequence_element = cElementTree.Element( + "sequence", + { + "duration": self._calculate_rational_number( + stack.duration().value, + stack.duration().rate + ), + "format": str(format_element.get("id")) + } + ) + spine = cElementTree.SubElement(sequence_element, "spine") + video_tracks = [ + t for t in stack + if t.kind == otio.schema.TrackKind.Video + ] + audio_tracks = [ + t for t in stack + if t.kind == otio.schema.TrackKind.Audio + ] + + for idx, track in enumerate(video_tracks): + self._track_for_spine(track, idx, spine, compound_clip) + + for idx, track in enumerate(audio_tracks): + lane_id = -(idx + 1) + self._track_for_spine(track, lane_id, spine, compound_clip) + return sequence_element + + def _track_for_spine(self, track, lane_id, spine, compound): + for child in self._lanable_items(track.each_child()): + if self._item_in_compound_clip(child) and not compound: + continue + child_element = self._element_for_item( + child, + lane_id, + compound=compound + ) + if not lane_id: + spine.append(child_element) + continue + if child.schema_name() == "Gap": + continue + + parent_element = self._find_parent_element( + spine, + track.trimmed_range_of_child(child).start_time, + self._find_or_create_format_from(track).get("id") + ) + offset = self._offset_based_on_parent( + child_element, + parent_element, + self._find_or_create_format_from(track).get("id") + ) + child_element.set( + "offset", + from_rational_time(offset) + ) + + parent_element.append(child_element) + return [] + + def _find_parent_element(self, spine, trimmed_range, format_id): + for item in spine.iter(): + if item.tag not in ("clip", "asset-clip", "gap", "ref-clip"): + continue + if item.get("lane") is not None: + continue + if item.tag == "gap" and item.find("./audio") is not None: + continue + offset = to_rational_time( + item.get("offset"), + self._frame_rate_from_element(item, format_id) + ) + duration = to_rational_time( + item.get("duration"), + self._frame_rate_from_element(item, format_id) + ) + total_time = offset + duration + if offset > trimmed_range: + continue + if total_time > trimmed_range: + return item + return None + + def _offset_based_on_parent(self, child, parent, default_format_id): + parent_offset = to_rational_time( + parent.get("offset"), + self._frame_rate_from_element(parent, default_format_id) + ) + child_offset = to_rational_time( + child.get("offset"), + self._frame_rate_from_element(child, default_format_id) + ) + + parent_start = to_rational_time( + parent.get("start"), + self._frame_rate_from_element(parent, default_format_id) + ) + return (child_offset - parent_offset) + parent_start + + def _frame_rate_from_element(self, element, default_format_id): + if element.tag == "gap": + format_id = default_format_id + + if element.tag == "ref-clip": + media_element = self._media_by_id(element.get("ref")) + asset = media_element.find("./sequence") + format_id = asset.get("format") + + if element.tag == "clip": + if element.find("./gap") is not None: + asset_id = element.find("./gap").find("./audio").get("ref") + else: + asset_id = element.find("./video").get("ref") + asset = self._asset_by_id(asset_id) + format_id = asset.get("format") + + if element.tag == "asset-clip": + asset = self._asset_by_id(element.get("ref")) + format_id = asset.get("format") + + format_element = self.resource_element.find( + "./format[@id='{}']".format(format_id) + ) + total, rate = format_element.get("frameDuration").split("/") + rate = rate.replace("s", "") + return int(float(rate) / float(total)) + + def _element_for_item(self, item, lane, ref_only=False, compound=False): + element = None + duration = self._calculate_rational_number( + item.duration().value, + item.duration().rate + ) + if item.schema_name() == "Clip": + asset_id = self._add_asset(item, compound_only=compound) + element = self._element_for_clip(item, asset_id, duration, lane) + + if item.schema_name() == "Gap": + element = self._element_for_gap(item, duration) + + if item.schema_name() == "Stack": + element = self._element_for_stack(item, duration, ref_only) + + if element is None: + return None + if lane: + element.set("lane", str(lane)) + for marker in item.markers: + marker_attribs = { + "start": from_rational_time(marker.marked_range.start_time), + "duration": from_rational_time(marker.marked_range.duration), + "value": marker.name + } + marker_element = cElementTree.Element( + "marker", + marker_attribs + ) + if marker.color == otio.schema.MarkerColor.RED: + marker_element.set("completed", "0") + if marker.color == otio.schema.MarkerColor.GREEN: + marker_element.set("completed", "1") + element.append(marker_element) + return element + + def _lanable_items(self, items): + return [ + item for item in items + if item.schema_name() in ["Gap", "Stack", "Clip"] + ] + + def _element_for_clip(self, item, asset_id, duration, lane): + element = cElementTree.Element( + "clip", + { + "name": item.name, + "offset": from_rational_time( + item.trimmed_range_in_parent().start_time + ), + "duration": duration + } + ) + start = from_rational_time(item.source_range.start_time) + if start != "0s": + element.set("start", str(start)) + if item.parent().kind == otio.schema.TrackKind.Video: + cElementTree.SubElement( + element, + "video", + { + "offset": "0s", + "ref": asset_id, + "duration": self._find_asset_duration(item) + } + ) + else: + gap_element = cElementTree.SubElement( + element, + "gap", + { + "name": "Gap", + "offset": "0s", + "duration": self._find_asset_duration(item) + } + ) + audio = cElementTree.SubElement( + gap_element, + "audio", + { + "offset": "0s", + "ref": asset_id, + "duration": self._find_asset_duration(item) + } + ) + if lane: + audio.set("lane", str(lane)) + return element + + def _element_for_gap(self, item, duration): + element = cElementTree.Element( + "gap", + { + "name": "Gap", + "duration": duration, + "offset": from_rational_time( + item.trimmed_range_in_parent().start_time + ), + "start": "3600s" + } + ) + return element + + def _element_for_stack(self, item, duration, ref_only): + media_element = self._add_compound_clip(item) + asset_id = media_element.get("id") + element = cElementTree.Element( + "ref-clip", + { + "name": item.name, + "duration": duration, + "ref": str(asset_id) + } + ) + if not ref_only: + element.set( + "offset", + from_rational_time( + item.trimmed_range_in_parent().start_time + ) + ) + element.set( + "start", + from_rational_time(item.source_range.start_time) + ) + if item.parent() and item.parent().kind == otio.schema.TrackKind.Audio: + element.set("srcEnable", "audio") + return element + + def _find_asset_duration(self, item): + if (item.media_reference and + not item.media_reference.is_missing_reference): + return self._calculate_rational_number( + item.media_reference.available_range.duration.value, + item.media_reference.available_range.duration.rate + ) + return self._calculate_rational_number( + item.duration().value, + item.duration().rate + ) + + def _find_asset_start(self, item): + if (item.media_reference and + not item.media_reference.is_missing_reference): + return self._calculate_rational_number( + item.media_reference.available_range.start_time.value, + item.media_reference.available_range.start_time.rate + ) + return self._calculate_rational_number( + item.source_range.start_time.value, + item.source_range.start_time.rate + ) + + def _clip_format_name(self, clip): + if clip.schema_name() in ("Stack", "Track"): + return "" + if not clip.media_reference: + return "" + + if clip.media_reference.is_missing_reference: + return "" + + return format_name( + clip.duration().rate, + clip.media_reference.target_url + ) + + def _find_or_create_format_from(self, clip): + frame_duration = self._framerate_to_frame_duration( + clip.duration().rate + ) + format_element = self._format_by_frame_rate(clip.duration().rate) + if format_element is None: + format_element = cElementTree.SubElement( + self.resource_element, + "format", + { + "id": self._resource_id_generator(), + "frameDuration": frame_duration, + "name": self._clip_format_name(clip) + } + ) + if format_element.get("name", "") == "": + format_element.set("name", self._clip_format_name(clip)) + return format_element + + def _add_asset(self, clip, compound_only=False): + format_element = self._find_or_create_format_from(clip) + asset = self._create_asset_element(clip, format_element) + + if not compound_only and not self._asset_clip_by_name(clip.name): + self._create_asset_clip_element( + clip, + format_element, + asset.get("id") + ) + + if not clip.parent(): + asset.set("hasAudio", "1") + asset.set("hasVideo", "1") + return asset.get("id") + if clip.parent().kind == otio.schema.TrackKind.Audio: + asset.set("hasAudio", "1") + if clip.parent().kind == otio.schema.TrackKind.Video: + asset.set("hasVideo", "1") + return asset.get("id") + + def _create_asset_clip_element(self, clip, format_element, resource_id): + duration = self._find_asset_duration(clip) + a_clip = cElementTree.SubElement( + self.event_resource, + "asset-clip", + { + "name": clip.name, + "format": format_element.get("id"), + "ref": resource_id, + "duration": duration + } + ) + if clip.media_reference and not clip.media_reference.is_missing_reference: + fcpx_metadata = clip.media_reference.metadata.get("fcpx", {}) + note_element = self._create_note_element( + fcpx_metadata.get("note", None) + ) + keyword_elements = self._create_keyword_elements( + fcpx_metadata.get("keywords", []) + ) + metadata_element = self._create_metadata_elements( + fcpx_metadata.get("metadata", None) + ) + + if note_element is not None: + a_clip.append(note_element) + if keyword_elements: + for keyword_element in keyword_elements: + a_clip.append(keyword_element) + if metadata_element is not None: + a_clip.append(metadata_element) + + def _create_asset_element(self, clip, format_element): + target_url = self._target_url_from_clip(clip) + asset = self._asset_by_path(target_url) + if asset is not None: + return asset + + asset = cElementTree.SubElement( + self.resource_element, + "asset", + { + "name": clip.name, + "src": target_url, + "format": format_element.get("id"), + "id": self._resource_id_generator(), + "duration": self._find_asset_duration(clip), + "start": self._find_asset_start(clip), + "hasAudio": "0", + "hasVideo": "0" + } + ) + return asset + + def _add_compound_clip(self, item): + media_element = self._media_by_name(item.name) + if media_element is not None: + return media_element + resource_id = self._resource_id_generator() + media_element = cElementTree.SubElement( + self.resource_element, + "media", + { + "name": self._compound_clip_name(item, resource_id), + "id": resource_id + } + ) + if item.metadata.get("fcpx", {}).get("uid", False): + media_element.set("uid", item.metadata.get("fcpx", {}).get("uid")) + media_element.append(self._stack_to_sequence(item, compound_clip=True)) + return media_element + + def _stacks(self): + return self.otio_timeline.each_child( + descended_from_type=otio.schema.Stack + ) + + def _clips(self): + return self.otio_timeline.each_child( + descended_from_type=otio.schema.Clip + ) + + def _resource_id_generator(self): + self.resource_count += 1 + return "r{}".format(self.resource_count) + + def _event_name(self): + if self.otio_timeline.name: + return self.otio_timeline.name + return date.strftime(date.today(), "%m-%e-%y") + + def _asset_by_path(self, path): + return self.resource_element.find("./asset[@src='{}']".format(path)) + + def _asset_by_id(self, asset_id): + return self.resource_element.find("./asset[@id='{}']".format(asset_id)) + + def _media_by_name(self, name): + return self.resource_element.find("./media[@name='{}']".format(name)) + + def _media_by_id(self, media_id): + return self.resource_element.find("./media[@id='{}']".format(media_id)) + + def _format_by_frame_rate(self, frame_rate): + frame_duration = self._framerate_to_frame_duration(frame_rate) + return self.resource_element.find( + "./format[@frameDuration='{}']".format(frame_duration) + ) + + def _asset_clip_by_name(self, name): + return self.event_resource.find( + "./asset-clip[@name='{}']".format(name) + ) + + # -------------------- + # static methods + # -------------------- + + @staticmethod + def _framerate_to_frame_duration(framerate): + frame_duration = FRAMERATE_FRAMEDURATION.get(int(framerate), "") + if not frame_duration: + frame_duration = FRAMERATE_FRAMEDURATION.get(float(framerate), "") + return frame_duration + + @staticmethod + def _target_url_from_clip(clip): + if (clip.media_reference and + not clip.media_reference.is_missing_reference): + return clip.media_reference.target_url + return "file:///tmp/{}".format(clip.name) + + @staticmethod + def _calculate_rational_number(duration, rate): + if int(duration) == 0: + return "0s" + result = Fraction(float(duration) / float(rate)).limit_denominator() + return "{}/{}s".format(result.numerator, result.denominator) + + @staticmethod + def _compound_clip_name(compound_clip, resource_id): + if compound_clip.name: + return compound_clip.name + return "compound_clip_{}".format(resource_id) + + @staticmethod + def _item_in_compound_clip(item): + stack_count = 0 + parent = item.parent() + while parent is not None: + if parent.schema_name() == "Stack": + stack_count += 1 + parent = parent.parent() + return stack_count > 1 + + @staticmethod + def _create_metadata_elements(metadata): + if metadata is None: + return None + metadata_element = cElementTree.Element( + "metadata" + ) + for metadata_dict in metadata: + cElementTree.SubElement( + metadata_element, + "md", + { + "key": list(metadata_dict.keys())[0], + "value": list(metadata_dict.values())[0] + } + ) + return metadata_element + + @staticmethod + def _create_keyword_elements(keywords): + keyword_elements = [] + for keyword_dict in keywords: + keyword_elements.append( + cElementTree.Element( + "keyword", + keyword_dict + ) + ) + return keyword_elements + + @staticmethod + def _create_note_element(note): + if not note: + return None + note_element = cElementTree.Element( + "note" + ) + note_element.text = note + return note_element + + +class FcpxXml(object): + """ + This object is responsible for knowing how to convert an FCP X XML + otio into an otio timeline + """ + + def __init__(self, xml_string): + self.fcpx_xml = cElementTree.fromstring(xml_string) + self.child_parent_map = {c: p for p in self.fcpx_xml.iter() for c in p} + + def to_otio(self): + """ + Convert an FCP X XML to an otio + + Returns: + OpenTimeline: An OpenTimeline Timeline object + """ + + if self.fcpx_xml.find("./library") is not None: + return self._from_library() + if self.fcpx_xml.find("./event") is not None: + return self._from_event(self.fcpx_xml.find("./event")) + if self.fcpx_xml.find("./project") is not None: + return self._from_project(self.fcpx_xml.find("./project")) + if ((self.fcpx_xml.find("./asset-clip") is not None) or + (self.fcpx_xml.find("./ref-clip") is not None)): + return self._from_clips() + + def _from_library(self): + # We are just grabbing the first even in the project for now + return self._from_event(self.fcpx_xml.find("./library/event")) + + def _from_event(self, event_element): + container = otio.schema.SerializableCollection( + name=event_element.get("name") + ) + for project in event_element.findall("./project"): + container.append(self._from_project(project)) + return container + + def _from_project(self, project_element): + timeline = otio.schema.Timeline(name=project_element.get("name", "")) + timeline.tracks = self._squence_to_stack( + project_element.find("./sequence", {}) + ) + return timeline + + def _from_clips(self): + container = otio.schema.SerializableCollection() + if self.fcpx_xml.find("./asset-clip") is not None: + for asset_clip in self.fcpx_xml.findall("./asset-clip"): + container.append( + self._build_composable( + asset_clip, + asset_clip.get("format") + ) + ) + + if self.fcpx_xml.find("./ref-clip") is not None: + for ref_clip in self.fcpx_xml.findall("./ref-clip"): + container.append( + self._build_composable( + ref_clip, + "r1" + ) + ) + return container + + def _squence_to_stack(self, sequence_element, name="", source_range=None): + timeline_items = [] + lanes = [] + stack = otio.schema.Stack(name=name, source_range=source_range) + for element in sequence_element.iter(): + if element.tag not in COMPOSABLE_ELEMENTS: + continue + composable = self._build_composable( + element, + sequence_element.get("format") + ) + + offset, lane = self._offset_and_lane( + element, + sequence_element.get("format") + ) + + timeline_items.append( + { + "track": lane, + "offset": offset, + "composable": composable, + "audio_only": self._audio_only(element) + } + ) + + lanes.append(lane) + sorted_lanes = list(set(lanes)) + sorted_lanes.sort() + for lane in sorted_lanes: + sorted_items = self._sorted_items(lane, timeline_items) + track = otio.schema.Track( + name=lane, + kind=self._track_type(sorted_items) + ) + + for item in sorted_items: + frame_diff = ( + int(item["offset"].value) - track.duration().value + ) + if frame_diff > 0: + track.append( + self._create_gap( + 0, + frame_diff, + sequence_element.get("format") + ) + ) + track.append(item["composable"]) + stack.append(track) + return stack + + def _build_composable(self, element, default_format): + timing_clip = self._timing_clip(element) + source_range = self._time_range( + timing_clip, + self._format_id_for_clip(element, default_format) + ) + + if element.tag != "ref-clip": + otio_composable = otio.schema.Clip( + name=timing_clip.get("name"), + media_reference=self._reference_from_id( + element.get("ref"), + default_format + ), + source_range=source_range + ) + else: + media_element = self._compound_clip_by_id(element.get("ref")) + otio_composable = self._squence_to_stack( + media_element.find("./sequence"), + name=media_element.get("name"), + source_range=source_range + ) + + for marker in timing_clip.findall(".//marker"): + otio_composable.markers.append( + self._marker(marker, default_format) + ) + + return otio_composable + + def _marker(self, element, default_format): + if element.get("completed", None) and element.get("completed") == "1": + color = otio.schema.MarkerColor.GREEN + if element.get("completed", None) and element.get("completed") == "0": + color = otio.schema.MarkerColor.RED + if not element.get("completed", None): + color = otio.schema.MarkerColor.PURPLE + + otio_marker = otio.schema.Marker( + name=element.get("value", ""), + marked_range=self._time_range(element, default_format), + color=color + ) + return otio_marker + + def _audio_only(self, element): + if element.tag == "audio": + return True + if element.tag == "asset-clip": + asset = self._asset_by_id(element.get("ref", None)) + if asset and asset.get("hasVideo", "0") == "0": + return True + if element.tag == "ref-clip": + if element.get("srcEnable", "video") == "audio": + return True + return False + + def _create_gap(self, start_frame, number_of_frames, defualt_format): + fps = self._format_frame_rate(defualt_format) + source_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime(start_frame, fps), + duration=otio.opentime.RationalTime(number_of_frames, fps) + ) + return otio.schema.Gap(source_range=source_range) + + def _timing_clip(self, clip): + while clip.tag not in ("clip", "asset-clip", "ref-clip"): + clip = self.child_parent_map.get(clip) + return clip + + def _offset_and_lane(self, clip, default_format): + clip_format_id = self._format_id_for_clip(clip, default_format) + clip = self._timing_clip(clip) + parent = self.child_parent_map.get(clip) + + parent_format_id = self._format_id_for_clip(parent, default_format) + + if parent.tag == "spine" and parent.get("lane", None): + lane = parent.get("lane") + parent = self.child_parent_map.get(parent) + spine = True + else: + lane = clip.get("lane", "0") + spine = False + + clip_offset_frames = self._number_of_frames( + clip.get("offset"), + clip_format_id + ) + + if spine: + parent_start_frames = 0 + else: + parent_start_frames = self._number_of_frames( + parent.get("start", None), + parent_format_id + ) + + parent_offset_frames = self._number_of_frames( + parent.get("offset", None), + parent_format_id + ) + + clip_offset_frames = ( + (int(clip_offset_frames) - int(parent_start_frames)) + + int(parent_offset_frames) + ) + + offset = otio.opentime.RationalTime( + clip_offset_frames, + self._format_frame_rate(clip_format_id) + ) + + return offset, lane + + def _format_id_for_clip(self, clip, default_format): + if not clip.get("ref", None) or clip.tag == "gap": + return default_format + + resource = self._asset_by_id(clip.get("ref")) + + if resource is None: + resource = self._compound_clip_by_id( + clip.get("ref") + ).find("sequence") + + return resource.get("format", default_format) + + def _reference_from_id(self, asset_id, default_format): + asset = self._asset_by_id(asset_id) + if not asset.get("src", ""): + return otio.schema.MissingReference() + + available_range = otio.opentime.TimeRange( + start_time=to_rational_time( + asset.get("start"), + self._format_frame_rate( + asset.get("format", default_format) + ) + ), + duration=to_rational_time( + asset.get("duration"), + self._format_frame_rate( + asset.get("format", default_format) + ) + ) + ) + asset_clip = self._assetclip_by_ref(asset_id) + metadata = {} + if asset_clip: + metadata = self._create_metadta(asset_clip) + return otio.schema.ExternalReference( + target_url=asset.get("src"), + available_range=available_range, + metadata={"fcpx": metadata} + ) + + def _create_metadta(self, item): + metadata = {} + for element in item.iter(): + if element.tag == "md": + metadata.setdefault("metadata", []).append( + {element.attrib.get("key"): element.attrib.get("value")} + ) + # metadata.update( + # {element.attrib.get("key"): element.attrib.get("value")} + # ) + if element.tag == "note": + metadata.update({"note": element.text}) + if element.tag == "keyword": + metadata.setdefault("keywords", []).append(element.attrib) + return metadata + + # -------------------- + # time helpers + # -------------------- + def _format_frame_duration(self, format_id): + media_format = self._format_by_id(format_id) + total, rate = media_format.get("frameDuration").split("/") + rate = rate.replace("s", "") + return total, rate + + def _format_frame_rate(self, format_id): + fd_total, fd_rate = self._format_frame_duration(format_id) + return int(float(fd_rate) / float(fd_total)) + + def _number_of_frames(self, time_value, format_id): + if time_value == "0s" or time_value is None: + return 0 + fd_total, fd_rate = self._format_frame_duration(format_id) + time_value = time_value.split("/") + + if len(time_value) > 1: + time_value_a, time_value_b = time_value + return int( + (float(time_value_a) / float(time_value_b.replace("s", ""))) * + (float(fd_rate) / float(fd_total)) + ) + + return int( + int(time_value[0].replace("s", "")) * + (float(fd_rate) / float(fd_total)) + ) + + def _time_range(self, element, format_id): + return otio.opentime.TimeRange( + start_time=to_rational_time( + element.get("start", "0s"), + self._format_frame_rate(format_id) + ), + duration=to_rational_time( + element.get("duration"), + self._format_frame_rate(format_id) + ) + ) + # -------------------- + # search helpers + # -------------------- + + def _asset_by_id(self, asset_id): + return self.fcpx_xml.find( + "./resources/asset[@id='{}']".format(asset_id) + ) + + def _assetclip_by_ref(self, asset_id): + event = self.fcpx_xml.find("./event") + if event is None: + return self.fcpx_xml.find("./asset-clip[@ref='{}']".format(asset_id)) + else: + return event.find("./asset-clip[@ref='{}']".format(asset_id)) + + def _format_by_id(self, format_id): + return self.fcpx_xml.find( + "./resources/format[@id='{}']".format(format_id) + ) + + def _compound_clip_by_id(self, compound_id): + return self.fcpx_xml.find( + "./resources/media[@id='{}']".format(compound_id) + ) + + # -------------------- + # static methods + # -------------------- + @staticmethod + def _track_type(lane_items): + audio_only_items = [l for l in lane_items if l["audio_only"]] + if len(audio_only_items) == len(lane_items): + return otio.schema.TrackKind.Audio + return otio.schema.TrackKind.Video + + @staticmethod + def _sorted_items(lane, otio_objects): + lane_items = [item for item in otio_objects if item["track"] == lane] + return sorted(lane_items, key=lambda k: k["offset"]) + + +# -------------------- +# adapter requirements +# -------------------- +def read_from_string(input_str): + """ + Necessary read method for otio adapter + + Args: + input_str (str): An FCP X XML string + + Returns: + OpenTimeline: An OpenTimeline object + """ + + return FcpxXml(input_str).to_otio() + + +def write_to_string(input_otio): + """ + Necessary write method for otio adapter + + Args: + input_otio (OpenTimeline): An OpenTimeline object + + Returns: + str: The string contents of an FCP X XML + """ + + return FcpxOtio(input_otio).to_xml() diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ffmpeg_burnins.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ffmpeg_burnins.py new file mode 100644 index 0000000000..28f0b97f55 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/ffmpeg_burnins.py @@ -0,0 +1,424 @@ +# MIT License +# +# Copyright (c) 2017 Ed Caspersen +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# allcopies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +""" +This module provides an interface to allow users to easily +build out an FFMPEG command with all the correct filters +for applying text (with a background) to the rendered media. +""" +import os +import sys +import json +from subprocess import Popen, PIPE +from PIL import ImageFont + + +def _is_windows(): + """ + queries if the current operating system is Windows + + :rtype: bool + """ + return sys.platform.startswith('win') or \ + sys.platform.startswith('cygwin') + + +def _system_font(): + """ + attempts to determine a default system font + + :rtype: str + """ + if _is_windows(): + font_path = os.path.join(os.environ['WINDIR'], 'Fonts') + fonts = ('arial.ttf', 'calibri.ttf', 'times.ttf') + elif sys.platform.startswith('darwin'): + font_path = '/System/Library/Fonts' + fonts = ('Menlo.ttc',) + else: + # assuming linux + font_path = 'usr/share/fonts/msttcorefonts' + fonts = ('arial.ttf', 'times.ttf', 'couri.ttf') + + system_font = None + backup = None + for font in fonts: + font = os.path.join(font_path, font) + if os.path.exists(font): + system_font = font + break + else: + if os.path.exists(font_path): + for each in os.listdir(font_path): + ext = os.path.splitext(each)[-1] + if ext[1:].startswith('tt'): + system_font = os.path.join(font_path, each) + return system_font or backup + + +# Default valuues +FONT = _system_font() +FONT_SIZE = 16 +FONT_COLOR = 'white' +BG_COLOR = 'black' +BG_PADDING = 5 + +# FFMPEG command strings +FFMPEG = ('ffmpeg -loglevel panic -i %(input)s ' + '%(filters)s %(args)s%(output)s') +FFPROBE = ('ffprobe -v quiet -print_format json -show_format ' + '-show_streams %(source)s') +BOX = 'box=1:boxborderw=%(border)d:boxcolor=%(color)s@%(opacity).1f' +DRAWTEXT = ("drawtext=text='%(text)s':x=%(x)s:y=%(y)s:fontcolor=" + "%(color)s@%(opacity).1f:fontsize=%(size)d:fontfile='%(font)s'") +TIMECODE = ("drawtext=timecode='%(text)s':timecode_rate=%(fps).2f" + ":x=%(x)s:y=%(y)s:fontcolor=" + "%(color)s@%(opacity).1f:fontsize=%(size)d:fontfile='%(font)s'") + + +# Valid aligment parameters. +TOP_CENTERED = 'top_centered' +BOTTOM_CENTERED = 'bottom_centered' +TOP_LEFT = 'top_left' +BOTTOM_LEFT = 'bottom_left' +TOP_RIGHT = 'top_right' +BOTTOM_RIGHT = 'bottom_right' + + +class Options(dict): + """ + Base options class. + """ + _params = { + 'opacity': 1, + 'x_offset': 0, + 'y_offset': 0, + 'font': FONT, + 'font_size': FONT_SIZE, + 'bg_color': BG_COLOR, + 'bg_padding': BG_PADDING, + 'font_color': FONT_COLOR + } + + def __init__(self, **kwargs): + super(Options, self).__init__() + params = self._params.copy() + params.update(kwargs) + super(Options, self).update(**params) + + def __setitem__(self, key, value): + if key not in self._params: + raise KeyError("Not a valid option key '%s'" % key) + super(Options, self).update({key: value}) + + +class FrameNumberOptions(Options): + """ + :key int frame_offset: offset the frame numbers + :key float opacity: opacity value (0-1) + :key str expression: expression that would be used instead of text + :key bool x_offset: X position offset + (does not apply to centered alignments) + :key bool y_offset: Y position offset + :key str font: path to the font file + :key int font_size: size to render the font in + :key str bg_color: background color of the box + :key int bg_padding: padding between the font and box + :key str font_color: color to render + """ + + def __init__(self, **kwargs): + self._params.update({ + 'frame_offset': 0, + 'expression': None + }) + super(FrameNumberOptions, self).__init__(**kwargs) + + +class TextOptions(Options): + """ + :key float opacity: opacity value (0-1) + :key str expression: expression that would be used instead of text + :key bool x_offset: X position offset + (does not apply to centered alignments) + :key bool y_offset: Y position offset + :key str font: path to the font file + :key int font_size: size to render the font in + :key str bg_color: background color of the box + :key int bg_padding: padding between the font and box + :key str font_color: color to render + """ + + +class TimeCodeOptions(Options): + """ + :key int frame_offset: offset the frame numbers + :key float fps: frame rate to calculate the timecode by + :key float opacity: opacity value (0-1) + :key bool x_offset: X position offset + (does not apply to centered alignments) + :key bool y_offset: Y position offset + :key str font: path to the font file + :key int font_size: size to render the font in + :key str bg_color: background color of the box + :key int bg_padding: padding between the font and box + :key str font_color: color to render + """ + + def __init__(self, **kwargs): + self._params.update({ + 'frame_offset': 0, + 'fps': 24 + }) + super(TimeCodeOptions, self).__init__(**kwargs) + + +class Burnins(object): + """ + Class that provides convenience API for building filter + flags for the FFMPEG command. + """ + + def __init__(self, source, streams=None): + """ + :param str source: source media file + :param [] streams: ffprobe stream data if parsed as a pre-process + """ + self.source = source + self.filters = { + 'drawtext': [] + } + self._streams = streams or _streams(self.source) + + def __repr__(self): + return '' % os.path.basename(self.source) + + @property + def start_frame(self): + """ + :rtype: int + """ + start_time = float(self._video_stream['start_time']) + return round(start_time * self.frame_rate) + + @property + def end_frame(self): + """ + :rtype: int + """ + end_time = float(self._video_stream['duration']) + return round(end_time * self.frame_rate) + + @property + def frame_rate(self): + """ + :rtype: int + """ + data = self._video_stream + tokens = data['r_frame_rate'].split('/') + return int(tokens[0]) / int(tokens[1]) + + @property + def _video_stream(self): + video_stream = None + for each in self._streams: + if each.get('codec_type') == 'video': + video_stream = each + break + else: + raise RuntimeError("Failed to locate video stream " + "from '%s'" % self.source) + return video_stream + + @property + def resolution(self): + """ + :rtype: (int, int) + """ + data = self._video_stream + return data['width'], data['height'] + + @property + def filter_string(self): + """ + Generates the filter string that would be applied + to the `-vf` argument + + :rtype: str + """ + return ','.join(self.filters['drawtext']) + + def add_timecode(self, align, options=None): + """ + Convenience method to create the frame number expression. + + :param enum align: alignment, must use provided enum flags + :param dict options: recommended to use TimeCodeOptions + """ + options = options or TimeCodeOptions() + timecode = _frames_to_timecode(options['frame_offset'], + self.frame_rate) + options = options.copy() + if not options.get('fps'): + options['fps'] = self.frame_rate + self._add_burnin(timecode.replace(':', r'\:'), + align, + options, + TIMECODE) + + def add_frame_numbers(self, align, options=None): + """ + Convenience method to create the frame number expression. + + :param enum align: alignment, must use provided enum flags + :param dict options: recommended to use FrameNumberOptions + """ + options = options or FrameNumberOptions() + options['expression'] = r'%%{eif\:n+%d\:d}' % options['frame_offset'] + text = str(int(self.end_frame + options['frame_offset'])) + self._add_burnin(text, align, options, DRAWTEXT) + + def add_text(self, text, align, options=None): + """ + Adding static text to a filter. + + :param str text: text to apply to the drawtext + :param enum align: alignment, must use provided enum flags + :param dict options: recommended to use TextOptions + """ + options = options or TextOptions() + self._add_burnin(text, align, options, DRAWTEXT) + + def _add_burnin(self, text, align, options, draw): + """ + Generic method for building the filter flags. + + :param str text: text to apply to the drawtext + :param enum align: alignment, must use provided enum flags + :param dict options: + """ + resolution = self.resolution + data = { + 'text': options.get('expression') or text, + 'color': options['font_color'], + 'size': options['font_size'] + } + data.update(options) + data.update(_drawtext(align, resolution, text, options)) + if 'font' in data and _is_windows(): + data['font'] = data['font'].replace(os.sep, r'\\' + os.sep) + data['font'] = data['font'].replace(':', r'\:') + self.filters['drawtext'].append(draw % data) + + if options.get('bg_color') is not None: + box = BOX % { + 'border': options['bg_padding'], + 'color': options['bg_color'], + 'opacity': options['opacity'] + } + self.filters['drawtext'][-1] += ':%s' % box + + def command(self, output=None, args=None, overwrite=False): + """ + Generate the entire FFMPEG command. + + :param str output: output file + :param str args: additional FFMPEG arguments + :param bool overwrite: overwrite the output if it exists + :returns: completed command + :rtype: str + """ + output = output or '' + if overwrite: + output = '-y %s' % output + return (FFMPEG % { + 'input': self.source, + 'output': output, + 'args': '%s ' % args if args else '', + 'filters': '-vf "%s"' % self.filter_string + }).strip() + + def render(self, output, args=None, overwrite=False): + """ + Render the media to a specified destination. + + :param str output: output file + :param str args: additional FFMPEG arguments + :param bool overwrite: overwrite the output if it exists + """ + if not overwrite and os.path.exists(output): + raise RuntimeError("Destination '%s' exists, please " + "use overwrite" % output) + command = self.command(output=output, + args=args, + overwrite=overwrite) + proc = Popen(command, shell=True) + proc.communicate() + if proc.returncode != 0: + raise RuntimeError("Failed to render '%s': %s'" + % (output, command)) + if not os.path.exists(output): + raise RuntimeError("Failed to generate '%s'" % output) + + +def _streams(source): + """ + :param str source: source media file + :rtype: [{}, ...] + """ + command = FFPROBE % {'source': source} + proc = Popen(command, shell=True, stdout=PIPE) + out = proc.communicate()[0] + if proc.returncode != 0: + raise RuntimeError("Failed to run: %s" % command) + return json.loads(out)['streams'] + + +def _drawtext(align, resolution, text, options): + """ + :rtype: {'x': int, 'y': int} + """ + x_pos = '0' + if align in (TOP_CENTERED, BOTTOM_CENTERED): + x_pos = 'w/2-tw/2' + elif align in (TOP_RIGHT, BOTTOM_RIGHT): + ifont = ImageFont.truetype(options['font'], + options['font_size']) + box_size = ifont.getsize(text) + x_pos = resolution[0] - (box_size[0] + options['x_offset']) + elif align in (TOP_LEFT, BOTTOM_LEFT): + x_pos = options['x_offset'] + + if align in (TOP_CENTERED, + TOP_RIGHT, + TOP_LEFT): + y_pos = '%d' % options['y_offset'] + else: + y_pos = 'h-text_h-%d' % (options['y_offset']) + return {'x': x_pos, 'y': y_pos} + + +def _frames_to_timecode(frames, framerate): + return '{0:02d}:{1:02d}:{2:02d}:{3:02d}'.format( + int(frames / (3600 * framerate)), + int(frames / (60 * framerate) % 60), + int(frames / framerate % 60), + int(frames % framerate)) diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/hls_playlist.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/hls_playlist.py new file mode 100644 index 0000000000..e0e3f8f872 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/hls_playlist.py @@ -0,0 +1,1781 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""HLS Playlist OpenTimelineIO adapter + +This adapter supports authoring of HLS playlists within OpenTimelineIO by using +clips to represent media fragments. + +Status: + - Export of Media Playlists well supported + - Export of Master Playlists supported + - Import of Media Playlists well supported + - Import of Master Playlists unsupported + - Explicit Variant Stream controls in Master Playlists unsupported + +In general, you can author otio as follows: + t = otio.schema.Timeline() + track = otio.schema.Track("v1") + track.metadata['HLS'] = { + "EXT-X-INDEPENDENT-SEGMENTS": None, + "EXT-X-PLAYLIST-TYPE": "VOD" + } + t.tracks.append(track) + + # Make a prototype media ref with the fragment's initialization metadata + fragmented_media_ref = otio.schema.ExternalReference( + target_url='video1.mp4', + metadata={ + "streaming": { + "init_byterange": { + "byte_count": 729, + "byte_offset": 0 + }, + "init_uri": "media-video-1.mp4" + } + } + ) + + # Make a copy of the media ref specifying the byte range for the fragment + media_ref1 = fragmented_media_ref.deepcopy() + media_ref1.available_range=otio.opentime.TimeRange( + otio.opentime.RationalTime(0, 1), + otio.opentime.RationalTime(2.002, 1) + ) + media_ref1.metadata['streaming'].update( + { + "byte_count": 534220, + "byte_offset": 1361 + } + ) + + # make the fragment and append it + fragment1 = otio.schema.Clip(media_reference=media_ref1) + track.append(fragment1) + + # (repeat to define each fragment) + +The code above would yield an HLS playlist like: + #EXTM3U + #EXT-X-VERSION:7 + #EXT-X-TARGETDURATION:2 + #EXT-X-PLAYLIST-TYPE:VOD + #EXT-X-INDEPENDENT-SEGMENTS + #EXT-X-MEDIA-SEQUENCE:1 + #EXT-X-MAP:BYTERANGE="729@0",URI="media-video-1.mp4" + #EXTINF:2.00200, + #EXT-X-BYTERANGE:534220@1361 + video1.mp4 + #EXT-X-ENDLIST + +If you add min_segment_duration and max_segment_duration to the timeline's +metadata dictionary as RationalTime objects, you can control the rule set +deciding how many fragments to accumulate into a single segment. When nothing +is specified for these metadata keys, the adapter will create one segment per +fragment. + +In general, any metadata added to the track metadata dict under the HLS +namespace will be included at the top level of the exported playlist (see +``EXT-X-INDEPENDENT-SEGMENTS`` and ``EXT-X-PLAYLIST-TYPE`` in the example +above). Each segment will pass through any metadata in the HLS namespace from +the media_reference. + +If you write a Timeline with more than one track specified, then the adapter +will create an HLS master playlist. + +The following track metadata keys will be used to inform exported master +playlist metadata per variant stream: + bandwidth + codec + language + mimeType + group_id (audio) + autoselect (audio) + default (audio) +These values are translated to EXT-X-STREAM-INF and EXT-X-MEDIA +attributes as defined in sections 4.3.4.2 and 4.3.4.1 of +draft-pantos-http-live-streaming, respectively. +""" + +import re +import copy + +import opentimelineio as otio + +# TODO: determine output version based on features used +OUTPUT_PLAYLIST_VERSION = "7" + +# TODO: make sure all strings get sanitized through encoding and decoding +PLAYLIST_STRING_ENCODING = "utf-8" + +# Enable isinstance(my_instance, basestring) tests in Python 3 +# This can be phased out when Python 2 support is dropped. Replace tests with: +# isinstance(my_instance, str) + +try: + basestring +except NameError: + basestring = str + +""" +Matches a single key/value pair from an HLS Attribute List. +See section 4.2 of draft-pantos-http-live-streaming for more detail. +""" +ATTRIBUTE_RE = re.compile( + r'(?P[A-Z0-9-]+)' + r'\=' + + r'(?P(?:\"[^\r\n"]*\")|[^,]+)' + r',?' +) + +""" +Matches AttributeValue of the above regex into appropriate data types. +Note that these are meant to be joined using regex "or" in this order. +""" +_ATTRIBUTE_RE_VALUE_STR_LIST = [ + r'(?P(?P[0-9]+)x(?P[0-9]+))\Z', + r'(?P0[xX](?P[0-9A-F]+))\Z', + r'(?P-?[0-9]+\.[0-9]+)\Z', + r'(?P[0-9]+)\Z', + r'(?P\"(?P[^\r\n"]*)\")\Z', + r'(?P[^",\s]+)\Z' +] +ATTRIBUTE_VALUE_RE = re.compile("|".join(_ATTRIBUTE_RE_VALUE_STR_LIST)) + +""" +Matches a byterange as used in various contexts. +See section 4.3.2.2 of draft-pantos-http-live-streaming for an example use of +this byterange form. +""" +BYTERANGE_RE = re.compile(r'(?P\d+)(?:@(?P\d+))?') + +""" +Matches HLS Playlist tags or comments, respective. +See section 4.1 of draft-pantos-http-live-streaming for more detail. +""" +TAG_RE = re.compile( + r'#(?PEXT[^:\s]+)(?P:?)(?P.*)' +) +COMMENT_RE = re.compile(r'#(?!EXT)(?P.*)') + + +class AttributeListEnum(str): + """ A subclass allowing us to differentiate enums in HLS attribute lists + """ + + +def _value_from_raw_attribute_value(raw_attribute_value): + """ + Takes in a raw AttributeValue and returns an appopritate Python type. + If there is a problem decoding the value, None is returned. + """ + value_match = ATTRIBUTE_VALUE_RE.match(raw_attribute_value) + if not value_match: + return None + + group_dict = value_match.groupdict() + # suss out the match + for k, v in group_dict.items(): + # not a successful group match + if v is None: + continue + + # decode the string + if k == 'resolution': + return v + elif k == 'enumerated': + return AttributeListEnum(v) + elif k == 'hexcidecimal': + return int(group_dict['hex_value'], base=16) + elif k == 'floating_point': + return float(v) + elif k == 'decimal': + return int(v) + elif k == 'string': + # grab only the data within the quotes, excluding the quotes + string_value = group_dict['string_value'] + return string_value + + return None + + +class AttributeList(dict): + """ + Dictionary-like object representing an HLS AttributeList. + See section 4.2 of draft-pantos-http-live-streaming for more detail. + """ + + def __init__(self, other=None): + """ + contstructs an :class:`AttributeList`. + + ``Other`` can be either another dictionary-like object or a list of + key/value pairs + """ + if not other: + return + + try: + items = other.items() + except AttributeError: + items = other + + for k, v in items: + self[k] = v + + def __str__(self): + """ + Construct attribute list string as it would exist in an HLS playlist. + """ + attr_list_entries = [] + # Use a sorted version of the dictionary to ensure consistency + for k, v in sorted(self.items(), key=lambda i: i[0]): + out_value = '' + if isinstance(v, AttributeListEnum): + out_value = v + elif isinstance(v, basestring): + out_value = '"{}"'.format(v) + else: + out_value = str(v) + + attr_list_entries.append('{}={}'.format(k, out_value)) + + return ','.join(attr_list_entries) + + @classmethod + def from_string(cls, attrlist_string): + """ + Accepts an attribute list string and returns an :class:`AttributeList`. + + The values will be transformed to Python types. + """ + attr_list = cls() + match = ATTRIBUTE_RE.search(attrlist_string) + while match: + # unpack the values from the match + group_dict = match.groupdict() + name = group_dict['AttributeName'] + raw_value = group_dict['AttributeValue'] + + # parse the raw value + value = _value_from_raw_attribute_value(raw_value) + attr_list[name] = value + + # search for the next attribute in the string + match_end = match.span()[1] + match = ATTRIBUTE_RE.search(attrlist_string, match_end) + + return attr_list + + +# some special top-levle keys that HLS metadata will be decoded into +FORMAT_METADATA_KEY = 'HLS' +""" +Some concepts are translatable between HLS and other streaming formats (DASH). +These metadata keys are used on OTIO objects outside the HLS namespace because +they are higher level concepts. +""" +STREAMING_METADATA_KEY = 'streaming' +INIT_BYTERANGE_KEY = 'init_byterange' +INIT_URI_KEY = 'init_uri' +SEQUENCE_NUM_KEY = 'sequence_num' +BYTE_OFFSET_KEY = 'byte_offset' +BYTE_COUNT_KEY = 'byte_count' + + +class Byterange(object): + """Offers interpretation of HLS byte ranges in various forms.""" + + count = None + """(:class:`int`) Number of bytes included in the range.""" + + offset = None + """(:class:`int`) Byte offset at which the range starts.""" + + def __init__(self, count=None, offset=None): + """Constructs a :class:`Byterange` object. + + :param count: (:class:`int`) Number of bytes included in the range. + :param offset: (:class:`int`) Byte offset at which the range starts. + """ + self.count = (count if count is not None else 0) + self.offset = offset + + def __eq__(self, other): + if not isinstance(other, Byterange): + # fall back on identity, this should always be False + return (self is other) + return (self.count == other.count and self.offset == other.offset) + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return '{}(offset = {}, count = {})'.format( + type(self), + str(self.offset), + str(self.count) + ) + + def __str__(self): + """returns a string in HLS format""" + + out_str = str(self.count) + if self.offset is not None: + out_str += '@{}'.format(str(self.offset)) + + return out_str + + def to_dict(self): + """Returns a dict suitable for storing in otio metadata. + + :return: (:class:`dict`) serializable version of byterange. + """ + range_dict = {BYTE_COUNT_KEY: self.count} + if self.offset is not None: + range_dict[BYTE_OFFSET_KEY] = self.offset + + return range_dict + + @classmethod + def from_string(cls, byterange_string): + """Construct a :class:`Byterange` given a string in HLS format. + + :param byterange_string: (:class:`str`) a byterange string. + :return: (:class:`Byterange`) The instance for the provided string. + """ + m = BYTERANGE_RE.match(byterange_string) + + return cls.from_match_dict(m.groupdict()) + + @classmethod + def from_match_dict(cls, match_dict): + """ + Construct a :class:`Byterange` given a groupdict from ``BYTERANGE_RE`` + + :param match_dict: (:class:`dict`) the ``match_dict``. + :return: (:class:`Byterange`) The instance for the provided string. + """ + byterange = cls(count=int(match_dict['n'])) + + try: + byterange.offset = int(match_dict['o']) + except KeyError: + pass + + return byterange + + @classmethod + def from_dict(cls, info_dict): + """ Creates a :class:`Byterange` given a dictionary containing keys + like generated from the :meth:`to_dict method`. + + :param info_dict: (:class:`dict`) Dictionary byterange. + :return: (:class:`Byterange`) an equivalent instance. + """ + byterange = cls( + count=info_dict.get(BYTE_COUNT_KEY), + offset=info_dict.get(BYTE_OFFSET_KEY) + ) + + return byterange + + +""" +For a given collection of media, HLS has two playlist types: + - Media Playlist + - Master Playlist + +The media playlist refers directly to the individual segments that make up an +audio or video track of a given program. The master playlist refers to a +collection of media playlists and provides ways to use them together +(rendition groups). + +See section 2 of draft-pantos-http-live-streaming for more detail. + +The constants below define which tags belong to which schema. +""" + +""" +Basic tags appear in both media and master playlists. +See section 4.3.1 of draft-pantos-http-live-streaming for more detail. +""" +BASIC_TAGS = set([ + "EXTM3U", + "EXT-X-VERSION" +]) + +""" +Media segment tags apply to either the following media or all subsequent +segments. They MUST NOT appear in master playlists. +See section 4.3.2 of draft-pantos-http-live-streaming for more detail. +""" +MEDIA_SEGMENT_TAGS = set([ + 'EXTINF', + 'EXT-X-BYTERANGE', + 'EXT-X-DISCONTINUITY', + 'EXT-X-KEY', + 'EXT-X-MAP', + 'EXT-X-PROGRAM-DATE-TIME', + 'EXT-X-DATERANGE' +]) + +""" The subset of above tags that apply to every segment following them """ +MEDIA_SEGMENT_SUBSEQUENT_TAGS = set([ + 'EXT-X-KEY', + 'EXT-X-MAP', +]) + +""" +Media Playlist tags must only occur once per playlist, and must not appear in +Master Playlists. +See section 4.3.3 of draft-pantos-http-live-streaming for more detail. +""" +MEDIA_PLAYLIST_TAGS = set([ + 'EXT-X-TARGETDURATION', + 'EXT-X-MEDIA-SEQUENCE', + 'EXT-X-DISCONTINUITY-SEQUENCE', + 'EXT-X-ENDLIST', + 'EXT-X-PLAYLIST-TYPE', + 'EXT-X-I-FRAMES-ONLY' +]) + +""" +Master playlist tags declare global parameters for the presentation. +They must not appear in media playlists. +See section 4.3.4 of draft-pantos-http-live-streaming for more detail. +""" +MASTER_PLAYLIST_TAGS = set([ + 'EXT-X-MEDIA', + 'EXT-X-STREAM-INF', + 'EXT-X-I-FRAME-STREAM-INF', + 'EXT-X-SESSION-DATA', + 'EXT-X-SESSION-KEY', +]) + +""" +Media or Master Playlist tags can appear in either media or master playlists. +See section 4.3.5 of draft-pantos-http-live-streaming for more detail. +These tags SHOULD appear in either the media or master playlist. If they occur +in both, their values MUST agree. +These values MUST NOT appear more than once in a playlist. +""" +MEDIA_OR_MASTER_TAGS = set([ + "EXT-X-INDEPENDENT-SEGMENTS", + "EXT-X-START" +]) + +""" +Some special tags used by the parser. +""" +PLAYLIST_START_TAG = "EXTM3U" +PLAYLIST_END_TAG = "EXT-X-ENDLIST" +PLAYLIST_VERSION_TAG = "EXT-X-VERSION" +PLAYLIST_SEGMENT_INF_TAG = "EXTINF" + +""" +attribute list entries to omit from EXT-I-FRAME-STREAM-INF tags +See section 4.3.4.3 of draft-pantos-http-live-streaming for more detail. +""" +I_FRAME_OMIT_ATTRS = set([ + 'FRAME-RATE', + 'AUDIO', + 'SUBTITLES', + 'CLOSED-CAPTIONS' +]) + +""" enum for kinds of playlist entries """ +EntryType = type('EntryType', (), { + 'tag': 'tag', + 'comment': 'comment', + 'URI': 'URI' +}) + +""" enum for types of playlists """ +PlaylistType = type('PlaylistType', (), { + 'media': 'media', + 'master': 'master' +}) + +""" mapping from HLS track type to otio ``TrackKind`` """ +HLS_TRACK_TYPE_TO_OTIO_KIND = { + AttributeListEnum('AUDIO'): otio.schema.TrackKind.Audio, + AttributeListEnum('VIDEO'): otio.schema.TrackKind.Video, + # TODO: determine how to handle SUBTITLES and CLOSED-CAPTIONS +} + +""" mapping from otio ``TrackKind`` to HLS track type """ +OTIO_TRACK_KIND_TO_HLS_TYPE = dict(( + (v, k) for k, v in HLS_TRACK_TYPE_TO_OTIO_KIND.items() +)) + + +class HLSPlaylistEntry(object): + """An entry in an HLS playlist. + + Entries can be a tag, a comment, or a URI. All HLS playlists are parsed + into lists of :class:`HLSPlaylistEntry` instances that can then be + interpreted against the HLS schema. + """ + + # TODO: rename this to entry_type to fix builtin masking + # type = None + """ (``EntryType``) the type of entry """ + + comment_string = None + """ + (:class:`str`) value of comment (if the ``entry_type`` is + ``EntryType.comment``). + """ + + tag_name = None + """ + (:class:`str`) Name of tag (if the ``entry_type`` is ``EntryType.tag``). + """ + + tag_value = None + """ + (:class:`str`) Value of tag (if the ``entry_type`` is ``EntryType.tag``). + """ + + uri = None + """ + (:class:`str`) Value of the URI (if the ``entry_type is ``EntryType.uri``). + """ + + def __init__(self, type): + """ + Constructs an :class:`HLSPlaylistEntry`. + + :param type: (``EntryType``) Type of entry. + """ + self.type = type + + def __repr__(self): + base_str = 'otio.adapter.HLSPlaylistEntry(type={}'.format( + self.type) + if self.type == EntryType.tag: + base_str += ', tag_name={}, tag_value={}'.format( + repr(self.tag_name), + repr(self.tag_value) + ) + elif self.type == EntryType.comment: + base_str += ', comment={}'.format(repr(self.comment_string)) + elif self.type == EntryType.URI: + base_str += ', URI={}'.format(repr(self.uri)) + + return base_str + ')' + + def __str__(self): + """ + Returns a string as it would appear in an HLS playlist. + + :return: (:class:`str`) HLS playlist entry string. + """ + if self.type == EntryType.comment and self.comment_string: + return "# {}".format(self.comment_string) + elif self.type == EntryType.comment: + # empty comments are blank lines + return "" + elif self.type == EntryType.URI: + return self.uri + elif self.type == EntryType.tag: + out_tag_name = self.tag_name + if self.tag_value is not None: + return '#{}:{}'.format(out_tag_name, self.tag_value) + else: + return '#{}'.format(out_tag_name) + + @classmethod + def tag_entry(cls, name, value=None): + """ + Creates an ``EntryType.tag`` :class:`HLSPlaylistEntry`. + + :param name: (:class:`str`) tag name. + :param value: (:class:`str`) tag value. + :return: (:class:`HLSPlaylistEntry`) Entry instance. + """ + entry = cls(EntryType.tag) + entry.tag_name = name + entry.tag_value = value + + return entry + + @classmethod + def comment_entry(cls, comment): + """Creates an ``EntryType.comment`` :class:`HLSPlaylistEntry`. + + :param comment: (:class:`str`) the comment. + :return: (:class:`HLSPlaylistEntry`) Entry instance. + """ + entry = cls(EntryType.comment) + entry.comment_string = comment + + return entry + + @classmethod + def uri_entry(cls, uri): + """Creates an ``EntryType.uri`` :class:`HLSPlaylistEntry`. + + :param uri: (:class:`str`) A URI string. + :return: (:class:`HLSPlaylistEntry`) Entry instance. + """ + entry = cls(EntryType.URI) + entry.uri = uri + + return entry + + @classmethod + def from_string(cls, entry_string): + """Creates an `:class:`HLSPlaylistEntry` given a string as it appears + in an HLS playlist. + + :param entry_string: (:class:`str`) String from an HLS playlist. + :return: (:class:`HLSPlaylistEntry`) Entry instance. + """ + # Empty lines are skipped + if not entry_string.strip(): + return None + + # Attempt to parse as a tag + m = TAG_RE.match(entry_string) + if m: + group_dict = m.groupdict() + tag_value = ( + group_dict['tagvalue'] + if group_dict['hasvalue'] else None + ) + entry = cls.tag_entry(group_dict['tagname'], tag_value) + return entry + + # Attempt to parse as a comment + m = COMMENT_RE.match(entry_string) + if m: + entry = cls.comment_entry(m.groupdict()['comment']) + return entry + + # If it's not the others, treat as a URI + entry = cls.uri_entry(entry_string) + + return entry + + """A dispatch dictionary for grabbing the right Regex to parse tags.""" + TAG_VALUE_RE_MAP = { + "EXTINF": re.compile(r'(?P\d+(\.\d*)?),(?P.*$)'), + "EXT-X-BYTERANGE": BYTERANGE_RE, + "EXT-X-KEY": re.compile(r'(?P<attribute_list>.*$)'), + "EXT-X-MAP": re.compile(r'(?P<attribute_list>.*$)'), + "EXT-X-MEDIA-SEQUENCE": re.compile(r'(?P<number>\d+)'), + "EXT-X-PLAYLIST-TYPE": re.compile(r'(?P<type>EVENT|VOD)'), + PLAYLIST_VERSION_TAG: re.compile(r'(?P<n>\d+)') + } + + def parsed_tag_value(self, playlist_version=None): + """Parses and returns ``self.tag_value`` based on the HLS schema. + + The value will be a dictionary where the keys are the names used in the + draft Pantos HTTP Live Streaming doc. When "attribute-list" is + specified, an entry "attribute_list" will be present containing + an :class:`AttributeList` instance. + + :param playlist_version: (:class:`int`) version number of the playlist. + If none is provided, a best guess will be made. + :return: The parsed value. + """ + if self.type != EntryType.tag: + return None + + try: + tag_re = self.TAG_VALUE_RE_MAP[self.tag_name] + except KeyError: + return None + + # parse the tag + m = tag_re.match(self.tag_value) + group_dict = m.groupdict() + + if not m: + return None + + # If the tag value has an attribute list, parse it and add it + try: + attribute_list = group_dict['attribute_list'] + attr_list = AttributeList.from_string(attribute_list) + group_dict['attributes'] = attr_list + except KeyError: + pass + + return group_dict + + +class HLSPlaylistParser(object): + """Bootstraps HLS parsing and hands the playlist string off to the + appropriate parser for the type + """ + + def __init__(self, edl_string): + self.timeline = otio.schema.Timeline() + self.playlist_type = None + + self._parse_playlist(edl_string) + + def _parse_playlist(self, edl_string): + """Parses the HLS Playlist string line-by-line.""" + # parse lines until we encounter one that identifies the playlist type + # then hand off + start_encountered = False + end_encountered = False + playlist_entries = [] + playlist_version = 1 + for line in edl_string.splitlines(): + # attempt to parse the entry + entry = HLSPlaylistEntry.from_string(line) + if entry is None: + continue + + entry_is_tag = (entry.type == EntryType.tag) + + # identify if the playlist start/end is encountered + if (entry_is_tag and not (start_encountered and end_encountered)): + if entry.tag_name == PLAYLIST_START_TAG: + start_encountered = True + elif entry.tag_name == PLAYLIST_END_TAG: + end_encountered = True + + # if the playlist starting tag hasn't been encountered, ignore + if not start_encountered: + continue + + # Store the parsed entry + playlist_entries.append(entry) + + # Determine if this tells us the playlist type + if not self.playlist_type and entry_is_tag: + if entry.tag_name in MASTER_PLAYLIST_TAGS: + self.playlist_type = PlaylistType.master + elif entry.tag_name in MEDIA_PLAYLIST_TAGS: + self.playlist_type = PlaylistType.media + + if end_encountered: + break + + # try to grab the version from the playlist + if entry_is_tag and entry.tag_name == PLAYLIST_VERSION_TAG: + playlist_version = int(entry.parsed_tag_value()['n']) + + # dispatch to the appropriate schema interpreter + if self.playlist_type is None: + self.timeline = None + raise otio.exceptions.ReadingNotSupportedError( + "could not determine playlist type" + ) + elif self.playlist_type == PlaylistType.master: + self.timeline = None + raise otio.exceptions.AdapterDoesntSupportFunction( + "HLS master playlists are not yet supported" + ) + elif self.playlist_type == PlaylistType.media: + parser = MediaPlaylistParser(playlist_entries, playlist_version) + if len(parser.track): + self.timeline.tracks.append(parser.track) + + +class MediaPlaylistParser(object): + """Parses an HLS Media playlist returning a SEQUENCE""" + + def __init__(self, playlist_entries, playlist_version=None): + self.track = otio.schema.Track( + metadata={FORMAT_METADATA_KEY: {}} + ) + + self._parse_entries(playlist_entries, playlist_version) + + def _handle_track_metadata(self, entry, playlist_version, clip): + """Stashes the tag value in the track metadata""" + value = entry.tag_value + self.track.metadata[FORMAT_METADATA_KEY][entry.tag_name] = value + + def _handle_discarded_metadata(self, entry, playlist_version, clip): + """Handler for tags that are discarded. This is done when a tag's + information is represented by the native OTIO concepts. + + For instance, the EXT-X-TARGETDURATION tag simply gives a rounded + value for the maximum segment size in the playlist. This can easily + be found in OTIO by examining the clips. + """ + # Do nothing + + def _metadata_dict_for_MAP(self, entry, playlist_version): + entry_data = entry.parsed_tag_value() + attributes = entry_data['attributes'] + map_dict = {} + for attr, value in attributes.items(): + if attr == 'BYTERANGE': + byterange = Byterange.from_string(value) + map_dict[INIT_BYTERANGE_KEY] = byterange.to_dict() + elif attr == 'URI': + map_dict[INIT_URI_KEY] = value + + return map_dict + + def _handle_INF(self, entry, playlist_version, clip): + # This specifies segment duration and optional title + info_dict = entry.parsed_tag_value(playlist_version) + segment_duration = float(info_dict['duration']) + segment_title = info_dict['title'] + available_range = otio.opentime.TimeRange( + otio.opentime.RationalTime(0, 1), + otio.opentime.RationalTime(segment_duration, 1) + ) + + # Push the info to the clip + clip.media_reference.available_range = available_range + clip.source_range = available_range + clip.name = segment_title + + def _handle_BYTERANGE(self, entry, playlist_version, clip): + reference_metadata = clip.media_reference.metadata + ref_streaming_metadata = reference_metadata.setdefault( + STREAMING_METADATA_KEY, + {} + ) + + # Pull out the byte count and offset + byterange = Byterange.from_match_dict( + entry.parsed_tag_value(playlist_version) + ) + ref_streaming_metadata.update(byterange.to_dict()) + + """ + Specifies handlers for specific HLS tags. + """ + TAG_HANDLERS = { + "EXTINF": _handle_INF, + PLAYLIST_VERSION_TAG: _handle_track_metadata, + "EXT-X-TARGETDURATION": _handle_discarded_metadata, + "EXT-X-MEDIA-SEQUENCE": _handle_discarded_metadata, + "EXT-X-PLAYLIST-TYPE": _handle_track_metadata, + "EXT-X-INDEPENDENT-SEGMENTS": _handle_track_metadata, + "EXT-X-BYTERANGE": _handle_BYTERANGE + } + + def _parse_entries(self, playlist_entries, playlist_version): + """Interpret the entries through the lens of the schema""" + current_clip = otio.schema.Clip( + media_reference=otio.schema.ExternalReference( + metadata={ + FORMAT_METADATA_KEY: {}, + STREAMING_METADATA_KEY: {} + } + ) + ) + current_media_ref = current_clip.media_reference + segment_metadata = {} + current_map_data = {} + # per section 4.3.3.2 of Pantos HLS, 0 is default start track + current_track = 0 + for entry in playlist_entries: + if entry.type == EntryType.URI: + # the URI ends the segment definition + current_media_ref.target_url = entry.uri + current_media_ref.metadata[FORMAT_METADATA_KEY].update( + segment_metadata + ) + current_media_ref.metadata[STREAMING_METADATA_KEY].update( + current_map_data + ) + current_clip.metadata.setdefault( + STREAMING_METADATA_KEY, + {} + )[SEQUENCE_NUM_KEY] = current_track + self.track.append(current_clip) + current_track += 1 + + # Set up the next segment definition + current_clip = otio.schema.Clip( + media_reference=otio.schema.ExternalReference( + metadata={ + FORMAT_METADATA_KEY: {}, + STREAMING_METADATA_KEY: {} + } + ) + ) + current_media_ref = current_clip.media_reference + continue + elif entry.type != EntryType.tag: + # the rest of the code deals only with tags + continue + + # Explode the EXT-X-MAP info out + if entry.tag_name == "EXT-X-MAP": + map_data = self._metadata_dict_for_MAP(entry, playlist_version) + current_map_data.update(map_data) + continue + + # Grab the track when it comes around + if entry.tag_name == "EXT-X-MEDIA-SEQUENCE": + entry_data = entry.parsed_tag_value() + current_track = int(entry_data['number']) + + # If the segment tag is one that applies to all that follow + # store the value to be applied to each segment + if entry.tag_name in MEDIA_SEGMENT_SUBSEQUENT_TAGS: + segment_metadata[entry.tag_name] = entry.tag_value + continue + + # use a handler if available + try: + handler = self.TAG_HANDLERS[entry.tag_name] + handler(self, entry, playlist_version, current_clip) + continue + except KeyError: + pass + + # add the tag to the reference metadata at the correct level + if entry.tag_name in [PLAYLIST_START_TAG, PLAYLIST_END_TAG]: + continue + elif entry.tag_name in MEDIA_SEGMENT_TAGS: + # Media segments translate into media refs + hls_metadata = current_media_ref.metadata[FORMAT_METADATA_KEY] + hls_metadata[entry.tag_name] = entry.tag_value + elif entry.tag_name in MEDIA_PLAYLIST_TAGS: + # Media playlists translate into tracks + hls_metadata = self.track.metadata[FORMAT_METADATA_KEY] + hls_metadata[entry.tag_name] = entry.tag_value + + +""" +Compatibility version list: + EXT-X-BYTERANGE >= 4 + EXT-X-I-FRAMES-ONLY >= 4 + EXT-X-MAP in media playlist with EXT-X-I-FRAMES-ONLY >= 5 + EXT-X-MAP in media playlist without I-FRAMES-ONLY >= 6 + EXT-X-KEY constrants are by attributes specified: + - IV >= 2 + - KEYFORMAT >= 5 + - KEYFORMATVERSIONS >= 5 + EXTINF with floating point vaules >= 3 + + master playlist: + EXT-X-MEDIA with INSTREAM-ID="SERVICE" +""" + + +def entries_for_segment( + uri, + segment_duration, + segment_name=None, + segment_byterange=None, + segment_tags=None +): + """Creates a set of :class:`HLSPlaylistEntries` with the given parameters. + + :param uri: (:class:`str`) The uri for the segment media. + :param segment_duration: (:class:`opentimelineio.opentime.RationalTime`) + playback duration of the segment. + :param segment_byterange: (:class:`ByteRange`) The data range for the + segment in the media (if required) + :param segment_tags: (:class:`dict`) key/value pairs of to become + additional tags for the segment + + :return: (:class:`list`) a group of :class:`HLSPlaylistEntry` instances for + the segment + """ + # Create the tags dict to build + if segment_tags: + tags = copy.deepcopy(segment_tags) + else: + tags = {} + + # Start building the entries list + segment_entries = [] + + # add the EXTINF + name = segment_name if segment_name is not None else '' + tag_value = '{0:.5f},{1}'.format( + otio.opentime.to_seconds(segment_duration), + name + ) + extinf_entry = HLSPlaylistEntry.tag_entry('EXTINF', tag_value) + segment_entries.append(extinf_entry) + + # add the additional tags + tag_entries = [ + HLSPlaylistEntry.tag_entry(k, v) for k, v in + tags.items() + ] + segment_entries.extend(tag_entries) + + # Now add the byterange for the entry + if segment_byterange: + byterange_entry = HLSPlaylistEntry.tag_entry( + 'EXT-X-BYTERANGE', + str(segment_byterange) + ) + segment_entries.append(byterange_entry) + + # Add the URI + # this method expects all fragments come from the same source file + uri_entry = HLSPlaylistEntry.uri_entry(uri) + segment_entries.append(uri_entry) + + return segment_entries + + +def stream_inf_attr_list_for_track(track): + """ Builds an :class:`AttributeList` instance for use in ``STREAM-INF`` + tags for the provided track. + + :param track: (:class:`otio.schema.Track`) A track representing a + variant stream + :return: (:class:`AttributeList`) The instance from the metadata + """ + streaming_metadata = track.metadata.get(STREAMING_METADATA_KEY, {}) + + attributes = [] + bandwidth = streaming_metadata.get('bandwidth') + if bandwidth is not None: + attributes.append(('BANDWIDTH', bandwidth)) + + codec = streaming_metadata.get('codec') + if codec is not None: + attributes.append(('CODECS', codec)) + + frame_rate = streaming_metadata.get('frame_rate') + if frame_rate is not None: + attributes.append(('FRAME-RATE', frame_rate)) + + if 'width' in streaming_metadata and 'height' in streaming_metadata: + resolution = "{}x{}".format( + streaming_metadata['width'], + streaming_metadata['height'] + ) + attributes.append(('RESOLUTION', AttributeListEnum(resolution))) + + al = AttributeList(attributes) + + return al + + +def master_playlist_to_string(master_timeline): + """Writes a master playlist describing the tracks""" + + # start with a version number of 1, as features are encountered, we will + # update the version accordingly + version_requirements = set([1]) + + # TODO: detect rather than forcing version 6 + version_requirements.add(6) + + header_tags = copy.copy( + master_timeline.metadata.get(FORMAT_METADATA_KEY, {}) + ) + + # Filter out any values from the HLS metadata that aren't meant to become + # tags, such as the directive to force an HLS master playlist + hls_md_blacklist = ['master_playlist'] + for key in hls_md_blacklist: + try: + del(header_tags[key]) + except KeyError: + pass + + playlist_entries = [] + + # First declare the non-visual media + hls_type_count = {} + video_tracks = [] + audio_tracks = [ + t for t in master_timeline.tracks if + t.kind == otio.schema.TrackKind.Audio + ] + for track in master_timeline.tracks: + if track.kind == otio.schema.TrackKind.Video: + # video is done later, skip + video_tracks.append(track) + continue + + # Determine the HLS type + hls_type = OTIO_TRACK_KIND_TO_HLS_TYPE[track.kind] + + streaming_metadata = track.metadata.get(STREAMING_METADATA_KEY, {}) + + # Find the group name + try: + group_id = streaming_metadata['group_id'] + except KeyError: + sub_id = hls_type_count.setdefault(hls_type, 1) + group_id = '{}{}'.format(hls_type, sub_id) + hls_type_count[hls_type] += 1 + + media_playlist_default_uri = "{}.m3u8".format(track.name) + try: + track_uri = track.metadata[FORMAT_METADATA_KEY].get( + 'uri', + media_playlist_default_uri + ) + except KeyError: + track_uri = media_playlist_default_uri + + # Build the attribute list + attributes = AttributeList( + [ + ('TYPE', hls_type), + ('GROUP-ID', group_id), + ('URI', track_uri), + ('NAME', track.name), + ] + ) + + if streaming_metadata.get('autoselect'): + attributes['AUTOSELECT'] = AttributeListEnum('YES') + + if streaming_metadata.get('default'): + attributes['DEFAULT'] = AttributeListEnum('YES') + + # Finally, create the tag + entry = HLSPlaylistEntry.tag_entry( + 'EXT-X-MEDIA', + str(attributes) + ) + + playlist_entries.append(entry) + + # Add a blank line in the playlist to separate sections + if playlist_entries: + playlist_entries.append(HLSPlaylistEntry.comment_entry('')) + + # First write any i-frame playlist entires + iframe_list_entries = [] + for track in video_tracks: + try: + iframe_uri = track.metadata[FORMAT_METADATA_KEY]['iframe_uri'] + except KeyError: + # don't include iframe playlist + continue + + # Create the attribute list + attribute_list = stream_inf_attr_list_for_track(track) + + # Remove entries to not be included for I-Frame streams + for attr in I_FRAME_OMIT_ATTRS: + try: + del(attribute_list[attr]) + except KeyError: + pass + + # Add the URI + attribute_list['URI'] = iframe_uri + + iframe_list_entries.append( + HLSPlaylistEntry.tag_entry( + 'EXT-X-I-FRAME-STREAM-INF', + str(attribute_list) + ) + ) + + if iframe_list_entries: + iframe_list_entries.append(HLSPlaylistEntry.comment_entry('')) + + playlist_entries.extend(iframe_list_entries) + + # Write an EXT-STREAM-INF for each rendition set + for track in video_tracks: + # create the base attribute list for the video track + al = stream_inf_attr_list_for_track(track) + + # Create the uri + media_playlist_default_uri = "{}.m3u8".format(track.name) + try: + track_uri = track.metadata[FORMAT_METADATA_KEY].get( + 'uri', media_playlist_default_uri + ) + except KeyError: + track_uri = media_playlist_default_uri + uri_entry = HLSPlaylistEntry.uri_entry(track_uri) + + # TODO: this will break when we have subtitle and CC tracks + added_entry = False + for audio_track in audio_tracks: + if track.name not in audio_track.metadata['linked_tracks']: + continue + + # Write an entry for using these together + try: + audio_track_streaming_metadata = audio_track.metadata[ + STREAMING_METADATA_KEY + ] + aud_group = audio_track_streaming_metadata['group_id'] + aud_codec = audio_track_streaming_metadata['codec'] + aud_bandwidth = audio_track_streaming_metadata['bandwidth'] + except KeyError: + raise TypeError( + "HLS audio tracks must have 'codec', 'group_id', and" + " 'bandwidth' specified in metadata" + ) + + combo_al = copy.copy(al) + combo_al['CODECS'] += ',{}'.format(aud_codec) + combo_al['AUDIO'] = aud_group + combo_al['BANDWIDTH'] += aud_bandwidth + + entry = HLSPlaylistEntry.tag_entry( + 'EXT-X-STREAM-INF', + str(combo_al) + ) + playlist_entries.append(entry) + playlist_entries.append(uri_entry) + + added_entry = True + + if not added_entry: + # write out one simple entry + entry = HLSPlaylistEntry.tag_entry( + 'EXT-X-STREAM-INF', + str(al) + ) + playlist_entries.append(entry) + playlist_entries.append(uri_entry) + + # add a break before the next grouping of entries + playlist_entries.append(HLSPlaylistEntry.comment_entry('')) + + out_entries = [HLSPlaylistEntry.tag_entry(PLAYLIST_START_TAG, None)] + + playlist_version = max(version_requirements) + playlist_version_entry = HLSPlaylistEntry.tag_entry( + PLAYLIST_VERSION_TAG, + str(playlist_version) + ) + + out_entries.append(playlist_version_entry) + + out_entries += ( + HLSPlaylistEntry.tag_entry(k, v) for k, v in header_tags.items() + ) + + # separate the header entries from the rest of the entries + out_entries.append(HLSPlaylistEntry.comment_entry('')) + + out_entries += playlist_entries + + playlist_string = '\n'.join( + (str(entry) for entry in out_entries) + ) + + return playlist_string + + +class MediaPlaylistWriter(): + + def __init__( + self, + media_track, + min_seg_duration=None, + max_seg_duration=None + ): + # Default to one segment per fragment + if min_seg_duration is None: + min_seg_duration = otio.opentime.RationalTime(0, 1) + if max_seg_duration is None: + max_seg_duration = otio.opentime.RationalTime(0, 1) + + self._min_seg_duration = min_seg_duration + self._max_seg_duration = max_seg_duration + + self._playlist_entries = [] + self._playlist_tags = {} + + # Whenever an entry is added that has a minimum version requirement, + # we add that version to this set. The max value from this set is the + # playlist's version requirement + self._versions_used = set([1]) + + # TODO: detect rather than forcing version 7 + self._versions_used.add(7) + + # Start the build + self._build_playlist_with_track(media_track) + + def _build_playlist_with_track(self, media_track): + """ + Executes methods to result in a fully populated _playlist_entries list + """ + self._copy_HLS_metadata(media_track) + self._setup_track_info(media_track) + self._add_segment_entries(media_track) + self._finalize_entries(media_track) + + def _copy_HLS_metadata(self, media_track): + """ + Copies any metadata in the "HLS" namespace from the track to the + playlist-global tags + """ + # Grab any metadata provided on the otio + try: + track_metadata = media_track.metadata[FORMAT_METADATA_KEY] + self._playlist_tags.update(track_metadata) + + # Remove the version tag from the track metadata, we'll compute + # based on what we write out + del(self._playlist_tags[PLAYLIST_VERSION_TAG]) + + except KeyError: + pass + + # additionally remove metadata keys added for providing master + # playlist URIs + for key in ('uri', 'iframe_uri'): + try: + del(self._playlist_tags[key]) + except KeyError: + pass + + def _setup_track_info(self, media_track): + """sets up playlist global metadata""" + + # Setup the track start + if 'EXT-X-I-FRAMES-ONLY' in media_track.metadata.get( + FORMAT_METADATA_KEY, + {} + ): + # I-Frame playlists start at zero no matter what + track_start = 0 + else: + # Pull the track num from the first clip, if provided + first_segment_streaming_metadata = media_track[0].metadata.get( + STREAMING_METADATA_KEY, + {} + ) + track_start = first_segment_streaming_metadata.get( + SEQUENCE_NUM_KEY + ) + + # If we found a track start or one isn't already set in the + # metadata, create the tag for it. + if ( + track_start is not None or + 'EXT-X-MEDIA-SEQUENCE' not in self._playlist_tags + ): + # Choose a reasonable track start default + if track_start is None: + track_start = 1 + self._playlist_tags['EXT-X-MEDIA-SEQUENCE'] = str(track_start) + + def _add_map_entry(self, fragment): + """adds an EXT-X-MAP entry from the given fragment + + returns the added entry + """ + + media_ref = fragment.media_reference + + # Extract useful tag data + media_ref_streaming_metadata = media_ref.metadata[ + STREAMING_METADATA_KEY + ] + uri = media_ref_streaming_metadata[INIT_URI_KEY] + seg_map_byterange_dict = media_ref_streaming_metadata.get( + INIT_BYTERANGE_KEY + ) + + # Create the attrlist + map_attr_list = AttributeList([ + ('URI', uri), + ]) + + # Add the byterange if provided + if seg_map_byterange_dict is not None: + seg_map_byterange = Byterange.from_dict(seg_map_byterange_dict) + map_attr_list['BYTERANGE'] = str(seg_map_byterange) + + # Construct the entry with the attrlist as the value + map_tag_str = str(map_attr_list) + entry = HLSPlaylistEntry.tag_entry("EXT-X-MAP", map_tag_str) + + self._playlist_entries.append(entry) + + return entry + + def _add_entries_for_segment_from_fragments( + self, + fragments, + omit_hls_keys=None, + is_iframe_playlist=False + ): + """ + For the given list of otio clips representing fragments in the mp4, + add playlist entries for single HLS segment. + + :param fragments: (:clas:`list`) :class:`opentimelineio.schema.Clip` + objects to write as a contiguous segment. + :param omit_hls_keys: (:class:`list`) metadata keys from the original + "HLS" metadata namespeaces will not be passed through. + :param is_iframe_playlist: (:class:`bool`) If true, writes one segment + per fragment, otherwise writes all fragments as a single segment + + :return: (:class:`list` the :class:`HLSPlaylistEntry` instances added + to the playlist + """ + if is_iframe_playlist: + entries = [] + for fragment in fragments: + name = '' + fragment_range = Byterange.from_dict( + fragment.media_reference.metadata[STREAMING_METADATA_KEY] + ) + + segment_tags = {} + frag_tags = fragment.media_reference.metadata.get( + FORMAT_METADATA_KEY, + {} + ) + segment_tags.update(copy.deepcopy(frag_tags)) + + # scrub any metadata marked for omission + omit_hls_keys = omit_hls_keys or [] + for key in omit_hls_keys: + try: + del(segment_tags[key]) + except KeyError: + pass + + segment_entries = entries_for_segment( + fragment.media_reference.target_url, + fragment.duration(), + name, + fragment_range, + segment_tags + ) + entries.extend(segment_entries) + + self._playlist_entries.extend(entries) + return entries + + segment_tags = {} + for fragment in fragments: + frag_tags = fragment.media_reference.metadata.get( + FORMAT_METADATA_KEY, + {} + ) + segment_tags.update(copy.deepcopy(frag_tags)) + + # scrub any metadata marked for omission + omit_hls_keys = omit_hls_keys or [] + for key in omit_hls_keys: + try: + del(segment_tags[key]) + except KeyError: + pass + + # Calculate the byterange for the segment (if byteranges are specified) + first_ref = fragments[0].media_reference + first_ref_streaming_md = first_ref.metadata[STREAMING_METADATA_KEY] + if 'byte_offset' in first_ref_streaming_md and len(fragments) == 1: + segment_range = Byterange.from_dict(first_ref_streaming_md) + elif 'byte_offset' in first_ref_streaming_md: + # Find the byterange encapsulating everything + last_ref = fragments[-1].media_reference + last_ref_streaming_md = last_ref.metadata[STREAMING_METADATA_KEY] + first_range = Byterange.from_dict(first_ref_streaming_md) + last_range = Byterange.from_dict(last_ref_streaming_md) + + segment_offset = first_range.offset + segment_end = (last_range.offset + last_range.count) + segment_count = segment_end - segment_offset + segment_range = Byterange(segment_count, segment_offset) + else: + segment_range = None + + uri = fragments[0].media_reference.target_url + + # calculate the combined duration + segment_duration = fragments[0].duration() + for frag in fragments[1:]: + segment_duration += frag.duration() + + # TODO: Determine how to pass a segment name in + segment_name = '' + segment_entries = entries_for_segment( + uri, + segment_duration, + segment_name, + segment_range, + segment_tags + ) + + self._playlist_entries.extend(segment_entries) + return segment_entries + + def _fragments_have_same_map(self, fragment, following_fragment): + """ + Given fragment and following_fragment, returns whether or not their + initialization data is the same (what becomes EXT-X-MAP) + """ + media_ref = fragment.media_reference + media_ref_streaming_md = media_ref.metadata.get( + STREAMING_METADATA_KEY, + {} + ) + following_ref = following_fragment.media_reference + following_ref_streaming_md = following_ref.metadata.get( + STREAMING_METADATA_KEY, + {} + ) + # Check the init file + init_uri = media_ref_streaming_md.get(INIT_URI_KEY) + following_init_uri = media_ref_streaming_md.get(INIT_URI_KEY) + if init_uri != following_init_uri: + return False + + # Check the init byterange + init_dict = media_ref_streaming_md.get(INIT_BYTERANGE_KEY) + following_init_dict = following_ref_streaming_md.get( + INIT_BYTERANGE_KEY + ) + + dummy_range = Byterange(0, 0) + init_range = ( + Byterange.from_dict(init_dict) if init_dict else dummy_range + ) + following_range = ( + Byterange.from_dict(following_init_dict) + if following_init_dict else dummy_range + ) + + if init_range != following_range: + return False + + return True + + def _fragments_are_contiguous(self, fragment, following_fragment): + """ Given fragment and following_fragment (otio clips) returns whether + or not they are contiguous. + + To be contiguous the fragments must: + 1. have the same file URL + 2. have the same initialization data (what becomes EXT-X-MAP) + 3. be adjacent in the file (follwoing_fragment's first byte directly + follows fragment's last byte) + + Returns True if following_fragment is contiguous from fragment + """ + # Fragments are contiguous if: + # 1. They have the file url + # 2. They have the same map info + # 3. Their byte ranges are contiguous + media_ref = fragment.media_reference + media_ref_streaming_md = media_ref.metadata.get( + STREAMING_METADATA_KEY, + {} + ) + following_ref = following_fragment.media_reference + following_ref_streaming_md = following_ref.metadata.get( + STREAMING_METADATA_KEY, + {} + ) + if media_ref.target_url != following_ref.target_url: + return False + + if ( + media_ref_streaming_md.get(INIT_URI_KEY) != + following_ref_streaming_md.get(INIT_URI_KEY) + ): + return False + + if not self._fragments_have_same_map(fragment, following_fragment): + return False + + # Check if fragments are contiguous in file + try: + frag_end = ( + media_ref_streaming_md['byte_offset'] + + media_ref_streaming_md['byte_count'] + ) + if frag_end != following_ref_streaming_md['byte_offset']: + return False + except KeyError: + return False + + # since we haven't returned yet, all checks must have passed! + return True + + def _add_segment_entries(self, media_track): + """given a media track, generates the segment entries""" + + # Determine whether or not this is an I-Frame playlist + track_hls_metadata = media_track.metadata.get('HLS') + is_iframe_playlist = 'EXT-X-I-FRAMES-ONLY' in track_hls_metadata + + # Make a list copy of the fragments + fragments = [clip for clip in media_track] + + segment_durations = [] + previous_fragment = None + map_changed = True + while fragments: + # There should be at least one fragment per segment + frag_it = iter(fragments) + first_frag = next(frag_it) + gathered_fragments = [first_frag] + gathered_duration = first_frag.duration() + + # Determine this segment will need a new EXT-X-MAP entry + map_changed = ( + True if previous_fragment is None else + not self._fragments_have_same_map( + previous_fragment, + first_frag + ) + ) + + # Iterate through the remaining fragments until a discontinuity + # is found, our time limit is met, or we add all the fragments to + # the segment + for fragment in frag_it: + # Determine whther or not the fragments are contiguous + previous_fragment = gathered_fragments[-1] + contiguous = self._fragments_are_contiguous( + previous_fragment, + fragment + ) + + # Determine if we've hit our segment time conditions + new_duration = gathered_duration + fragment.duration() + segment_full = ( + gathered_duration >= self._min_seg_duration or + new_duration > self._max_seg_duration + ) + + # End condition met, cut the segment + if not contiguous or segment_full: + break + + # Include the fragment + gathered_duration = new_duration + gathered_fragments.append(fragment) + + # Write out the segment and start the next + start_fragment = gathered_fragments[0] + + # If the map for this segment was a change, write it + if map_changed: + self._add_map_entry(start_fragment) + + # add the entries for the segment. Omit any EXT-X-MAP metadata + # that may have come in from reading a file (we're updating) + self._add_entries_for_segment_from_fragments( + gathered_fragments, + omit_hls_keys=('EXT-X-MAP'), + is_iframe_playlist=is_iframe_playlist + ) + + duration_seconds = otio.opentime.to_seconds(gathered_duration) + segment_durations.append(duration_seconds) + + # in the next iteration, start where we left off + fragments = fragments[len(gathered_fragments):] + + # Set the max segment duration + max_duration = round(max(segment_durations)) + self._playlist_tags['EXT-X-TARGETDURATION'] = str(int(max_duration)) + + def _finalize_entries(self, media_track): + """Does final wrap-up of playlist entries""" + + self._playlist_tags['EXT-X-PLAYLIST-TYPE'] = 'VOD' + + # add the end + end_entry = HLSPlaylistEntry.tag_entry(PLAYLIST_END_TAG) + self._playlist_entries.append(end_entry) + + # find the maximum HLS feature version we've used + playlist_version = max(self._versions_used) + playlist_version_entry = HLSPlaylistEntry.tag_entry( + PLAYLIST_VERSION_TAG, + str(playlist_version) + ) + + # now that we know what was used, let's prepend the header + playlist_header_entries = [ + HLSPlaylistEntry.tag_entry(PLAYLIST_START_TAG), + playlist_version_entry + ] + + # add in the rest of the header entries in a deterministic order + playlist_header_entries += ( + HLSPlaylistEntry.tag_entry(k, v) + for k, v in sorted(self._playlist_tags.items(), key=lambda i: i[0]) + ) + + # Prepend the entries with the header entries + self._playlist_entries = ( + playlist_header_entries + self._playlist_entries + ) + + def playlist_string(self): + """Returns the string representation of the playlist entries""" + + return '\n'.join( + (str(entry) for entry in self._playlist_entries) + ) + +# Public interface + + +def read_from_string(input_str): + """Adapter entry point for reading.""" + + parser = HLSPlaylistParser(input_str) + return parser.timeline + + +def write_to_string(input_otio): + """Adapter entry point for writing.""" + + if len(input_otio.tracks) == 0: + return None + + # Determine whether we should write a media or master playlist + try: + write_master = input_otio.metadata['HLS']['master_playlist'] + except KeyError: + # If no explicit directive, infer + write_master = (len(input_otio.tracks) > 1) + + if write_master: + return master_playlist_to_string(input_otio) + else: + media_track = input_otio.tracks[0] + track_streaming_md = input_otio.metadata.get( + STREAMING_METADATA_KEY, + {} + ) + min_seg_duration = track_streaming_md.get('min_segment_duration') + max_seg_duration = track_streaming_md.get('max_segment_duration') + + writer = MediaPlaylistWriter( + media_track, + min_seg_duration, + max_seg_duration + ) + return writer.playlist_string() diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/maya_sequencer.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/maya_sequencer.py new file mode 100644 index 0000000000..03e6cf8763 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/maya_sequencer.py @@ -0,0 +1,132 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""Maya Sequencer Adapter Harness""" + +import os +import subprocess + +from .. import adapters + + +def write_to_file(input_otio, filepath): + if "OTIO_MAYA_PYTHON_BIN" not in os.environ: + raise RuntimeError( + "'OTIO_MAYA_PYTHON_BIN' not set, please set this to path to " + "mayapy within the Maya installation." + ) + maya_python_path = os.environ["OTIO_MAYA_PYTHON_BIN"] + if not os.path.exists(maya_python_path): + raise RuntimeError( + 'Cannot access file at OTIO_MAYA_PYTHON_BIN: "{}"'.format( + maya_python_path + ) + ) + if os.path.isdir(maya_python_path): + raise RuntimeError( + "OTIO_MAYA_PYTHON_BIN contains a path to a directory, not to an " + "executable file: {}".format(maya_python_path) + ) + + input_data = adapters.write_to_string(input_otio, "otio_json") + + os.environ['PYTHONPATH'] = ( + os.pathsep.join( + [ + os.environ.setdefault('PYTHONPATH', ''), + os.path.dirname(__file__) + ] + ) + ) + + proc = subprocess.Popen( + [ + os.environ["OTIO_MAYA_PYTHON_BIN"], + '-m', + 'extern_maya_sequencer', + 'write', + filepath + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + env=os.environ + ) + proc.stdin.write(input_data) + out, err = proc.communicate() + + if proc.returncode: + raise RuntimeError( + "ERROR: extern_maya_sequencer (called through the maya sequencer " + "file adapter) failed. stderr output: " + err + ) + + +def read_from_file(filepath): + if "OTIO_MAYA_PYTHON_BIN" not in os.environ: + raise RuntimeError( + "'OTIO_MAYA_PYTHON_BIN' not set, please set this to path to " + "mayapy within the Maya installation." + ) + + os.environ['PYTHONPATH'] = ( + os.pathsep.join( + [ + os.environ.setdefault('PYTHONPATH', ''), + os.path.dirname(__file__) + ] + ) + ) + + proc = subprocess.Popen( + [ + os.environ["OTIO_MAYA_PYTHON_BIN"], + '-m', + 'extern_maya_sequencer', + 'read', + filepath + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + env=os.environ + ) + out, err = proc.communicate() + + # maya probably puts a bunch of crap on the stdout + sentinel_str = "OTIO_JSON_BEGIN\n" + end_sentinel_str = "\nOTIO_JSON_END\n" + start = out.find(sentinel_str) + end = out.find(end_sentinel_str) + result = adapters.read_from_string( + out[start + len(sentinel_str):end], + "otio_json" + ) + + if proc.returncode: + raise RuntimeError( + "ERROR: extern_maya_sequencer (called through the maya sequencer " + "file adapter) failed. stderr output: " + err + ) + return result diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/rv.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/rv.py new file mode 100644 index 0000000000..33d00ce8c7 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/rv.py @@ -0,0 +1,84 @@ +# +# Copyright 2017 Pixar Animation Studios +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""RvSession Adapter harness""" + +import subprocess +import os +import copy + +from .. import adapters + + +def write_to_file(input_otio, filepath): + if "OTIO_RV_PYTHON_BIN" not in os.environ: + raise RuntimeError( + "'OTIO_RV_PYTHON_BIN' not set, please set this to path to " + "py-interp within the RV installation." + ) + + if "OTIO_RV_PYTHON_LIB" not in os.environ: + raise RuntimeError( + "'OTIO_RV_PYTHON_LIB' not set, please set this to path to python " + "directory within the RV installation." + ) + + input_data = adapters.write_to_string(input_otio, "otio_json") + + base_environment = copy.deepcopy(os.environ) + + base_environment['PYTHONPATH'] = ( + os.pathsep.join( + [ + base_environment.setdefault('PYTHONPATH', ''), + os.path.dirname(__file__) + ] + ) + ) + + proc = subprocess.Popen( + [ + base_environment["OTIO_RV_PYTHON_BIN"], + '-m', + 'extern_rv', + filepath + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE, + env=base_environment + ) + proc.stdin.write(input_data) + out, err = proc.communicate() + + if out.strip(): + print("stdout: {}".format(out)) + if err.strip(): + print("stderr: {}".format(err)) + + if proc.returncode: + raise RuntimeError( + "ERROR: extern_rv (called through the rv session file adapter) " + "failed. stderr output: " + err + ) diff --git a/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/xges.py b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/xges.py new file mode 100644 index 0000000000..525a8a4649 --- /dev/null +++ b/openpype/vendor/python/python_2/opentimelineio_contrib/adapters/xges.py @@ -0,0 +1,819 @@ +# +# Copyright (C) 2019 Igalia S.L +# +# Licensed under the Apache License, Version 2.0 (the "Apache License") +# with the following modification; you may not use this file except in +# compliance with the Apache License and the following modification to it: +# Section 6. Trademarks. is deleted and replaced with: +# +# 6. Trademarks. This License does not grant permission to use the trade +# names, trademarks, service marks, or product names of the Licensor +# and its affiliates, except as required to comply with Section 4(c) of +# the License and to reproduce the content of the NOTICE file. +# +# You may obtain a copy of the Apache License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the Apache License with the above modification is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the Apache License for the specific +# language governing permissions and limitations under the Apache License. +# + +"""OpenTimelineIO GStreamer Editing Services XML Adapter. """ +import re +import unittest + +from decimal import Decimal +from fractions import Fraction +from xml.etree import cElementTree +from xml.dom import minidom +import opentimelineio as otio + +META_NAMESPACE = "XGES" + + +FRAMERATE_FRAMEDURATION = {23.98: "24000/1001", + 24: "600/25", + 25: "25/1", + 29.97: "30000/1001", + 30: "30/1", + 50: "50/1", + 59.94: "60000/1001", + 60: "60/1"} + + +TRANSITION_MAP = { + "crossfade": otio.schema.TransitionTypes.SMPTE_Dissolve +} +# Two way map +TRANSITION_MAP.update(dict([(v, k) for k, v in TRANSITION_MAP.items()])) + + +class GstParseError(otio.exceptions.OTIOError): + pass + + +class GstStructure(object): + """ + GstStructure parser with a "dictionary" like API. + """ + UNESCAPE = re.compile(r'(?<!\\)\\(.)') + INT_TYPES = "".join( + ("int", "uint", "int8", "uint8", "int16", + "uint16", "int32", "uint32", "int64", "uint64") + ) + + def __init__(self, text): + self.text = text + self.modified = False + self.name, self.types, self.values = GstStructure._parse(text + ";") + + def __repr__(self): + if not self.modified: + return self.text + + res = self.name + for key, value in self.values.items(): + value_type = self.types[key] + res += ', %s=(%s)"%s"' % (key, value_type, self.escape(value)) + res += ';' + + return res + + def __getitem__(self, key): + return self.values[key] + + def set(self, key, value_type, value): + if self.types.get(key) == value_type and self.values.get(key) == value: + return + + self.modified = True + self.types[key] = value_type + self.values[key] = value + + def get(self, key, default=None): + return self.values.get(key, default) + + @staticmethod + def _find_eos(s): + # find next '"' without preceeding '\' + line = 0 + while 1: # faster than regexp for '[^\\]\"' + p = s.index('"') + line += p + 1 + if s[p - 1] != '\\': + return line + s = s[(p + 1):] + return -1 + + @staticmethod + def escape(s): + # XXX: The unicode type doesn't exist in Python 3 (all strings are unicode) + # so we have to use type(u"") which works in both Python 2 and 3. + if type(s) not in (str, type(u"")): + return s + return s.replace(" ", "\\ ") + + @staticmethod + def _parse(s): + in_string = s + types = {} + values = {} + scan = True + # parse id + p = s.find(',') + if p == -1: + try: + p = s.index(';') + except ValueError: + p = len(s) + scan = False + name = s[:p] + # parse fields + while scan: + comma_space_it = p + # skip 'name, ' / 'value, ' + while s[comma_space_it] in [' ', ',']: + comma_space_it += 1 + s = s[comma_space_it:] + p = s.index('=') + k = s[:p] + if not s[p + 1] == '(': + raise ValueError("In %s position: %d" % (in_string, p)) + s = s[(p + 2):] # skip 'key=(' + p = s.index(')') + t = s[:p] + s = s[(p + 1):] # skip 'type)' + + if s[0] == '"': + s = s[1:] # skip '"' + p = GstStructure._find_eos(s) + if p == -1: + raise ValueError + v = s[:(p - 1)] + if s[p] == ';': + scan = False + # unescape \., but not \\. (using a backref) + # need a reverse for re.escape() + v = v.replace('\\\\', '\\') + v = GstStructure.UNESCAPE.sub(r'\1', v) + else: + p = s.find(',') + if p == -1: + p = s.index(';') + scan = False + v = s[:p] + + if t == 'structure': + v = GstStructure(v) + elif t == 'string' and len(v) and v[0] == '"': + v = v[1:-1] + elif t == 'boolean': + v = (v == '1') + elif t in GstStructure.INT_TYPES: + v = int(v) + types[k] = t + values[k] = v + + return (name, types, values) + + +class GESTrackType: + UNKNOWN = 1 << 0 + AUDIO = 1 << 1 + VIDEO = 1 << 2 + TEXT = 1 << 3 + CUSTOM = 1 << 4 + + @staticmethod + def to_otio_type(_type): + if _type == GESTrackType.AUDIO: + return otio.schema.TrackKind.Audio + elif _type == GESTrackType.VIDEO: + return otio.schema.TrackKind.Video + + raise GstParseError("Can't translate track type %s" % _type) + + +GST_CLOCK_TIME_NONE = 18446744073709551615 +GST_SECOND = 1000000000 + + +def to_gstclocktime(rational_time): + """ + This converts a RationalTime object to a GstClockTime + + Args: + rational_time (RationalTime): This is a RationalTime object + + Returns: + int: A time in nanosecond + """ + + return int(rational_time.value_rescaled_to(1) * GST_SECOND) + + +def get_from_structure(xmlelement, fieldname, default=None, attribute="properties"): + structure = GstStructure(xmlelement.get(attribute, attribute)) + return structure.get(fieldname, default) + + +class XGES: + """ + This object is responsible for knowing how to convert an xGES + project into an otio timeline + """ + + def __init__(self, xml_string): + self.xges_xml = cElementTree.fromstring(xml_string) + self.rate = 25 + + def _set_rate_from_timeline(self, timeline): + metas = GstStructure(timeline.attrib.get("metadatas", "metadatas")) + framerate = metas.get("framerate") + if framerate: + rate = Fraction(framerate) + else: + video_track = timeline.find("./track[@track-type='4']") + rate = None + if video_track is not None: + properties = GstStructure( + video_track.get("properties", "properties;")) + restriction_caps = GstStructure(properties.get( + "restriction-caps", "restriction-caps")) + rate = restriction_caps.get("framerate") + + if rate is None: + return + + self.rate = float(Fraction(rate)) + if self.rate == int(self.rate): + self.rate = int(self.rate) + else: + self.rate = float(round(Decimal(self.rate), 2)) + + def to_rational_time(self, ns_timestamp): + """ + This converts a GstClockTime value to an otio RationalTime object + + Args: + ns_timestamp (int): This is a GstClockTime value (nanosecond absolute value) + + Returns: + RationalTime: A RationalTime object + """ + return otio.opentime.RationalTime(round(int(ns_timestamp) / + (GST_SECOND / self.rate)), self.rate) + + def to_otio(self): + """ + Convert an xges to an otio + + Returns: + OpenTimeline: An OpenTimeline Timeline object + """ + + project = self.xges_xml.find("./project") + metas = GstStructure(project.attrib.get("metadatas", "metadatas")) + otio_project = otio.schema.SerializableCollection( + name=metas.get('name'), + metadata={ + META_NAMESPACE: {"metadatas": project.attrib.get( + "metadatas", "metadatas")} + } + ) + timeline = project.find("./timeline") + self._set_rate_from_timeline(timeline) + + otio_timeline = otio.schema.Timeline( + name=metas.get('name', "unnamed"), + metadata={ + META_NAMESPACE: { + "metadatas": timeline.attrib.get("metadatas", "metadatas"), + "properties": timeline.attrib.get("properties", "properties") + } + } + ) + + all_names = set() + self._add_layers(timeline, otio_timeline, all_names) + otio_project.append(otio_timeline) + + return otio_project + + def _add_layers(self, timeline, otio_timeline, all_names): + for layer in timeline.findall("./layer"): + tracks = self._build_tracks_from_layer_clips(layer, all_names) + otio_timeline.tracks.extend(tracks) + + def _get_clips_for_type(self, clips, track_type): + if not clips: + return False + + clips_for_type = [] + for clip in clips: + if int(clip.attrib['track-types']) & track_type: + clips_for_type.append(clip) + + return clips_for_type + + def _build_tracks_from_layer_clips(self, layer, all_names): + all_clips = layer.findall('./clip') + + tracks = [] + for track_type in [GESTrackType.VIDEO, GESTrackType.AUDIO]: + clips = self._get_clips_for_type(all_clips, track_type) + if not clips: + continue + + track = otio.schema.Track() + track.kind = GESTrackType.to_otio_type(track_type) + self._add_clips_in_track(clips, track, all_names) + + tracks.append(track) + + return tracks + + def _add_clips_in_track(self, clips, track, all_names): + for clip in clips: + otio_clip = self._create_otio_clip(clip, all_names) + if otio_clip is None: + continue + + clip_offset = self.to_rational_time(int(clip.attrib['start'])) + if clip_offset > track.duration(): + track.append( + self._create_otio_gap( + 0, + (clip_offset - track.duration()) + ) + ) + + track.append(otio_clip) + + return track + + def _get_clip_name(self, clip, all_names): + i = 0 + tmpname = name = clip.get("name", GstStructure( + clip.get("properties", "properties;")).get("name")) + while True: + if tmpname not in all_names: + all_names.add(tmpname) + return tmpname + + i += 1 + tmpname = name + '_%d' % i + + def _create_otio_transition(self, clip, all_names): + start = self.to_rational_time(clip.attrib["start"]) + end = start + self.to_rational_time(clip.attrib["duration"]) + cut_point = otio.opentime.RationalTime((end.value - start.value) / + 2, start.rate) + + return otio.schema.Transition( + name=self._get_clip_name(clip, all_names), + transition_type=TRANSITION_MAP.get( + clip.attrib["asset-id"], otio.schema.TransitionTypes.Custom + ), + in_offset=cut_point, + out_offset=cut_point, + ) + + def _create_otio_uri_clip(self, clip, all_names): + source_range = otio.opentime.TimeRange( + start_time=self.to_rational_time(clip.attrib["inpoint"]), + duration=self.to_rational_time(clip.attrib["duration"]), + ) + + otio_clip = otio.schema.Clip( + name=self._get_clip_name(clip, all_names), + source_range=source_range, + media_reference=self._reference_from_id( + clip.get("asset-id"), clip.get("type-name")), + ) + + return otio_clip + + def _create_otio_clip(self, clip, all_names): + otio_clip = None + + if clip.get("type-name") == "GESTransitionClip": + otio_clip = self._create_otio_transition(clip, all_names) + elif clip.get("type-name") == "GESUriClip": + otio_clip = self._create_otio_uri_clip(clip, all_names) + + if otio_clip is None: + print("Could not represent: %s" % clip.attrib) + return None + + otio_clip.metadata[META_NAMESPACE] = { + "properties": clip.get("properties", "properties;"), + "metadatas": clip.get("metadatas", "metadatas;"), + } + + return otio_clip + + def _create_otio_gap(self, start, duration): + source_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime(start), + duration=duration + ) + return otio.schema.Gap(source_range=source_range) + + def _reference_from_id(self, asset_id, asset_type="GESUriClip"): + asset = self._asset_by_id(asset_id, asset_type) + if asset is None: + return None + if not asset.get("id", ""): + return otio.schema.MissingReference() + + duration = GST_CLOCK_TIME_NONE + if asset_type == "GESUriClip": + duration = get_from_structure(asset, "duration", duration) + + available_range = otio.opentime.TimeRange( + start_time=self.to_rational_time(0), + duration=self.to_rational_time(duration) + ) + ref = otio.schema.ExternalReference( + target_url=asset.get("id"), + available_range=available_range + ) + + ref.metadata[META_NAMESPACE] = { + "properties": asset.get("properties"), + "metadatas": asset.get("metadatas"), + } + + return ref + + # -------------------- + # search helpers + # -------------------- + def _asset_by_id(self, asset_id, asset_type): + return self.xges_xml.find( + "./project/ressources/asset[@id='{}'][@extractable-type-name='{}']".format( + asset_id, asset_type) + ) + + def _timeline_element_by_name(self, timeline, name): + for clip in timeline.findall("./layer/clip"): + if get_from_structure(clip, 'name') == name: + return clip + + return None + + +class XGESOtio: + + def __init__(self, input_otio): + self.container = input_otio + self.rate = 25 + + def _insert_new_sub_element(self, into_parent, tag, attrib=None, text=''): + elem = cElementTree.SubElement(into_parent, tag, **attrib or {}) + elem.text = text + return elem + + def _get_element_properties(self, element): + return element.metadata.get(META_NAMESPACE, {}).get("properties", "properties;") + + def _get_element_metadatas(self, element): + return element.metadata.get(META_NAMESPACE, + {"GES": {}}).get("metadatas", "metadatas;") + + def _serialize_ressource(self, ressources, ressource, asset_type): + if isinstance(ressource, otio.schema.MissingReference): + return + + if ressources.find("./asset[@id='%s'][@extractable-type-name='%s']" % ( + ressource.target_url, asset_type)) is not None: + return + + properties = GstStructure(self._get_element_properties(ressource)) + if properties.get('duration') is None: + properties.set('duration', 'guin64', + to_gstclocktime(ressource.available_range.duration)) + + self._insert_new_sub_element( + ressources, 'asset', + attrib={ + "id": ressource.target_url, + "extractable-type-name": 'GESUriClip', + "properties": str(properties), + "metadatas": self._get_element_metadatas(ressource), + } + ) + + def _get_transition_times(self, offset, otio_transition): + rational_offset = otio.opentime.RationalTime( + round(int(offset) / (GST_SECOND / self.rate)), + self.rate + ) + start = rational_offset - otio_transition.in_offset + end = rational_offset + otio_transition.out_offset + + return 0, to_gstclocktime(start), to_gstclocktime(end - start) + + def _serialize_clip( + self, + otio_track, + layer, + layer_priority, + ressources, + otio_clip, + clip_id, + offset + ): + + # FIXME - Figure out a proper way to determine clip type! + asset_id = "GESTitleClip" + asset_type = "GESTitleClip" + + if isinstance(otio_clip, otio.schema.Transition): + asset_type = "GESTransitionClip" + asset_id = TRANSITION_MAP.get(otio_clip.transition_type, "crossfade") + inpoint, offset, duration = self._get_transition_times(offset, otio_clip) + else: + inpoint = to_gstclocktime(otio_clip.source_range.start_time) + duration = to_gstclocktime(otio_clip.source_range.duration) + + if not isinstance(otio_clip.media_reference, otio.schema.MissingReference): + asset_id = otio_clip.media_reference.target_url + asset_type = "GESUriClip" + + self._serialize_ressource(ressources, otio_clip.media_reference, + asset_type) + + if otio_track.kind == otio.schema.TrackKind.Audio: + track_types = GESTrackType.AUDIO + elif otio_track.kind == otio.schema.TrackKind.Video: + track_types = GESTrackType.VIDEO + else: + raise ValueError("Unhandled track type: %s" % otio_track.kind) + + properties = otio_clip.metadata.get( + META_NAMESPACE, + { + "properties": 'properties, name=(string)"%s"' % ( + GstStructure.escape(otio_clip.name) + ) + }).get("properties") + return self._insert_new_sub_element( + layer, 'clip', + attrib={ + "id": str(clip_id), + "properties": properties, + "asset-id": str(asset_id), + "type-name": str(asset_type), + "track-types": str(track_types), + "layer-priority": str(layer_priority), + "start": str(offset), + "rate": '0', + "inpoint": str(inpoint), + "duration": str(duration), + "metadatas": self._get_element_metadatas(otio_clip), + } + ) + + def _serialize_tracks(self, timeline, otio_timeline): + audio_vals = ( + 'properties', + 'restriction-caps=(string)audio/x-raw(ANY)', + 'framerate=(GstFraction)1', + otio_timeline.duration().rate + ) + + properties = '%s, %s,%s/%s' % audio_vals + self._insert_new_sub_element( + timeline, 'track', + attrib={ + "caps": "audio/x-raw(ANY)", + "track-type": '2', + 'track-id': '0', + 'properties': properties + } + ) + + video_vals = ( + 'properties', + 'restriction-caps=(string)video/x-raw(ANY)', + 'framerate=(GstFraction)1', + otio_timeline.duration().rate + ) + + properties = '%s, %s,%s/%s' % video_vals + for otio_track in otio_timeline.tracks: + if otio_track.kind == otio.schema.TrackKind.Video: + self._insert_new_sub_element( + timeline, 'track', + attrib={ + "caps": "video/x-raw(ANY)", + "track-type": '4', + 'track-id': '1', + 'properties': properties, + } + ) + + return + + def _serialize_layer(self, timeline, layers, layer_priority): + if layer_priority not in layers: + layers[layer_priority] = self._insert_new_sub_element( + timeline, 'layer', + attrib={ + "priority": str(layer_priority), + } + ) + + def _serialize_timeline_element(self, timeline, layers, layer_priority, + offset, otio_track, otio_element, + ressources, all_clips): + self._serialize_layer(timeline, layers, layer_priority) + layer = layers[layer_priority] + if isinstance(otio_element, (otio.schema.Clip, otio.schema.Transition)): + element = self._serialize_clip(otio_track, layer, layer_priority, + ressources, otio_element, + str(len(all_clips)), offset) + all_clips.add(element) + if isinstance(otio_element, otio.schema.Transition): + # Make next clip overlap + return int(element.get("start")) - offset + elif not isinstance(otio_element, otio.schema.Gap): + print("FIXME: Add support for %s" % type(otio_element)) + return 0 + + return to_gstclocktime(otio_element.source_range.duration) + + def _make_element_names_unique(self, all_names, otio_element): + if isinstance(otio_element, otio.schema.Gap): + return + + if not isinstance(otio_element, otio.schema.Track): + i = 0 + name = otio_element.name + while True: + if name not in all_names: + otio_element.name = name + break + + i += 1 + name = otio_element.name + '_%d' % i + all_names.add(otio_element.name) + + if isinstance(otio_element, (otio.schema.Stack, otio.schema.Track)): + for sub_element in otio_element: + self._make_element_names_unique(all_names, sub_element) + + def _make_timeline_elements_names_unique(self, otio_timeline): + element_names = set() + for track in otio_timeline.tracks: + for element in track: + self._make_element_names_unique(element_names, element) + + def _serialize_timeline(self, project, ressources, otio_timeline): + metadatas = GstStructure(self._get_element_metadatas(otio_timeline)) + metadatas.set( + "framerate", "fraction", self._framerate_to_frame_duration( + otio_timeline.duration().rate + ) + ) + timeline = self._insert_new_sub_element( + project, 'timeline', + attrib={ + "properties": self._get_element_properties(otio_timeline), + "metadatas": str(metadatas), + } + ) + self._serialize_tracks(timeline, otio_timeline) + + self._make_timeline_elements_names_unique(otio_timeline) + + all_clips = set() + layers = {} + for layer_priority, otio_track in enumerate(otio_timeline.tracks): + self._serialize_layer(timeline, layers, layer_priority) + offset = 0 + for otio_element in otio_track: + offset += self._serialize_timeline_element( + timeline, layers, layer_priority, offset, + otio_track, otio_element, ressources, all_clips, + ) + + for layer in layers.values(): + layer[:] = sorted(layer, key=lambda child: int(child.get("start"))) + + # -------------------- + # static methods + # -------------------- + @staticmethod + def _framerate_to_frame_duration(framerate): + frame_duration = FRAMERATE_FRAMEDURATION.get(int(framerate), "") + if not frame_duration: + frame_duration = FRAMERATE_FRAMEDURATION.get(float(framerate), "") + return frame_duration + + def to_xges(self): + xges = cElementTree.Element('ges', version="0.4") + + metadatas = GstStructure(self._get_element_metadatas(self.container)) + if self.container.name is not None: + metadatas.set("name", "string", self.container.name) + if not isinstance(self.container, otio.schema.Timeline): + project = self._insert_new_sub_element( + xges, 'project', + attrib={ + "properties": self._get_element_properties(self.container), + "metadatas": str(metadatas), + } + ) + + if len(self.container) > 1: + print( + "WARNING: Only one timeline supported, using *only* the first one.") + + otio_timeline = self.container[0] + + else: + project = self._insert_new_sub_element( + xges, 'project', + attrib={ + "metadatas": str(metadatas), + } + ) + otio_timeline = self.container + + ressources = self._insert_new_sub_element(project, 'ressources') + self.rate = otio_timeline.duration().rate + self._serialize_timeline(project, ressources, otio_timeline) + + # with indentations. + string = cElementTree.tostring(xges, encoding="UTF-8") + dom = minidom.parseString(string) + return dom.toprettyxml(indent=' ') + + +# -------------------- +# adapter requirements +# -------------------- +def read_from_string(input_str): + """ + Necessary read method for otio adapter + + Args: + input_str (str): A GStreamer Editing Services formated project + + Returns: + OpenTimeline: An OpenTimeline object + """ + + return XGES(input_str).to_otio() + + +def write_to_string(input_otio): + """ + Necessary write method for otio adapter + + Args: + input_otio (OpenTimeline): An OpenTimeline object + + Returns: + str: The string contents of an FCP X XML + """ + + return XGESOtio(input_otio).to_xges() + + +# -------------------- +# Some unit check for internal types +# -------------------- + +class XGESTests(unittest.TestCase): + + def test_gst_structure_parsing(self): + struct = GstStructure('properties, name=(string)"%s";' % ( + GstStructure.escape("sc01 sh010_anim.mov")) + ) + self.assertEqual(struct["name"], "sc01 sh010_anim.mov") + + def test_gst_structure_editing(self): + struct = GstStructure('properties, name=(string)"%s";' % ( + GstStructure.escape("sc01 sh010_anim.mov")) + ) + self.assertEqual(struct["name"], "sc01 sh010_anim.mov") + + struct.set("name", "string", "test") + self.assertEqual(struct["name"], "test") + self.assertEqual(str(struct), 'properties, name=(string)"test";') + + def test_empty_string(self): + struct = GstStructure('properties, name=(string)"";') + self.assertEqual(struct["name"], "") + + +if __name__ == '__main__': + unittest.main() diff --git a/pype/vendor/python/python_2/pkg_resources/__init__.py b/openpype/vendor/python/python_2/pkg_resources/__init__.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/__init__.py rename to openpype/vendor/python/python_2/pkg_resources/__init__.py diff --git a/pype/widgets/__init__.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/__init__.py similarity index 100% rename from pype/widgets/__init__.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/__init__.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/appdirs.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/appdirs.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/appdirs.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/appdirs.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/__about__.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/__about__.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/__about__.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/__about__.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/__init__.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/__init__.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/__init__.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/__init__.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/_compat.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/_compat.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/_compat.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/_compat.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/_structures.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/_structures.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/_structures.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/_structures.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/markers.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/markers.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/markers.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/markers.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/requirements.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/requirements.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/requirements.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/requirements.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/specifiers.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/specifiers.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/specifiers.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/specifiers.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/utils.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/utils.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/utils.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/utils.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/packaging/version.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/version.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/packaging/version.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/packaging/version.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/pyparsing.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/pyparsing.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/pyparsing.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/pyparsing.py diff --git a/pype/vendor/python/python_2/pkg_resources/_vendor/six.py b/openpype/vendor/python/python_2/pkg_resources/_vendor/six.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/_vendor/six.py rename to openpype/vendor/python/python_2/pkg_resources/_vendor/six.py diff --git a/pype/vendor/python/python_2/pkg_resources/extern/__init__.py b/openpype/vendor/python/python_2/pkg_resources/extern/__init__.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/extern/__init__.py rename to openpype/vendor/python/python_2/pkg_resources/extern/__init__.py diff --git a/pype/vendor/python/python_2/pkg_resources/py2_warn.py b/openpype/vendor/python/python_2/pkg_resources/py2_warn.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/py2_warn.py rename to openpype/vendor/python/python_2/pkg_resources/py2_warn.py diff --git a/pype/vendor/python/python_2/pkg_resources/py31compat.py b/openpype/vendor/python/python_2/pkg_resources/py31compat.py similarity index 100% rename from pype/vendor/python/python_2/pkg_resources/py31compat.py rename to openpype/vendor/python/python_2/pkg_resources/py31compat.py diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/INSTALLER b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/INSTALLER similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/INSTALLER rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/INSTALLER diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/LICENSE b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/LICENSE similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/LICENSE rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/LICENSE diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/METADATA b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/METADATA similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/METADATA rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/METADATA diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/RECORD b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/RECORD similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/RECORD rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/RECORD diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/REQUESTED b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/REQUESTED similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/REQUESTED rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/REQUESTED diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/WHEEL b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/WHEEL similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/WHEEL rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/WHEEL diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/dependency_links.txt b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/dependency_links.txt similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/dependency_links.txt rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/dependency_links.txt diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/entry_points.txt b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/entry_points.txt similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/entry_points.txt rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/entry_points.txt diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/top_level.txt b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/top_level.txt similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/top_level.txt rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/top_level.txt diff --git a/pype/vendor/python/python_2/setuptools-45.0.0.dist-info/zip-safe b/openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/zip-safe similarity index 100% rename from pype/vendor/python/python_2/setuptools-45.0.0.dist-info/zip-safe rename to openpype/vendor/python/python_2/setuptools-45.0.0.dist-info/zip-safe diff --git a/pype/vendor/python/python_2/setuptools/__init__.py b/openpype/vendor/python/python_2/setuptools/__init__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/__init__.py rename to openpype/vendor/python/python_2/setuptools/__init__.py diff --git a/pype/vendor/python/python_2/setuptools/_deprecation_warning.py b/openpype/vendor/python/python_2/setuptools/_deprecation_warning.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_deprecation_warning.py rename to openpype/vendor/python/python_2/setuptools/_deprecation_warning.py diff --git a/pype/vendor/python/python_2/setuptools/_imp.py b/openpype/vendor/python/python_2/setuptools/_imp.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_imp.py rename to openpype/vendor/python/python_2/setuptools/_imp.py diff --git a/openpype/vendor/python/python_2/setuptools/_vendor/__init__.py b/openpype/vendor/python/python_2/setuptools/_vendor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/vendor/python/python_2/setuptools/_vendor/ordered_set.py b/openpype/vendor/python/python_2/setuptools/_vendor/ordered_set.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/ordered_set.py rename to openpype/vendor/python/python_2/setuptools/_vendor/ordered_set.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/__about__.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/__about__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/__about__.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/__about__.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/__init__.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/__init__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/__init__.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/__init__.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/_compat.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/_compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/_compat.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/_compat.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/_structures.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/_structures.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/_structures.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/_structures.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/markers.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/markers.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/markers.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/markers.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/requirements.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/requirements.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/requirements.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/requirements.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/specifiers.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/specifiers.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/specifiers.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/specifiers.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/tags.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/tags.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/tags.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/tags.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/utils.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/utils.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/utils.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/utils.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/packaging/version.py b/openpype/vendor/python/python_2/setuptools/_vendor/packaging/version.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/packaging/version.py rename to openpype/vendor/python/python_2/setuptools/_vendor/packaging/version.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/pyparsing.py b/openpype/vendor/python/python_2/setuptools/_vendor/pyparsing.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/pyparsing.py rename to openpype/vendor/python/python_2/setuptools/_vendor/pyparsing.py diff --git a/pype/vendor/python/python_2/setuptools/_vendor/six.py b/openpype/vendor/python/python_2/setuptools/_vendor/six.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/_vendor/six.py rename to openpype/vendor/python/python_2/setuptools/_vendor/six.py diff --git a/pype/vendor/python/python_2/setuptools/archive_util.py b/openpype/vendor/python/python_2/setuptools/archive_util.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/archive_util.py rename to openpype/vendor/python/python_2/setuptools/archive_util.py diff --git a/pype/vendor/python/python_2/setuptools/build_meta.py b/openpype/vendor/python/python_2/setuptools/build_meta.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/build_meta.py rename to openpype/vendor/python/python_2/setuptools/build_meta.py diff --git a/pype/vendor/python/python_2/setuptools/cli-32.exe b/openpype/vendor/python/python_2/setuptools/cli-32.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/cli-32.exe rename to openpype/vendor/python/python_2/setuptools/cli-32.exe diff --git a/pype/vendor/python/python_2/setuptools/cli-64.exe b/openpype/vendor/python/python_2/setuptools/cli-64.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/cli-64.exe rename to openpype/vendor/python/python_2/setuptools/cli-64.exe diff --git a/pype/vendor/python/python_2/setuptools/cli.exe b/openpype/vendor/python/python_2/setuptools/cli.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/cli.exe rename to openpype/vendor/python/python_2/setuptools/cli.exe diff --git a/pype/vendor/python/python_2/setuptools/command/__init__.py b/openpype/vendor/python/python_2/setuptools/command/__init__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/__init__.py rename to openpype/vendor/python/python_2/setuptools/command/__init__.py diff --git a/pype/vendor/python/python_2/setuptools/command/alias.py b/openpype/vendor/python/python_2/setuptools/command/alias.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/alias.py rename to openpype/vendor/python/python_2/setuptools/command/alias.py diff --git a/pype/vendor/python/python_2/setuptools/command/bdist_egg.py b/openpype/vendor/python/python_2/setuptools/command/bdist_egg.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/bdist_egg.py rename to openpype/vendor/python/python_2/setuptools/command/bdist_egg.py diff --git a/pype/vendor/python/python_2/setuptools/command/bdist_rpm.py b/openpype/vendor/python/python_2/setuptools/command/bdist_rpm.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/bdist_rpm.py rename to openpype/vendor/python/python_2/setuptools/command/bdist_rpm.py diff --git a/pype/vendor/python/python_2/setuptools/command/bdist_wininst.py b/openpype/vendor/python/python_2/setuptools/command/bdist_wininst.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/bdist_wininst.py rename to openpype/vendor/python/python_2/setuptools/command/bdist_wininst.py diff --git a/pype/vendor/python/python_2/setuptools/command/build_clib.py b/openpype/vendor/python/python_2/setuptools/command/build_clib.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/build_clib.py rename to openpype/vendor/python/python_2/setuptools/command/build_clib.py diff --git a/pype/vendor/python/python_2/setuptools/command/build_ext.py b/openpype/vendor/python/python_2/setuptools/command/build_ext.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/build_ext.py rename to openpype/vendor/python/python_2/setuptools/command/build_ext.py diff --git a/pype/vendor/python/python_2/setuptools/command/build_py.py b/openpype/vendor/python/python_2/setuptools/command/build_py.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/build_py.py rename to openpype/vendor/python/python_2/setuptools/command/build_py.py diff --git a/pype/vendor/python/python_2/setuptools/command/develop.py b/openpype/vendor/python/python_2/setuptools/command/develop.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/develop.py rename to openpype/vendor/python/python_2/setuptools/command/develop.py diff --git a/pype/vendor/python/python_2/setuptools/command/dist_info.py b/openpype/vendor/python/python_2/setuptools/command/dist_info.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/dist_info.py rename to openpype/vendor/python/python_2/setuptools/command/dist_info.py diff --git a/pype/vendor/python/python_2/setuptools/command/easy_install.py b/openpype/vendor/python/python_2/setuptools/command/easy_install.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/easy_install.py rename to openpype/vendor/python/python_2/setuptools/command/easy_install.py diff --git a/pype/vendor/python/python_2/setuptools/command/egg_info.py b/openpype/vendor/python/python_2/setuptools/command/egg_info.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/egg_info.py rename to openpype/vendor/python/python_2/setuptools/command/egg_info.py diff --git a/pype/vendor/python/python_2/setuptools/command/install.py b/openpype/vendor/python/python_2/setuptools/command/install.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/install.py rename to openpype/vendor/python/python_2/setuptools/command/install.py diff --git a/pype/vendor/python/python_2/setuptools/command/install_egg_info.py b/openpype/vendor/python/python_2/setuptools/command/install_egg_info.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/install_egg_info.py rename to openpype/vendor/python/python_2/setuptools/command/install_egg_info.py diff --git a/pype/vendor/python/python_2/setuptools/command/install_lib.py b/openpype/vendor/python/python_2/setuptools/command/install_lib.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/install_lib.py rename to openpype/vendor/python/python_2/setuptools/command/install_lib.py diff --git a/pype/vendor/python/python_2/setuptools/command/install_scripts.py b/openpype/vendor/python/python_2/setuptools/command/install_scripts.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/install_scripts.py rename to openpype/vendor/python/python_2/setuptools/command/install_scripts.py diff --git a/pype/vendor/python/python_2/setuptools/command/launcher manifest.xml b/openpype/vendor/python/python_2/setuptools/command/launcher manifest.xml similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/launcher manifest.xml rename to openpype/vendor/python/python_2/setuptools/command/launcher manifest.xml diff --git a/pype/vendor/python/python_2/setuptools/command/py36compat.py b/openpype/vendor/python/python_2/setuptools/command/py36compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/py36compat.py rename to openpype/vendor/python/python_2/setuptools/command/py36compat.py diff --git a/pype/vendor/python/python_2/setuptools/command/register.py b/openpype/vendor/python/python_2/setuptools/command/register.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/register.py rename to openpype/vendor/python/python_2/setuptools/command/register.py diff --git a/pype/vendor/python/python_2/setuptools/command/rotate.py b/openpype/vendor/python/python_2/setuptools/command/rotate.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/rotate.py rename to openpype/vendor/python/python_2/setuptools/command/rotate.py diff --git a/pype/vendor/python/python_2/setuptools/command/saveopts.py b/openpype/vendor/python/python_2/setuptools/command/saveopts.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/saveopts.py rename to openpype/vendor/python/python_2/setuptools/command/saveopts.py diff --git a/pype/vendor/python/python_2/setuptools/command/sdist.py b/openpype/vendor/python/python_2/setuptools/command/sdist.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/sdist.py rename to openpype/vendor/python/python_2/setuptools/command/sdist.py diff --git a/pype/vendor/python/python_2/setuptools/command/setopt.py b/openpype/vendor/python/python_2/setuptools/command/setopt.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/setopt.py rename to openpype/vendor/python/python_2/setuptools/command/setopt.py diff --git a/pype/vendor/python/python_2/setuptools/command/test.py b/openpype/vendor/python/python_2/setuptools/command/test.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/test.py rename to openpype/vendor/python/python_2/setuptools/command/test.py diff --git a/pype/vendor/python/python_2/setuptools/command/upload.py b/openpype/vendor/python/python_2/setuptools/command/upload.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/upload.py rename to openpype/vendor/python/python_2/setuptools/command/upload.py diff --git a/pype/vendor/python/python_2/setuptools/command/upload_docs.py b/openpype/vendor/python/python_2/setuptools/command/upload_docs.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/command/upload_docs.py rename to openpype/vendor/python/python_2/setuptools/command/upload_docs.py diff --git a/pype/vendor/python/python_2/setuptools/config.py b/openpype/vendor/python/python_2/setuptools/config.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/config.py rename to openpype/vendor/python/python_2/setuptools/config.py diff --git a/pype/vendor/python/python_2/setuptools/dep_util.py b/openpype/vendor/python/python_2/setuptools/dep_util.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/dep_util.py rename to openpype/vendor/python/python_2/setuptools/dep_util.py diff --git a/pype/vendor/python/python_2/setuptools/depends.py b/openpype/vendor/python/python_2/setuptools/depends.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/depends.py rename to openpype/vendor/python/python_2/setuptools/depends.py diff --git a/pype/vendor/python/python_2/setuptools/dist.py b/openpype/vendor/python/python_2/setuptools/dist.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/dist.py rename to openpype/vendor/python/python_2/setuptools/dist.py diff --git a/pype/vendor/python/python_2/setuptools/errors.py b/openpype/vendor/python/python_2/setuptools/errors.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/errors.py rename to openpype/vendor/python/python_2/setuptools/errors.py diff --git a/pype/vendor/python/python_2/setuptools/extension.py b/openpype/vendor/python/python_2/setuptools/extension.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/extension.py rename to openpype/vendor/python/python_2/setuptools/extension.py diff --git a/pype/vendor/python/python_2/setuptools/extern/__init__.py b/openpype/vendor/python/python_2/setuptools/extern/__init__.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/extern/__init__.py rename to openpype/vendor/python/python_2/setuptools/extern/__init__.py diff --git a/pype/vendor/python/python_2/setuptools/glob.py b/openpype/vendor/python/python_2/setuptools/glob.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/glob.py rename to openpype/vendor/python/python_2/setuptools/glob.py diff --git a/pype/vendor/python/python_2/setuptools/gui-32.exe b/openpype/vendor/python/python_2/setuptools/gui-32.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/gui-32.exe rename to openpype/vendor/python/python_2/setuptools/gui-32.exe diff --git a/pype/vendor/python/python_2/setuptools/gui-64.exe b/openpype/vendor/python/python_2/setuptools/gui-64.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/gui-64.exe rename to openpype/vendor/python/python_2/setuptools/gui-64.exe diff --git a/pype/vendor/python/python_2/setuptools/gui.exe b/openpype/vendor/python/python_2/setuptools/gui.exe similarity index 100% rename from pype/vendor/python/python_2/setuptools/gui.exe rename to openpype/vendor/python/python_2/setuptools/gui.exe diff --git a/pype/vendor/python/python_2/setuptools/installer.py b/openpype/vendor/python/python_2/setuptools/installer.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/installer.py rename to openpype/vendor/python/python_2/setuptools/installer.py diff --git a/pype/vendor/python/python_2/setuptools/launch.py b/openpype/vendor/python/python_2/setuptools/launch.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/launch.py rename to openpype/vendor/python/python_2/setuptools/launch.py diff --git a/pype/vendor/python/python_2/setuptools/lib2to3_ex.py b/openpype/vendor/python/python_2/setuptools/lib2to3_ex.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/lib2to3_ex.py rename to openpype/vendor/python/python_2/setuptools/lib2to3_ex.py diff --git a/pype/vendor/python/python_2/setuptools/monkey.py b/openpype/vendor/python/python_2/setuptools/monkey.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/monkey.py rename to openpype/vendor/python/python_2/setuptools/monkey.py diff --git a/pype/vendor/python/python_2/setuptools/msvc.py b/openpype/vendor/python/python_2/setuptools/msvc.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/msvc.py rename to openpype/vendor/python/python_2/setuptools/msvc.py diff --git a/pype/vendor/python/python_2/setuptools/namespaces.py b/openpype/vendor/python/python_2/setuptools/namespaces.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/namespaces.py rename to openpype/vendor/python/python_2/setuptools/namespaces.py diff --git a/pype/vendor/python/python_2/setuptools/package_index.py b/openpype/vendor/python/python_2/setuptools/package_index.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/package_index.py rename to openpype/vendor/python/python_2/setuptools/package_index.py diff --git a/pype/vendor/python/python_2/setuptools/py27compat.py b/openpype/vendor/python/python_2/setuptools/py27compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/py27compat.py rename to openpype/vendor/python/python_2/setuptools/py27compat.py diff --git a/pype/vendor/python/python_2/setuptools/py31compat.py b/openpype/vendor/python/python_2/setuptools/py31compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/py31compat.py rename to openpype/vendor/python/python_2/setuptools/py31compat.py diff --git a/pype/vendor/python/python_2/setuptools/py33compat.py b/openpype/vendor/python/python_2/setuptools/py33compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/py33compat.py rename to openpype/vendor/python/python_2/setuptools/py33compat.py diff --git a/pype/vendor/python/python_2/setuptools/py34compat.py b/openpype/vendor/python/python_2/setuptools/py34compat.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/py34compat.py rename to openpype/vendor/python/python_2/setuptools/py34compat.py diff --git a/pype/vendor/python/python_2/setuptools/sandbox.py b/openpype/vendor/python/python_2/setuptools/sandbox.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/sandbox.py rename to openpype/vendor/python/python_2/setuptools/sandbox.py diff --git a/pype/vendor/python/python_2/setuptools/script (dev).tmpl b/openpype/vendor/python/python_2/setuptools/script (dev).tmpl similarity index 100% rename from pype/vendor/python/python_2/setuptools/script (dev).tmpl rename to openpype/vendor/python/python_2/setuptools/script (dev).tmpl diff --git a/pype/vendor/python/python_2/setuptools/script.tmpl b/openpype/vendor/python/python_2/setuptools/script.tmpl similarity index 100% rename from pype/vendor/python/python_2/setuptools/script.tmpl rename to openpype/vendor/python/python_2/setuptools/script.tmpl diff --git a/pype/vendor/python/python_2/setuptools/site-patch.py b/openpype/vendor/python/python_2/setuptools/site-patch.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/site-patch.py rename to openpype/vendor/python/python_2/setuptools/site-patch.py diff --git a/pype/vendor/python/python_2/setuptools/ssl_support.py b/openpype/vendor/python/python_2/setuptools/ssl_support.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/ssl_support.py rename to openpype/vendor/python/python_2/setuptools/ssl_support.py diff --git a/pype/vendor/python/python_2/setuptools/unicode_utils.py b/openpype/vendor/python/python_2/setuptools/unicode_utils.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/unicode_utils.py rename to openpype/vendor/python/python_2/setuptools/unicode_utils.py diff --git a/pype/vendor/python/python_2/setuptools/version.py b/openpype/vendor/python/python_2/setuptools/version.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/version.py rename to openpype/vendor/python/python_2/setuptools/version.py diff --git a/pype/vendor/python/python_2/setuptools/wheel.py b/openpype/vendor/python/python_2/setuptools/wheel.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/wheel.py rename to openpype/vendor/python/python_2/setuptools/wheel.py diff --git a/pype/vendor/python/python_2/setuptools/windows_support.py b/openpype/vendor/python/python_2/setuptools/windows_support.py similarity index 100% rename from pype/vendor/python/python_2/setuptools/windows_support.py rename to openpype/vendor/python/python_2/setuptools/windows_support.py diff --git a/pype/vendor/python/python_3/README.md b/openpype/vendor/python/python_3/README.md similarity index 100% rename from pype/vendor/python/python_3/README.md rename to openpype/vendor/python/python_3/README.md diff --git a/pype/version.py b/openpype/version.py similarity index 68% rename from pype/version.py rename to openpype/version.py index 43bf9844af..f85ea13ac8 100644 --- a/pype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.0.0-alpha1" +__version__ = "3.0.0-beta" diff --git a/openpype/widgets/__init__.py b/openpype/widgets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/widgets/message_window.py b/openpype/widgets/message_window.py similarity index 100% rename from pype/widgets/message_window.py rename to openpype/widgets/message_window.py diff --git a/pype/widgets/popup.py b/openpype/widgets/popup.py similarity index 100% rename from pype/widgets/popup.py rename to openpype/widgets/popup.py diff --git a/pype/widgets/project_settings.py b/openpype/widgets/project_settings.py similarity index 100% rename from pype/widgets/project_settings.py rename to openpype/widgets/project_settings.py diff --git a/poetry.lock b/poetry.lock index e6c08b8ae9..6695a7bcca 100644 --- a/poetry.lock +++ b/poetry.lock @@ -637,8 +637,8 @@ view = ["PySide2 (>=5.11,<6.0)"] [package.source] type = "legacy" -url = "https://d.r1.wbsprt.com/pype.club/distribute" -reference = "pype" +url = "https://distribute.openpype.io/wheels" +reference = "openpype" [[package]] name = "packaging" diff --git a/poetry.toml b/poetry.toml index f05df6491a..19eca443f8 100644 --- a/poetry.toml +++ b/poetry.toml @@ -2,4 +2,4 @@ in-project = true [repositories.pype] -url = "http://d.r1.wbsprt.com/pype.club/distribute/" +url = "https://distribute.openpype.io/wheels/" diff --git a/pype/hosts/blender/startup/init.py b/pype/hosts/blender/startup/init.py deleted file mode 100644 index 75fe4d30f7..0000000000 --- a/pype/hosts/blender/startup/init.py +++ /dev/null @@ -1,3 +0,0 @@ -from pype.hosts.blender import api - -api.install() diff --git a/pype/hosts/premiere/README.markdown b/pype/hosts/premiere/README.markdown deleted file mode 100644 index 8d478f4c02..0000000000 --- a/pype/hosts/premiere/README.markdown +++ /dev/null @@ -1,6 +0,0 @@ -## How to -1. start aport server -1. a. deregistering path could be used [](http://localhost:4242/pipeline/deregister_plugin_path) -2. set aport into correct context by [](http://localhost:4242/pipeline/context?project=jakub_projectx&asset=shot02&task=rotopaint&app=premiera) -3. register premiera publish plugin path [](http://localhost:4242/pipeline/register_plugin_path?publish_path=C:/Users/hubert/CODE/pype-setup/repos/pype-config/pype/plugins/premiere/publish) -4. publish with test json file [](http://localhost:4242/pipeline/publish?json_data_path=C:/Users/hubert/CODE/pype-setup/repos/pype-config/pype/premiere/example_publish_reqst.json) diff --git a/pype/hosts/premiere/__init__.py b/pype/hosts/premiere/__init__.py deleted file mode 100644 index 8a9a032c54..0000000000 --- a/pype/hosts/premiere/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -from avalon import api as avalon -from pyblish import api as pyblish -from pype.api import Logger - - -from .lib import ( - setup, - reload_pipeline, - ls, - LOAD_PATH, - CREATE_PATH, - PUBLISH_PATH -) - -__all__ = [ - "setup", - "reload_pipeline", - "ls" -] - -log = Logger().get_logger(__name__) - - -def install(): - """Install Premiere-specific functionality of avalon-core. - - This is where you install menus and register families, data - and loaders into Premiere. - - It is called automatically when installing via `api.install(premiere)`. - - See the Maya equivalent for inspiration on how to implement this. - - """ - - # Disable all families except for the ones we explicitly want to see - family_states = [ - "imagesequence", - "mov" - ] - avalon.data["familiesStateDefault"] = False - avalon.data["familiesStateToggled"] = family_states - - log.info("pype.hosts.premiere installed") - - pyblish.register_host("premiere") - pyblish.register_plugin_path(PUBLISH_PATH) - log.info("Registering Premiera plug-ins..") - - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - - -def uninstall(): - """Uninstall all tha was installed - - This is where you undo everything that was done in `install()`. - That means, removing menus, deregistering families and data - and everything. It should be as though `install()` was never run, - because odds are calling this function means the user is interested - in re-installing shortly afterwards. If, for example, he has been - modifying the menu or registered families. - - """ - pyblish.deregister_host("premiere") - pyblish.deregister_plugin_path(PUBLISH_PATH) - log.info("Deregistering Premiera plug-ins..") - - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) diff --git a/pype/hosts/premiere/extensions/build_extension.bat b/pype/hosts/premiere/extensions/build_extension.bat deleted file mode 100644 index 4fcee96d21..0000000000 --- a/pype/hosts/premiere/extensions/build_extension.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off -rem You need https://github.com/Adobe-CEP/CEP-Resources/raw/master/ZXPSignCMD/4.1.1/win64/ZXPSignCmd.exe - -rem You need https://partners.adobe.com/exchangeprogram/creativecloud/support/exman-com-line-tool.html - -rem !!! make sure you run windows power shell as admin - -set pwd="12PPROext581" - -echo ">>> creating certificate ..." -.\ZXPSignCmd -selfSignedCert CZ Prague OrbiTools "Signing robot" %pwd% certificate.p12 -echo ">>> building com.pype" -.\ZXPSignCmd -sign com.pype/ pype.zxp certificate.p12 %pwd% -echo ">>> building com.pype.rename" -.\ZXPSignCmd -sign com.pype.rename/ pype_rename.zxp certificate.p12 %pwd% - -echo ">>> installing com.pype" -.\ExManCmd.exe /install .\pype.zxp -echo ">>> installing com.pype.rename" -.\ExManCmd.exe /install .\pype_rename.zxp diff --git a/pype/hosts/premiere/extensions/com.pype.rename/.debug b/pype/hosts/premiere/extensions/com.pype.rename/.debug deleted file mode 100644 index de631269e6..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/.debug +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ExtensionList> - <Extension Id="com.pype.rename"> - <HostList> - <Host Name="PPRO" Port="7776"/> - </HostList> - </Extension> -</ExtensionList> diff --git a/pype/hosts/premiere/extensions/com.pype.rename/CSXS/manifest.xml b/pype/hosts/premiere/extensions/com.pype.rename/CSXS/manifest.xml deleted file mode 100644 index 8e6c1a43c4..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/CSXS/manifest.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - - -. == [ part 0f PyPE CluB ] == .- -_______________.___._____________________ -\______ \__ | |\______ \_ _____/ - | ___// | | | ___/| __)_ - | | \____ | | | | \ - |____| / ______| |____| /_______ / - \/ \/ - .. __/ CliP R3N4M3R \__ .. - ---> -<ExtensionManifest Version="5.0" ExtensionBundleId="com.pype.rename" ExtensionBundleVersion="0.1.0" -ExtensionBundleName="Pype Rename dialog" -xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <ExtensionList> - <Extension Id="com.pype.rename" Version="0.1.0" /> - </ExtensionList> - <ExecutionEnvironment> - <HostList> - <Host Name="PPRO" Version="9.0" /> - </HostList> - <LocaleList> - <Locale Code="All" /> - </LocaleList> - <RequiredRuntimeList> - <RequiredRuntime Name="CSXS" Version="6.0" /> - </RequiredRuntimeList> - </ExecutionEnvironment> - - <DispatchInfoList> - <Extension Id="com.pype.rename"> - <DispatchInfo > - <Resources> - <MainPath>./index.html</MainPath> - <ScriptPath>./jsx/PypeRename.jsx</ScriptPath> - <CEFCommandLine> - <Parameter>--allow-file-access</Parameter> - <Parameter>--allow-file-access-from-files</Parameter> - <Parameter>--mixed-context</Parameter> - </CEFCommandLine> - </Resources> - <Lifecycle> - <AutoVisible>true</AutoVisible> - </Lifecycle> - <UI> - <Type>Panel</Type> - <Menu>Pype Rename</Menu> - <Geometry> - <Size> - <Height>550</Height> - <Width>400</Width> - </Size> - </Geometry> - </UI> - </DispatchInfo> - </Extension> - </DispatchInfoList> -</ExtensionManifest> diff --git a/pype/hosts/premiere/extensions/com.pype.rename/ReadMe.md b/pype/hosts/premiere/extensions/com.pype.rename/ReadMe.md deleted file mode 100644 index d98fd99738..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/ReadMe.md +++ /dev/null @@ -1,40 +0,0 @@ -# Pype timeline items renamer - -This panel is used to rename selected clips on timeline. It is not directly interconnected with **Avalon** so it can be used separately. It has several different modes: - -### Sequential Rename with Hierarchy - -This mode uses tokens as `{folder}`, `{episode}` and `{sequence}` to rename clips along with numeric padding pattern `####`. Value for this tokens is filled in input boxes. If value is left empty, token is ignored. Those values are also stored in clips properties, so even if token is not used in clip name, it will be stored in clip property (and then used to create hierarchy when publishing into **Avalon**). `####` pattern can be arbitrary long, but is mandatory. - -Example: - -``` -{folder}_{episode}_{sequence}_##### -``` -Will result in `f01_ep01_sq01_0010` if respective tokens are set to these values. If folder token value isn't set result will be `_ep01_sq01_0010`. - -Clip numbering can be adjusted by **Start #** and **Increment** fields. Setting start to **10** and increment to **10** with number padding pattern **####** will result in clip number **0010** for first clip, **0020** for second and so on. - -### Sequential Rename - -Is same as the one above, except not using tokens. - -### Simple Rename - -This will rename shot to new specified name. If `{shot}` token is used, it will reference current clip name. So if current clip name is `clip01` and we specify new name as `{shot}_foo`, result will be **clip01_foo**. - -### Find and replace - -Classic find and replace mode, using `{shot}` token as the mode above. - -### Match sequence - -Is not implemented yet. - -### Clip Rename - -Will name clip based on filename without extension. - -### Change Case - -This will change case of clip name to `UPPER` or `lower` case. diff --git a/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css b/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css deleted file mode 100644 index e6b4977799..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.2.1 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors - * Copyright 2011-2018 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(2.25rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.8125rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(2.875rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:2.25rem;background-repeat:no-repeat;background-position:center right calc(2.25rem / 4);background-size:calc(2.25rem / 2) calc(2.25rem / 2);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e")}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:2.25rem;background-position:top calc(2.25rem / 4) right calc(2.25rem / 4)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:3.4375rem;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") no-repeat center right 1.75rem/1.125rem 1.125rem}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:2.25rem;background-repeat:no-repeat;background-position:center right calc(2.25rem / 4);background-size:calc(2.25rem / 2) calc(2.25rem / 2);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E")}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:2.25rem;background-position:top calc(2.25rem / 4) right calc(2.25rem / 4)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:3.4375rem;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") no-repeat center right 1.75rem/1.125rem 1.125rem}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media screen and (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media screen and (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-right{right:0;left:auto}}.dropdown-menu-left{right:auto;left:0}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:first-child{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.dropdown-item:last-child{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(2.875rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.8125rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:2.25rem;padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media screen and (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;color:inherit;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion .card{overflow:hidden}.accordion .card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion .card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion .card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion .card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion .card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media screen and (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled){cursor:pointer}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);border-radius:.25rem;box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media screen and (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-dialog-centered::before{display:block;height:calc(100vh - (.5rem * 2));content:""}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-dialog-centered::before{height:calc(100vh - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media screen and (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media screen and (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media screen and (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media screen and (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-3by4::before{padding-top:133.333333%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css.map b/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css.map deleted file mode 100644 index 5acf96bdd1..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/css/bootstrap.min.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","bootstrap.css","../../scss/mixins/_hover.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/mixins/_border-radius.scss","../../scss/_code.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/mixins/_breakpoints.scss","../../scss/mixins/_grid-framework.scss","../../scss/_tables.scss","../../scss/mixins/_table-row.scss","../../scss/_forms.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_forms.scss","../../scss/mixins/_gradients.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/mixins/_nav-divider.scss","../../scss/_button-group.scss","../../scss/_input-group.scss","../../scss/_custom-forms.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/mixins/_badge.scss","../../scss/_jumbotron.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_media.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/utilities/_align.scss","../../scss/mixins/_background-variant.scss","../../scss/utilities/_background.scss","../../scss/utilities/_borders.scss","../../scss/utilities/_display.scss","../../scss/utilities/_embed.scss","../../scss/utilities/_flex.scss","../../scss/utilities/_float.scss","../../scss/mixins/_float.scss","../../scss/utilities/_overflow.scss","../../scss/utilities/_position.scss","../../scss/utilities/_screenreaders.scss","../../scss/mixins/_screen-reader.scss","../../scss/utilities/_shadows.scss","../../scss/utilities/_sizing.scss","../../scss/utilities/_spacing.scss","../../scss/utilities/_text.scss","../../scss/mixins/_text-truncate.scss","../../scss/mixins/_text-emphasis.scss","../../scss/mixins/_text-hide.scss","../../scss/utilities/_visibility.scss","../../scss/mixins/_visibility.scss","../../scss/_print.scss"],"names":[],"mappings":"AAAA;;;;;ACAA,MAGI,OAAA,QAAA,SAAA,QAAA,SAAA,QAAA,OAAA,QAAA,MAAA,QAAA,SAAA,QAAA,SAAA,QAAA,QAAA,QAAA,OAAA,QAAA,OAAA,QAAA,QAAA,KAAA,OAAA,QAAA,YAAA,QAIA,UAAA,QAAA,YAAA,QAAA,UAAA,QAAA,OAAA,QAAA,UAAA,QAAA,SAAA,QAAA,QAAA,QAAA,OAAA,QAIA,gBAAA,EAAA,gBAAA,MAAA,gBAAA,MAAA,gBAAA,MAAA,gBAAA,OAKF,yBAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBACA,wBAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UCCF,ECqBA,QADA,SDjBE,WAAA,WAGF,KACE,YAAA,WACA,YAAA,KACA,yBAAA,KACA,4BAAA,YAMF,QAAA,MAAA,WAAA,OAAA,OAAA,OAAA,OAAA,KAAA,IAAA,QACE,QAAA,MAUF,KACE,OAAA,EACA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,KACA,iBAAA,KEYF,sBFHE,QAAA,YASF,GACE,WAAA,YACA,OAAA,EACA,SAAA,QAaF,GAAA,GAAA,GAAA,GAAA,GAAA,GACE,WAAA,EACA,cAAA,MAOF,EACE,WAAA,EACA,cAAA,KCZF,0BDuBA,YAEE,gBAAA,UACA,wBAAA,UAAA,OAAA,gBAAA,UAAA,OACA,OAAA,KACA,cAAA,EACA,yBAAA,KAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QClBF,GDqBA,GCtBA,GDyBE,WAAA,EACA,cAAA,KAGF,MCrBA,MACA,MAFA,MD0BE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAGF,ECtBA,ODwBE,YAAA,OAGF,MACE,UAAA,IAQF,IC3BA,ID6BE,SAAA,SACA,UAAA,IACA,YAAA,EACA,eAAA,SAGF,IAAM,OAAA,OACN,IAAM,IAAA,MAON,EACE,MAAA,QACA,gBAAA,KACA,iBAAA,YG5KA,QH+KE,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KGxLA,oCAAA,oCH2LE,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,EC7BJ,KACA,IDqCA,ICpCA,KDwCE,YAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UACA,UAAA,IAGF,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAQF,OAEE,OAAA,EAAA,EAAA,KAQF,IACE,eAAA,OACA,aAAA,KAGF,IAGE,SAAA,OACA,eAAA,OAQF,MACE,gBAAA,SAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAGE,WAAA,QAQF,MAEE,QAAA,aACA,cAAA,MAMF,OACE,cAAA,EAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBCvEF,OD0EA,MCxEA,SADA,OAEA,SD4EE,OAAA,EACA,YAAA,QACA,UAAA,QACA,YAAA,QAGF,OC1EA,MD4EE,SAAA,QAGF,OC1EA,OD4EE,eAAA,KCvEF,cACA,aACA,cD2EA,OAIE,mBAAA,OC1EF,gCACA,+BACA,gCD4EA,yBAIE,QAAA,EACA,aAAA,KC3EF,qBD8EA,kBAEE,WAAA,WACA,QAAA,EAIF,iBC9EA,2BACA,kBAFA,iBDwFE,mBAAA,QAGF,SACE,SAAA,KAEA,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAKF,OACE,QAAA,MACA,MAAA,KACA,UAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QACA,MAAA,QACA,YAAA,OAGF,SACE,eAAA,SE5FF,yCDEA,yCDgGE,OAAA,KE7FF,cFqGE,eAAA,KACA,mBAAA,KEjGF,yCFyGE,mBAAA,KAQF,6BACE,KAAA,QACA,mBAAA,OAOF,OACE,QAAA,aAGF,QACE,QAAA,UACA,OAAA,QAGF,SACE,QAAA,KE9GF,SFoHE,QAAA,eC9GF,IAAK,IAAK,IAAK,IAAK,IAAK,IGxVzB,GAAA,GAAA,GAAA,GAAA,GAAA,GAEE,cAAA,MACA,YAAA,QACA,YAAA,IACA,YAAA,IACA,MAAA,QAGF,IAAA,GAAU,UAAA,OACV,IAAA,GAAU,UAAA,KACV,IAAA,GAAU,UAAA,QACV,IAAA,GAAU,UAAA,OACV,IAAA,GAAU,UAAA,QACV,IAAA,GAAU,UAAA,KAEV,MACE,UAAA,QACA,YAAA,IAIF,WACE,UAAA,KACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IAEF,WACE,UAAA,OACA,YAAA,IACA,YAAA,IJyBF,GIhBE,WAAA,KACA,cAAA,KACA,OAAA,EACA,WAAA,IAAA,MAAA,eHyWF,OGjWA,MAEE,UAAA,IACA,YAAA,IHoWF,MGjWA,KAEE,QAAA,KACA,iBAAA,QAQF,eC/EE,aAAA,EACA,WAAA,KDmFF,aCpFE,aAAA,EACA,WAAA,KDsFF,kBACE,QAAA,aADF,mCAII,aAAA,MAUJ,YACE,UAAA,IACA,eAAA,UAIF,YACE,cAAA,KACA,UAAA,QAGF,mBACE,QAAA,MACA,UAAA,IACA,MAAA,QAHF,2BAMI,QAAA,aEnHJ,WCIE,UAAA,KAGA,OAAA,KDDF,eACE,QAAA,OACA,iBAAA,KACA,OAAA,IAAA,MAAA,QEZE,cAAA,ODOF,UAAA,KAGA,OAAA,KDcF,QAEE,QAAA,aAGF,YACE,cAAA,MACA,YAAA,EAGF,gBACE,UAAA,IACA,MAAA,QGvCF,KACE,UAAA,MACA,MAAA,QACA,WAAA,WAGA,OACE,MAAA,QAKJ,IACE,QAAA,MAAA,MACA,UAAA,MACA,MAAA,KACA,iBAAA,QDbE,cAAA,MCSJ,QASI,QAAA,EACA,UAAA,KACA,YAAA,ITyMJ,ISlME,QAAA,MACA,UAAA,MACA,MAAA,QAHF,SAOI,UAAA,QACA,MAAA,QACA,WAAA,OAKJ,gBACE,WAAA,MACA,WAAA,OCzCA,WCAA,MAAA,KACA,cAAA,KACA,aAAA,KACA,aAAA,KACA,YAAA,KCmDE,yBFvDF,WCYI,UAAA,OC2CF,yBFvDF,WCYI,UAAA,OC2CF,yBFvDF,WCYI,UAAA,OC2CF,0BFvDF,WCYI,UAAA,QDAJ,iBCZA,MAAA,KACA,cAAA,KACA,aAAA,KACA,aAAA,KACA,YAAA,KDkBA,KCJA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,MACA,YAAA,MDOA,YACE,aAAA,EACA,YAAA,EAFF,iBT+iBF,0BSziBM,cAAA,EACA,aAAA,EGjCJ,KAAA,OAAA,QAAA,QAAA,QAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OAAA,OZ+kBF,UAEqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aAFqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aAFkJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACnG,aAEqJ,QAAvI,UAAmG,WAAY,WAAY,WAAhH,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UAAW,UACtG,aYllBI,SAAA,SACA,MAAA,KACA,cAAA,KACA,aAAA,KAmBE,KACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,UACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,OFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,OFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,QFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,QFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,QFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,aAAwB,eAAA,GAAA,MAAA,GAExB,YAAuB,eAAA,GAAA,MAAA,GAGrB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,SAAwB,eAAA,EAAA,MAAA,EAAxB,UAAwB,eAAA,GAAA,MAAA,GAAxB,UAAwB,eAAA,GAAA,MAAA,GAAxB,UAAwB,eAAA,GAAA,MAAA,GAMtB,UFTR,YAAA,UESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,UFTR,YAAA,WESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,UFTR,YAAA,WESQ,UFTR,YAAA,WESQ,UFTR,YAAA,IESQ,WFTR,YAAA,WESQ,WFTR,YAAA,WCWE,yBC9BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCWE,yBC9BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCWE,yBC9BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YCWE,0BC9BE,QACE,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,UAAA,KAEF,aACE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,UAAA,KAIA,UFFN,SAAA,EAAA,EAAA,UAAA,KAAA,EAAA,EAAA,UAIA,UAAA,UEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,UFFN,SAAA,EAAA,EAAA,IAAA,KAAA,EAAA,EAAA,IAIA,UAAA,IEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,WAAA,KAAA,EAAA,EAAA,WAIA,UAAA,WEFM,WFFN,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAIA,UAAA,KEGI,gBAAwB,eAAA,GAAA,MAAA,GAExB,eAAuB,eAAA,GAAA,MAAA,GAGrB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,YAAwB,eAAA,EAAA,MAAA,EAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAAxB,aAAwB,eAAA,GAAA,MAAA,GAMtB,aFTR,YAAA,EESQ,aFTR,YAAA,UESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,aFTR,YAAA,WESQ,aFTR,YAAA,WESQ,aFTR,YAAA,IESQ,cFTR,YAAA,WESQ,cFTR,YAAA,YG7CF,OACE,MAAA,KACA,cAAA,KACA,iBAAA,Yb+9CF,Ual+CA,UAOI,QAAA,OACA,eAAA,IACA,WAAA,IAAA,MAAA,QATJ,gBAaI,eAAA,OACA,cAAA,IAAA,MAAA,QAdJ,mBAkBI,WAAA,IAAA,MAAA,QAlBJ,cAsBI,iBAAA,Kbg+CJ,aav9CA,aAGI,QAAA,MASJ,gBACE,OAAA,IAAA,MAAA,Qbm9CF,mBap9CA,mBAKI,OAAA,IAAA,MAAA,Qbo9CJ,yBaz9CA,yBAWM,oBAAA,Ibq9CN,8BAFA,qBa98CA,qBb+8CA,2Ba18CI,OAAA,EAQJ,yCAEI,iBAAA,gBXlEF,4BW8EI,iBAAA,iBCrFJ,edwhDF,kBADA,kBcnhDM,iBAAA,Qd2hDN,2BAFA,kBc7hDE,kBd8hDF,wBclhDQ,aAAA,QZLN,kCYiBM,iBAAA,QALN,qCdqhDF,qCc5gDU,iBAAA,QA5BR,iBd8iDF,oBADA,oBcziDM,iBAAA,QdijDN,6BAFA,oBcnjDE,oBdojDF,0BcxiDQ,aAAA,QZLN,oCYiBM,iBAAA,QALN,uCd2iDF,uCcliDU,iBAAA,QA5BR,edokDF,kBADA,kBc/jDM,iBAAA,QdukDN,2BAFA,kBczkDE,kBd0kDF,wBc9jDQ,aAAA,QZLN,kCYiBM,iBAAA,QALN,qCdikDF,qCcxjDU,iBAAA,QA5BR,Yd0lDF,eADA,ecrlDM,iBAAA,Qd6lDN,wBAFA,ec/lDE,edgmDF,qBcplDQ,aAAA,QZLN,+BYiBM,iBAAA,QALN,kCdulDF,kCc9kDU,iBAAA,QA5BR,edgnDF,kBADA,kBc3mDM,iBAAA,QdmnDN,2BAFA,kBcrnDE,kBdsnDF,wBc1mDQ,aAAA,QZLN,kCYiBM,iBAAA,QALN,qCd6mDF,qCcpmDU,iBAAA,QA5BR,cdsoDF,iBADA,iBcjoDM,iBAAA,QdyoDN,0BAFA,iBc3oDE,iBd4oDF,uBchoDQ,aAAA,QZLN,iCYiBM,iBAAA,QALN,oCdmoDF,oCc1nDU,iBAAA,QA5BR,ad4pDF,gBADA,gBcvpDM,iBAAA,Qd+pDN,yBAFA,gBcjqDE,gBdkqDF,sBctpDQ,aAAA,QZLN,gCYiBM,iBAAA,QALN,mCdypDF,mCchpDU,iBAAA,QA5BR,YdkrDF,eADA,ec7qDM,iBAAA,QdqrDN,wBAFA,ecvrDE,edwrDF,qBc5qDQ,aAAA,QZLN,+BYiBM,iBAAA,QALN,kCd+qDF,kCctqDU,iBAAA,QA5BR,cdwsDF,iBADA,iBcnsDM,iBAAA,iBZGJ,iCYiBM,iBAAA,iBALN,oCd8rDF,oCcrrDU,iBAAA,iBDgFV,sBAGM,MAAA,KACA,iBAAA,QACA,aAAA,QALN,uBAWM,MAAA,QACA,iBAAA,QACA,aAAA,QAKN,YACE,MAAA,KACA,iBAAA,QbumDF,eazmDA,eb0mDA,qBanmDI,aAAA,QAPJ,2BAWI,OAAA,EAXJ,oDAgBM,iBAAA,sBXvIJ,uCW8IM,iBAAA,uBFjFJ,4BEkGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF5GN,4BEkGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF5GN,4BEkGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GF5GN,6BEkGA,qBAEI,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBANH,qCAUK,OAAA,GAfV,kBAOQ,QAAA,MACA,MAAA,KACA,WAAA,KACA,2BAAA,MACA,mBAAA,yBAXR,kCAeU,OAAA,EE/KV,cACE,QAAA,MACA,MAAA,KACA,OAAA,oBACA,QAAA,QAAA,OACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,QAKE,cAAA,OChBE,WAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAKF,kDDLJ,cCMM,WAAA,MDNN,0BA2BI,iBAAA,YACA,OAAA,EErBF,oBACE,MAAA,QACA,iBAAA,KACA,aAAA,QACA,QAAA,EAKE,WAAA,EAAA,EAAA,EAAA,MAAA,oBFhBN,yCAoCI,MAAA,QAEA,QAAA,EAtCJ,gCAoCI,MAAA,QAEA,QAAA,EAtCJ,oCAoCI,MAAA,QAEA,QAAA,EAtCJ,qCAoCI,MAAA,QAEA,QAAA,EAtCJ,2BAoCI,MAAA,QAEA,QAAA,EAtCJ,uBAAA,wBAgDI,iBAAA,QAEA,QAAA,EAIJ,qCAOI,MAAA,QACA,iBAAA,KAKJ,mBf2zDA,oBezzDE,QAAA,MACA,MAAA,KAUF,gBACE,YAAA,oBACA,eAAA,oBACA,cAAA,EACA,UAAA,QACA,YAAA,IAGF,mBACE,YAAA,kBACA,eAAA,kBACA,UAAA,QACA,YAAA,IAGF,mBACE,YAAA,mBACA,eAAA,mBACA,UAAA,QACA,YAAA,IASF,wBACE,QAAA,MACA,MAAA,KACA,YAAA,QACA,eAAA,QACA,cAAA,EACA,YAAA,IACA,MAAA,QACA,iBAAA,YACA,OAAA,MAAA,YACA,aAAA,IAAA,EAVF,wCAAA,wCAcI,cAAA,EACA,aAAA,EAYJ,iBACE,OAAA,sBACA,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IR7IE,cAAA,MQiJJ,iBACE,OAAA,qBACA,QAAA,MAAA,KACA,UAAA,QACA,YAAA,IRrJE,cAAA,MQ0JJ,8BAAA,0BAGI,OAAA,KAKJ,sBACE,OAAA,KAQF,YACE,cAAA,KAGF,WACE,QAAA,MACA,WAAA,OAQF,UACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,KACA,YAAA,KAJF,ef+xDA,wBevxDI,cAAA,IACA,aAAA,IASJ,YACE,SAAA,SACA,QAAA,MACA,aAAA,QAGF,kBACE,SAAA,SACA,WAAA,MACA,YAAA,SAHF,6CAMI,MAAA,QAIJ,kBACE,cAAA,EAGF,mBACE,QAAA,mBAAA,QAAA,YACA,eAAA,OAAA,YAAA,OACA,aAAA,EACA,aAAA,OAJF,qCAQI,SAAA,OACA,WAAA,EACA,aAAA,SACA,YAAA,EEjNF,gBACE,QAAA,KACA,MAAA,KACA,WAAA,OACA,UAAA,IACA,MAAA,QAGF,eACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,OAAA,MACA,WAAA,MACA,UAAA,QACA,YAAA,IACA,MAAA,KACA,iBAAA,mBV5CA,cAAA,OUiDA,uBAAA,mCAEE,aAAA,QAGE,cAAA,QACA,kBAAA,UACA,oBAAA,OAAA,MAAA,kBACA,gBAAA,kBAAA,kBAGE,iBAAA,2OAXN,6BAAA,yCAkBI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjBm+D6C,uCACrD,sCiBv/DI,mDjBs/DJ,kDiB99DQ,QAAA,MAOJ,2CAAA,+BAGI,cAAA,QACA,oBAAA,IAAA,kBAAA,MAAA,kBAMJ,wBAAA,oCAEE,aAAA,QAIE,cAAA,UACA,WAAA,0JAAA,UAAA,MAAA,OAAA,MAAA,CAAA,IAAA,IAAA,CAAA,2OAAA,UAAA,OAAA,MAAA,OAAA,CAAA,SAAA,SAPJ,8BAAA,0CAWI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjBu9D8C,wCACtD,uCiBp+DI,oDjBm+DJ,mDiBl9DQ,QAAA,MjBw9DkD,4CAC1D,2CiBl9DI,wDjBi9DJ,uDiB78DQ,QAAA,MAMJ,6CAAA,yDAGI,MAAA,QjB88DiD,2CACzD,0CiBl9DI,uDjBi9DJ,sDiBz8DQ,QAAA,MAMJ,qDAAA,iEAGI,MAAA,QAHJ,6DAAA,yEAMM,aAAA,QjB28DmD,+CAC7D,8CiBl9DI,2DjBi9DJ,0DiBr8DQ,QAAA,MAZJ,qEAAA,iFAiBM,aAAA,QCzJN,iBAAA,QDwIA,mEAAA,+EAwBM,WAAA,EAAA,EAAA,EAAA,MAAA,oBAxBN,iFAAA,6FA4BM,aAAA,QAQN,+CAAA,2DAGI,aAAA,QjBi8DkD,4CAC1D,2CiBr8DI,wDjBo8DJ,uDiB57DQ,QAAA,MARJ,qDAAA,iEAaM,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBAnKR,kBACE,QAAA,KACA,MAAA,KACA,WAAA,OACA,UAAA,IACA,MAAA,QAGF,iBACE,SAAA,SACA,IAAA,KACA,QAAA,EACA,QAAA,KACA,UAAA,KACA,QAAA,OAAA,MACA,WAAA,MACA,UAAA,QACA,YAAA,IACA,MAAA,KACA,iBAAA,mBV5CA,cAAA,OUiDA,yBAAA,qCAEE,aAAA,QAGE,cAAA,QACA,kBAAA,UACA,oBAAA,OAAA,MAAA,kBACA,gBAAA,kBAAA,kBAKE,iBAAA,qRAbN,+BAAA,2CAkBI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjB8lEiD,2CACzD,0CiBlnEI,uDjBinEJ,sDiBzlEQ,QAAA,MAOJ,6CAAA,iCAGI,cAAA,QACA,oBAAA,IAAA,kBAAA,MAAA,kBAMJ,0BAAA,sCAEE,aAAA,QAIE,cAAA,UACA,WAAA,0JAAA,UAAA,MAAA,OAAA,MAAA,CAAA,IAAA,IAAA,CAAA,qRAAA,UAAA,OAAA,MAAA,OAAA,CAAA,SAAA,SAPJ,gCAAA,4CAWI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBjBklEkD,4CAC1D,2CiB/lEI,wDjB8lEJ,uDiB7kEQ,QAAA,MjBmlEsD,gDAC9D,+CiB7kEI,4DjB4kEJ,2DiBxkEQ,QAAA,MAMJ,+CAAA,2DAGI,MAAA,QjBykEqD,+CAC7D,8CiB7kEI,2DjB4kEJ,0DiBpkEQ,QAAA,MAMJ,uDAAA,mEAGI,MAAA,QAHJ,+DAAA,2EAMM,aAAA,QjBskEuD,mDACjE,kDiB7kEI,+DjB4kEJ,8DiBhkEQ,QAAA,MAZJ,uEAAA,mFAiBM,aAAA,QCzJN,iBAAA,QDwIA,qEAAA,iFAwBM,WAAA,EAAA,EAAA,EAAA,MAAA,oBAxBN,mFAAA,+FA4BM,aAAA,QAQN,iDAAA,6DAGI,aAAA,QjB4jEsD,gDAC9D,+CiBhkEI,4DjB+jEJ,2DiBvjEQ,QAAA,MARJ,uDAAA,mEAaM,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBFsEV,aACE,QAAA,YAAA,QAAA,KACA,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,eAAA,OAAA,YAAA,OAHF,yBASI,MAAA,KJnNA,yBI0MJ,mBAeM,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,cAAA,EAlBN,yBAuBM,QAAA,YAAA,QAAA,KACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,EA3BN,2BAgCM,QAAA,aACA,MAAA,KACA,eAAA,OAlCN,qCAuCM,QAAA,afy/DJ,4BehiEF,0BA4CM,MAAA,KA5CN,yBAkDM,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,KACA,aAAA,EAtDN,+BAyDM,SAAA,SACA,WAAA,EACA,aAAA,OACA,YAAA,EA5DN,6BAgEM,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OAjEN,mCAoEM,cAAA,GIpUN,KACE,QAAA,aACA,YAAA,IACA,MAAA,QACA,WAAA,OACA,eAAA,OACA,oBAAA,KAAA,iBAAA,KAAA,gBAAA,KAAA,YAAA,KACA,iBAAA,YACA,OAAA,IAAA,MAAA,YCuFA,QAAA,QAAA,OACA,UAAA,KACA,YAAA,IAGE,cAAA,OJpGE,WAAA,MAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAKF,kDGLJ,KHMM,WAAA,MdAJ,WiBOE,MAAA,QACA,gBAAA,KAdJ,WAAA,WAmBI,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBApBJ,cAAA,cA0BI,QAAA,IA1BJ,mCAgCI,OAAA,QAcJ,enB4zEA,wBmB1zEE,eAAA,KASA,aCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,oBAKJ,sBAAA,sBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,kDAAA,kDpBo2EF,mCoBj2EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpBi2EJ,yCoB51EQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDSN,eCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,qBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,qBAAA,qBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,qBAKJ,wBAAA,wBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,oDAAA,oDpBs4EF,qCoBn4EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,0DAAA,0DpBm4EJ,2CoB93EQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDSN,aCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,sBAAA,sBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,kDAAA,kDpBw6EF,mCoBr6EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpBq6EJ,yCoBh6EQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDSN,UCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,gBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,gBAAA,gBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,oBAKJ,mBAAA,mBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,+CAAA,+CpB08EF,gCoBv8EI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,qDAAA,qDpBu8EJ,sCoBl8EQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDSN,aCzDA,MAAA,QFAE,iBAAA,QEEF,aAAA,QlBIA,mBkBAE,MAAA,QFNA,iBAAA,QEQA,aAAA,QAGF,mBAAA,mBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,oBAKJ,sBAAA,sBAEE,MAAA,QACA,iBAAA,QACA,aAAA,QAOF,kDAAA,kDpB4+EF,mCoBz+EI,MAAA,QACA,iBAAA,QAIA,aAAA,QAEA,wDAAA,wDpBy+EJ,yCoBp+EQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDSN,YCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,kBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,kBAAA,kBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,mBAKJ,qBAAA,qBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,iDAAA,iDpB8gFF,kCoB3gFI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,uDAAA,uDpB2gFJ,wCoBtgFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDSN,WCzDA,MAAA,QFAE,iBAAA,QEEF,aAAA,QlBIA,iBkBAE,MAAA,QFNA,iBAAA,QEQA,aAAA,QAGF,iBAAA,iBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,qBAKJ,oBAAA,oBAEE,MAAA,QACA,iBAAA,QACA,aAAA,QAOF,gDAAA,gDpBgjFF,iCoB7iFI,MAAA,QACA,iBAAA,QAIA,aAAA,QAEA,sDAAA,sDpB6iFJ,uCoBxiFQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDSN,UCzDA,MAAA,KFAE,iBAAA,QEEF,aAAA,QlBIA,gBkBAE,MAAA,KFNA,iBAAA,QEQA,aAAA,QAGF,gBAAA,gBAMI,WAAA,EAAA,EAAA,EAAA,MAAA,kBAKJ,mBAAA,mBAEE,MAAA,KACA,iBAAA,QACA,aAAA,QAOF,+CAAA,+CpBklFF,gCoB/kFI,MAAA,KACA,iBAAA,QAIA,aAAA,QAEA,qDAAA,qDpB+kFJ,sCoB1kFQ,WAAA,EAAA,EAAA,EAAA,MAAA,kBDeN,qBCRA,MAAA,QACA,aAAA,QlBlDA,2BkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpBwkFF,2CoBrkFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpBwkFJ,iDoBnkFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDxBN,uBCRA,MAAA,QACA,aAAA,QlBlDA,6BkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,6BAAA,6BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,qBAGF,gCAAA,gCAEE,MAAA,QACA,iBAAA,YAGF,4DAAA,4DpBwmFF,6CoBrmFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,kEAAA,kEpBwmFJ,mDoBnmFQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDxBN,qBCRA,MAAA,QACA,aAAA,QlBlDA,2BkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpBwoFF,2CoBroFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpBwoFJ,iDoBnoFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDxBN,kBCRA,MAAA,QACA,aAAA,QlBlDA,wBkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,wBAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,oBAGF,2BAAA,2BAEE,MAAA,QACA,iBAAA,YAGF,uDAAA,uDpBwqFF,wCoBrqFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6DAAA,6DpBwqFJ,8CoBnqFQ,WAAA,EAAA,EAAA,EAAA,MAAA,oBDxBN,qBCRA,MAAA,QACA,aAAA,QlBlDA,2BkBqDE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,2BAAA,2BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,8BAAA,8BAEE,MAAA,QACA,iBAAA,YAGF,0DAAA,0DpBwsFF,2CoBrsFI,MAAA,QACA,iBAAA,QACA,aAAA,QAEA,gEAAA,gEpBwsFJ,iDoBnsFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDxBN,oBCRA,MAAA,QACA,aAAA,QlBlDA,0BkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,0BAAA,0BAEE,WAAA,EAAA,EAAA,EAAA,MAAA,mBAGF,6BAAA,6BAEE,MAAA,QACA,iBAAA,YAGF,yDAAA,yDpBwuFF,0CoBruFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,+DAAA,+DpBwuFJ,gDoBnuFQ,WAAA,EAAA,EAAA,EAAA,MAAA,mBDxBN,mBCRA,MAAA,QACA,aAAA,QlBlDA,yBkBqDE,MAAA,QACA,iBAAA,QACA,aAAA,QAGF,yBAAA,yBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,qBAGF,4BAAA,4BAEE,MAAA,QACA,iBAAA,YAGF,wDAAA,wDpBwwFF,yCoBrwFI,MAAA,QACA,iBAAA,QACA,aAAA,QAEA,8DAAA,8DpBwwFJ,+CoBnwFQ,WAAA,EAAA,EAAA,EAAA,MAAA,qBDxBN,kBCRA,MAAA,QACA,aAAA,QlBlDA,wBkBqDE,MAAA,KACA,iBAAA,QACA,aAAA,QAGF,wBAAA,wBAEE,WAAA,EAAA,EAAA,EAAA,MAAA,kBAGF,2BAAA,2BAEE,MAAA,QACA,iBAAA,YAGF,uDAAA,uDpBwyFF,wCoBryFI,MAAA,KACA,iBAAA,QACA,aAAA,QAEA,6DAAA,6DpBwyFJ,8CoBnyFQ,WAAA,EAAA,EAAA,EAAA,MAAA,kBDbR,UACE,YAAA,IACA,MAAA,QjBtEA,gBiByEE,MAAA,QACA,gBAAA,UANJ,gBAAA,gBAWI,gBAAA,UACA,WAAA,KAZJ,mBAAA,mBAiBI,MAAA,QACA,eAAA,KAWJ,mBAAA,QCRE,QAAA,MAAA,KACA,UAAA,QACA,YAAA,IAGE,cAAA,MDOJ,mBAAA,QCZE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IAGE,cAAA,MDgBJ,WACE,QAAA,MACA,MAAA,KAFF,sBAMI,WAAA,MnBizFJ,6BADA,4BmB3yFA,6BAII,MAAA,KEvIJ,MLIM,WAAA,QAAA,KAAA,OAKF,kDKTJ,MLUM,WAAA,MKVN,iBAII,QAAA,EAIJ,qBAEI,QAAA,KAIJ,YACE,SAAA,SACA,OAAA,EACA,SAAA,OLbI,WAAA,OAAA,KAAA,KAKF,kDKKJ,YLJM,WAAA,MhB08FN,UACA,UAFA,WsBp9FA,QAIE,SAAA,SCwBE,wBACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAhCJ,WAAA,KAAA,MACA,aAAA,KAAA,MAAA,YACA,cAAA,EACA,YAAA,KAAA,MAAA,YAqDE,8BACE,YAAA,ED5CN,eACE,SAAA,SACA,IAAA,KACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,UAAA,MACA,QAAA,MAAA,EACA,OAAA,QAAA,EAAA,EACA,UAAA,KACA,MAAA,QACA,WAAA,KACA,WAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,gBf1BE,cAAA,OemCA,qBACE,MAAA,EACA,KAAA,KXmBF,yBWrBA,wBACE,MAAA,EACA,KAAA,MXmBF,yBWrBA,wBACE,MAAA,EACA,KAAA,MXmBF,yBWrBA,wBACE,MAAA,EACA,KAAA,MXmBF,0BWrBA,wBACE,MAAA,EACA,KAAA,MASF,oBACE,MAAA,KACA,KAAA,EXQF,yBWVA,uBACE,MAAA,KACA,KAAA,GXQF,yBWVA,uBACE,MAAA,KACA,KAAA,GXQF,yBWVA,uBACE,MAAA,KACA,KAAA,GXQF,0BWVA,uBACE,MAAA,KACA,KAAA,GAON,uBAEI,IAAA,KACA,OAAA,KACA,WAAA,EACA,cAAA,QCnCA,gCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAzBJ,WAAA,EACA,aAAA,KAAA,MAAA,YACA,cAAA,KAAA,MACA,YAAA,KAAA,MAAA,YA8CE,sCACE,YAAA,EDcN,0BAEI,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,YAAA,QCjDA,mCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAlBJ,WAAA,KAAA,MAAA,YACA,aAAA,EACA,cAAA,KAAA,MAAA,YACA,YAAA,KAAA,MAuCE,yCACE,YAAA,EA7BF,mCDuDE,eAAA,EAKN,yBAEI,IAAA,EACA,MAAA,KACA,KAAA,KACA,WAAA,EACA,aAAA,QClEA,kCACE,QAAA,aACA,YAAA,OACA,eAAA,OACA,QAAA,GAJF,kCAgBI,QAAA,KAGF,mCACE,QAAA,aACA,aAAA,OACA,eAAA,OACA,QAAA,GA9BN,WAAA,KAAA,MAAA,YACA,aAAA,KAAA,MACA,cAAA,KAAA,MAAA,YAiCE,wCACE,YAAA,EAVA,mCDqDA,eAAA,EAON,oCAAA,kCAAA,mCAAA,iCAKI,MAAA,KACA,OAAA,KAKJ,kBElHE,OAAA,EACA,OAAA,MAAA,EACA,SAAA,OACA,WAAA,IAAA,MAAA,QFsHF,eACE,QAAA,MACA,MAAA,KACA,QAAA,OAAA,OACA,MAAA,KACA,YAAA,IACA,MAAA,QACA,WAAA,QACA,YAAA,OACA,iBAAA,YACA,OAAA,EAVF,2BfpHI,uBAAA,mBACA,wBAAA,mBemHJ,0BftGI,2BAAA,mBACA,0BAAA,mBLTF,qBAAA,qBoBmIE,MAAA,QACA,gBAAA,KJ9IA,iBAAA,QIwHJ,sBAAA,sBA4BI,MAAA,KACA,gBAAA,KJrJA,iBAAA,QIwHJ,wBAAA,wBAmCI,MAAA,QACA,eAAA,KACA,iBAAA,YAQJ,oBACE,QAAA,MAIF,iBACE,QAAA,MACA,QAAA,MAAA,OACA,cAAA,EACA,UAAA,QACA,MAAA,QACA,YAAA,OAIF,oBACE,QAAA,MACA,QAAA,OAAA,OACA,MAAA,QG1LF,WzB4tGA,oByB1tGE,SAAA,SACA,QAAA,mBAAA,QAAA,YACA,eAAA,OzBguGF,yByBpuGA,gBAOI,SAAA,SACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KzBmuGJ,+BEluGE,sBuBII,QAAA,EzBquGN,gCADA,gCADA,+ByBhvGA,uBAAA,uBAAA,sBAkBM,QAAA,EAMN,aACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,cAAA,MAAA,gBAAA,WAHF,0BAMI,MAAA,KzBsuGJ,wCyBluGA,kCAII,YAAA,KzBmuGJ,4CyBvuGA,uDlBpBI,wBAAA,EACA,2BAAA,EPgwGJ,6CyB7uGA,kClBNI,uBAAA,EACA,0BAAA,EkBoCJ,uBACE,cAAA,SACA,aAAA,SAFF,8BzB0tGA,yCADA,sCyBltGI,YAAA,EAGF,yCACE,aAAA,EAIJ,0CAAA,+BACE,cAAA,QACA,aAAA,QAGF,0CAAA,+BACE,cAAA,OACA,aAAA,OAoBF,oBACE,mBAAA,OAAA,eAAA,OACA,eAAA,MAAA,YAAA,WACA,cAAA,OAAA,gBAAA,OAHF,yBzB4sGA,+ByBrsGI,MAAA,KzB0sGJ,iDyBjtGA,2CAYI,WAAA,KzB0sGJ,qDyBttGA,gElBtFI,2BAAA,EACA,0BAAA,EPizGJ,sDyB5tGA,2ClBpGI,uBAAA,EACA,wBAAA,EkB2IJ,uBzB0rGA,kCyBvrGI,cAAA,EzB4rGJ,4CyB/rGA,yCzBisGA,uDADA,oDyBzrGM,SAAA,SACA,KAAA,cACA,eAAA,KCzJN,aACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,QAAA,YAAA,QACA,MAAA,K1Bg2GF,0BADA,4B0Bp2GA,2B1Bm2GA,qC0Bx1GI,SAAA,SACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KAGA,MAAA,GACA,cAAA,E1Bw2GJ,uCADA,yCADA,wCADA,yCADA,2CADA,0CAJA,wCADA,0C0B92GA,yC1Bk3GA,kDADA,oDADA,mD0B31GM,YAAA,K1By2GN,sEADA,kC0B73GA,iCA6BI,QAAA,EA7BJ,mDAkCI,QAAA,E1Bq2GJ,6C0Bv4GA,4CnBWI,wBAAA,EACA,2BAAA,EPi4GJ,8C0B74GA,6CnByBI,uBAAA,EACA,0BAAA,EmB1BJ,0BA8CI,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OA/CJ,8D1B05GA,qEO/4GI,wBAAA,EACA,2BAAA,EmBZJ,+DnByBI,uBAAA,EACA,0BAAA,EP24GJ,oB0Bv2GA,qBAEE,QAAA,YAAA,QAAA,K1B22GF,yB0B72GA,0BAQI,SAAA,SACA,QAAA,E1B02GJ,+B0Bn3GA,gCAYM,QAAA,E1B+2GN,8BACA,2CAEA,2CADA,wD0B73GA,+B1Bw3GA,4CAEA,4CADA,yD0Br2GI,YAAA,KAIJ,qBAAuB,aAAA,KACvB,oBAAsB,YAAA,KAQtB,kBACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,QAAA,QAAA,OACA,cAAA,EACA,UAAA,KACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,WAAA,OACA,YAAA,OACA,iBAAA,QACA,OAAA,IAAA,MAAA,QnB7GE,cAAA,OP69GJ,uC0B53GA,oCAkBI,WAAA,E1B+2GJ,+B0Br2GA,4CAEE,OAAA,qB1Bw2GF,+B0Br2GA,8B1By2GA,yCAFA,sDACA,0CAFA,uD0Bh2GE,QAAA,MAAA,KACA,UAAA,QACA,YAAA,InB1IE,cAAA,MPm/GJ,+B0Br2GA,4CAEE,OAAA,sB1Bw2GF,+B0Br2GA,8B1By2GA,yCAFA,sDACA,0CAFA,uD0Bh2GE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,InB3JE,cAAA,MmB+JJ,+B1Bq2GA,+B0Bn2GE,cAAA,Q1B22GF,wFACA,+EAHA,uDACA,oE0B/1GA,uC1B61GA,oDO5/GI,wBAAA,EACA,2BAAA,EmBuKJ,sC1B81GA,mDAGA,qEACA,kFAHA,yDACA,sEO1/GI,uBAAA,EACA,0BAAA,EoBvBJ,gBACE,SAAA,SACA,QAAA,MACA,WAAA,OACA,aAAA,OAGF,uBACE,QAAA,mBAAA,QAAA,YACA,aAAA,KAGF,sBACE,SAAA,SACA,QAAA,GACA,QAAA,EAHF,4DAMI,MAAA,KACA,aAAA,QTtBA,iBAAA,QSeJ,0DAiBM,WAAA,EAAA,EAAA,EAAA,MAAA,oBAjBN,wEAsBI,aAAA,QAtBJ,0EA0BI,MAAA,KACA,iBAAA,QACA,aAAA,QA5BJ,qDAkCM,MAAA,QAlCN,6DAqCQ,iBAAA,QAUR,sBACE,SAAA,SACA,cAAA,EACA,eAAA,IAHF,8BAOI,SAAA,SACA,IAAA,OACA,KAAA,QACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,eAAA,KACA,QAAA,GACA,iBAAA,KACA,OAAA,QAAA,MAAA,IAhBJ,6BAsBI,SAAA,SACA,IAAA,OACA,KAAA,QACA,QAAA,MACA,MAAA,KACA,OAAA,KACA,QAAA,GACA,kBAAA,UACA,oBAAA,OAAA,OACA,gBAAA,IAAA,IASJ,+CpBxGI,cAAA,OoBwGJ,4EAOM,iBAAA,4LAPN,mFAaM,aAAA,QTnHF,iBAAA,QSsGJ,kFAkBM,iBAAA,yIAlBN,sFAwBM,iBAAA,mBAxBN,4FA2BM,iBAAA,mBASN,4CAEI,cAAA,IAFJ,yEAOM,iBAAA,sIAPN,mFAaM,iBAAA,mBAUN,eACE,aAAA,QADF,6CAKM,KAAA,SACA,MAAA,QACA,eAAA,IACA,cAAA,MARN,4CAYM,IAAA,mBACA,KAAA,qBACA,MAAA,iBACA,OAAA,iBACA,iBAAA,QACA,cAAA,MXlLA,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,WAAA,CAAA,kBAAA,KAAA,YAAA,WAAA,UAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAAA,WAAA,UAAA,KAAA,WAAA,CAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,WAAA,CAAA,kBAAA,KAAA,YAKF,kDW4JJ,4CX3JM,WAAA,MW2JN,0EAwBM,iBAAA,KACA,kBAAA,mBAAA,UAAA,mBAzBN,oFA+BM,iBAAA,mBAYN,eACE,QAAA,aACA,MAAA,KACA,OAAA,oBACA,QAAA,QAAA,QAAA,QAAA,OACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,eAAA,OACA,WAAA,0JAAA,UAAA,MAAA,OAAA,MAAA,CAAA,IAAA,KACA,iBAAA,KACA,OAAA,IAAA,MAAA,QAEE,cAAA,OAKF,mBAAA,KAAA,gBAAA,KAAA,WAAA,KAlBF,qBAqBI,aAAA,QACA,QAAA,EAIE,WAAA,EAAA,EAAA,EAAA,MAAA,qBA1BN,gCAmCM,MAAA,QACA,iBAAA,KApCN,yBAAA,qCA0CI,OAAA,KACA,cAAA,OACA,iBAAA,KA5CJ,wBAgDI,MAAA,QACA,iBAAA,QAjDJ,2BAsDI,QAAA,EAIJ,kBACE,OAAA,sBACA,YAAA,OACA,eAAA,OACA,aAAA,MACA,UAAA,QAGF,kBACE,OAAA,qBACA,YAAA,MACA,eAAA,MACA,aAAA,KACA,UAAA,QAQF,aACE,SAAA,SACA,QAAA,aACA,MAAA,KACA,OAAA,oBACA,cAAA,EAGF,mBACE,SAAA,SACA,QAAA,EACA,MAAA,KACA,OAAA,oBACA,OAAA,EACA,QAAA,EANF,4CASI,aAAA,QACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBAVJ,+CAcI,iBAAA,QAdJ,sDAmBM,QAAA,SAnBN,0DAwBI,QAAA,kBAIJ,mBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,KAAA,EACA,QAAA,EACA,OAAA,oBACA,QAAA,QAAA,OACA,YAAA,IACA,YAAA,IACA,MAAA,QACA,iBAAA,KACA,OAAA,IAAA,MAAA,QpB7UE,cAAA,OoBiUJ,0BAiBI,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,QAAA,EACA,QAAA,MACA,OAAA,QACA,QAAA,QAAA,OACA,YAAA,IACA,MAAA,QACA,QAAA,ST1VA,iBAAA,QS4VA,YAAA,QpB9VA,cAAA,EAAA,OAAA,OAAA,EoByWJ,cACE,MAAA,KACA,OAAA,mBACA,QAAA,EACA,iBAAA,YACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KALF,oBAQI,QAAA,EARJ,0CAY8B,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAZ9B,sCAa8B,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAb9B,+BAc8B,WAAA,EAAA,EAAA,EAAA,IAAA,IAAA,CAAA,EAAA,EAAA,EAAA,MAAA,oBAd9B,gCAkBI,OAAA,EAlBJ,oCAsBI,MAAA,KACA,OAAA,KACA,WAAA,QT/XA,iBAAA,QSiYA,OAAA,EpBnYA,cAAA,KSEE,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YWqYF,mBAAA,KAAA,WAAA,KXhYA,kDWkWJ,oCXjWM,WAAA,MWiWN,2CTvWI,iBAAA,QSuWJ,6CAsCI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YpBpZA,cAAA,KoByWJ,gCAiDI,MAAA,KACA,OAAA,KTzZA,iBAAA,QS2ZA,OAAA,EpB7ZA,cAAA,KSEE,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YW+ZF,gBAAA,KAAA,WAAA,KX1ZA,kDWkWJ,gCXjWM,WAAA,MWiWN,uCTvWI,iBAAA,QSuWJ,gCAgEI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,QACA,aAAA,YpB9aA,cAAA,KoByWJ,yBA2EI,MAAA,KACA,OAAA,KACA,WAAA,EACA,aAAA,MACA,YAAA,MTtbA,iBAAA,QSwbA,OAAA,EpB1bA,cAAA,KSEE,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YW4bF,WAAA,KXvbA,kDWkWJ,yBXjWM,WAAA,MWiWN,gCTvWI,iBAAA,QSuWJ,yBA6FI,MAAA,KACA,OAAA,MACA,MAAA,YACA,OAAA,QACA,iBAAA,YACA,aAAA,YACA,aAAA,MAnGJ,8BAwGI,iBAAA,QpBjdA,cAAA,KoByWJ,8BA6GI,aAAA,KACA,iBAAA,QpBvdA,cAAA,KoByWJ,6CAoHM,iBAAA,QApHN,sDAwHM,OAAA,QAxHN,yCA4HM,iBAAA,QA5HN,yCAgIM,OAAA,QAhIN,kCAoIM,iBAAA,QAKN,8B3Bk+GA,mBACA,egBn9HM,WAAA,iBAAA,KAAA,WAAA,CAAA,aAAA,KAAA,WAAA,CAAA,WAAA,KAAA,YAKF,kDW2eJ,8B3By+GE,mBACA,egBp9HI,WAAA,MYPN,KACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,aAAA,EACA,cAAA,EACA,WAAA,KAGF,UACE,QAAA,MACA,QAAA,MAAA,K1BCA,gBAAA,gB0BEE,gBAAA,KALJ,mBAUI,MAAA,QACA,eAAA,KACA,OAAA,QAQJ,UACE,cAAA,IAAA,MAAA,QADF,oBAII,cAAA,KAJJ,oBAQI,OAAA,IAAA,MAAA,YrB/BA,uBAAA,OACA,wBAAA,OLKF,0BAAA,0B0B6BI,aAAA,QAAA,QAAA,QAZN,6BAgBM,MAAA,QACA,iBAAA,YACA,aAAA,Y5Bo+HN,mC4Bt/HA,2BAwBI,MAAA,QACA,iBAAA,KACA,aAAA,QAAA,QAAA,KA1BJ,yBA+BI,WAAA,KrBtDA,uBAAA,EACA,wBAAA,EqBgEJ,qBrBvEI,cAAA,OqBuEJ,4B5B69HA,2B4Bt9HI,MAAA,KACA,iBAAA,QASJ,oBAEI,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,WAAA,OAIJ,yBAEI,wBAAA,EAAA,WAAA,EACA,kBAAA,EAAA,UAAA,EACA,WAAA,OASJ,uBAEI,QAAA,KAFJ,qBAKI,QAAA,MCpGJ,QACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,QAAA,gBAAA,cACA,QAAA,MAAA,KANF,mB7BgkIA,yB6BpjII,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,QAAA,gBAAA,cASJ,cACE,QAAA,aACA,YAAA,SACA,eAAA,SACA,aAAA,KACA,UAAA,QACA,YAAA,QACA,YAAA,O3BhCA,oBAAA,oB2BmCE,gBAAA,KASJ,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,aAAA,EACA,cAAA,EACA,WAAA,KALF,sBAQI,cAAA,EACA,aAAA,EATJ,2BAaI,SAAA,OACA,MAAA,KASJ,aACE,QAAA,aACA,YAAA,MACA,eAAA,MAYF,iBACE,wBAAA,KAAA,WAAA,KACA,kBAAA,EAAA,UAAA,EAGA,eAAA,OAAA,YAAA,OAIF,gBACE,QAAA,OAAA,OACA,UAAA,QACA,YAAA,EACA,iBAAA,YACA,OAAA,IAAA,MAAA,YtB5GE,cAAA,OLYF,sBAAA,sB2BoGE,gBAAA,KATJ,8CAcI,OAAA,QAMJ,qBACE,QAAA,aACA,MAAA,MACA,OAAA,MACA,eAAA,OACA,QAAA,GACA,WAAA,UAAA,OAAA,OACA,gBAAA,KAAA,KlB7DE,4BkBuEC,6B7B0hIH,mC6BthIQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7BmjIH,mC6BthIQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,4BkBuEC,6B7BokIH,mC6BhkIQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7B6lIH,mC6BhkIQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,4BkBuEC,6B7B8mIH,mC6B1mIQ,cAAA,EACA,aAAA,GlBzFN,yBkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7BuoIH,mC6B1mIQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MlB/GN,6BkBuEC,6B7BwpIH,mC6BppIQ,cAAA,EACA,aAAA,GlBzFN,0BkBoFA,kBAUI,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAXH,8BAcK,mBAAA,IAAA,eAAA,IAdL,6CAiBO,SAAA,SAjBP,wCAqBO,cAAA,MACA,aAAA,MAtBP,6B7BirIH,mC6BppIQ,cAAA,OAAA,UAAA,OA7BL,mCAiCK,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KApCL,kCAwCK,QAAA,MA7CV,eAeQ,cAAA,IAAA,OAAA,UAAA,IAAA,OACA,cAAA,MAAA,gBAAA,WAhBR,0B7B6sIA,gC6BpsIU,cAAA,EACA,aAAA,EAVV,2BAmBU,mBAAA,IAAA,eAAA,IAnBV,0CAsBY,SAAA,SAtBZ,qCA0BY,cAAA,MACA,aAAA,MA3BZ,0B7BiuIA,gC6B/rIU,cAAA,OAAA,UAAA,OAlCV,gCAsCU,QAAA,sBAAA,QAAA,eAGA,wBAAA,KAAA,WAAA,KAzCV,+BA6CU,QAAA,KAaV,4BAEI,MAAA,e3BvLF,kCAAA,kC2B0LI,MAAA,eALN,oCAWM,MAAA,e3BhMJ,0CAAA,0C2BmMM,MAAA,eAdR,6CAkBQ,MAAA,e7B0rIR,4CAEA,2CADA,yC6B7sIA,0CA0BM,MAAA,eA1BN,8BA+BI,MAAA,eACA,aAAA,eAhCJ,mCAoCI,iBAAA,uOApCJ,2BAwCI,MAAA,eAxCJ,6BA0CM,MAAA,e3B/NJ,mCAAA,mC2BkOM,MAAA,eAOR,2BAEI,MAAA,K3B3OF,iCAAA,iC2B8OI,MAAA,KALN,mCAWM,MAAA,qB3BpPJ,yCAAA,yC2BuPM,MAAA,sBAdR,4CAkBQ,MAAA,sB7BsrIR,2CAEA,0CADA,wC6BzsIA,yCA0BM,MAAA,KA1BN,6BA+BI,MAAA,qBACA,aAAA,qBAhCJ,kCAoCI,iBAAA,6OApCJ,0BAwCI,MAAA,qBAxCJ,4BA0CM,MAAA,K3BnRJ,kCAAA,kC2BsRM,MAAA,KClSR,MACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,UAAA,EACA,UAAA,WACA,iBAAA,KACA,gBAAA,WACA,OAAA,IAAA,MAAA,iBvBRE,cAAA,OuBAJ,SAYI,aAAA,EACA,YAAA,EAbJ,2DvBMI,uBAAA,OACA,wBAAA,OuBPJ,yDvBoBI,2BAAA,OACA,0BAAA,OuBQJ,WAGE,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,QAAA,QAGF,YACE,cAAA,OAGF,eACE,WAAA,SACA,cAAA,EAGF,sBACE,cAAA,E5BtCA,iB4B2CE,gBAAA,KAFJ,sBAMI,YAAA,QAQJ,aACE,QAAA,OAAA,QACA,cAAA,EACA,MAAA,QACA,iBAAA,gBACA,cAAA,IAAA,MAAA,iBALF,yBvB/DI,cAAA,mBAAA,mBAAA,EAAA,EuB+DJ,sDAaM,WAAA,EAKN,aACE,QAAA,OAAA,QACA,iBAAA,gBACA,WAAA,IAAA,MAAA,iBAHF,wBvBjFI,cAAA,EAAA,EAAA,mBAAA,mBuBgGJ,kBACE,aAAA,SACA,cAAA,QACA,YAAA,SACA,cAAA,EAGF,mBACE,aAAA,SACA,YAAA,SAIF,kBACE,SAAA,SACA,IAAA,EACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,QAGF,UACE,MAAA,KvBvHE,cAAA,mBuB4HJ,cACE,MAAA,KvBvHE,uBAAA,mBACA,wBAAA,mBuB0HJ,iBACE,MAAA,KvB9GE,2BAAA,mBACA,0BAAA,mBuBoHJ,WACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAFF,iBAKI,cAAA,KnBtFA,yBmBiFJ,WASI,cAAA,IAAA,KAAA,UAAA,IAAA,KACA,aAAA,MACA,YAAA,MAXJ,iBAcM,QAAA,YAAA,QAAA,KAEA,SAAA,EAAA,EAAA,GAAA,KAAA,EAAA,EAAA,GACA,mBAAA,OAAA,eAAA,OACA,aAAA,KACA,cAAA,EACA,YAAA,MAUN,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAFF,kBAOI,cAAA,KnBtHA,yBmB+GJ,YAWI,cAAA,IAAA,KAAA,UAAA,IAAA,KAXJ,kBAgBM,SAAA,EAAA,EAAA,GAAA,KAAA,EAAA,EAAA,GACA,cAAA,EAjBN,wBAoBQ,YAAA,EACA,YAAA,EArBR,8BvB1JI,wBAAA,EACA,2BAAA,EP+nJF,2C8Bt+IF,4CA+BY,wBAAA,E9B28IV,2C8B1+IF,+CAmCY,2BAAA,EAnCZ,6BvB5II,uBAAA,EACA,0BAAA,EP6nJF,0C8Bl/IF,2CA4CY,uBAAA,E9B08IV,0C8Bt/IF,8CAgDY,0BAAA,EAhDZ,6BvBvKI,cAAA,OPoqJF,0C8B7/IF,2CvBjKI,uBAAA,OACA,wBAAA,OPkqJF,0C8BlgJF,8CvBnJI,2BAAA,OACA,0BAAA,OuBkJJ,sEvBvKI,cAAA,EPmrJF,mFADA,mFADA,uF8B1gJF,oFvBvKI,cAAA,GuB4PJ,oBAEI,cAAA,OnBtMA,yBmBoMJ,cAMI,qBAAA,EAAA,kBAAA,EAAA,aAAA,EACA,mBAAA,QAAA,gBAAA,QAAA,WAAA,QACA,QAAA,EACA,OAAA,EATJ,oBAYM,QAAA,aACA,MAAA,MAUN,iBAEI,SAAA,OAFJ,8DAMQ,cAAA,EANR,wDAUQ,cAAA,EACA,cAAA,EAXR,+BAgBM,cAAA,EACA,2BAAA,EACA,0BAAA,EAlBN,8BAsBM,uBAAA,EACA,wBAAA,EAvBN,8BA2BM,cAAA,KClTN,YACE,QAAA,YAAA,QAAA,KACA,cAAA,KAAA,UAAA,KACA,QAAA,OAAA,KACA,cAAA,KACA,WAAA,KACA,iBAAA,QxBFE,cAAA,OwBMJ,kCAGI,aAAA,MAHJ,0CAMM,QAAA,aACA,cAAA,MACA,MAAA,QACA,QAAA,IATN,gDAoBI,gBAAA,UApBJ,gDAwBI,gBAAA,KAxBJ,wBA4BI,MAAA,QCtCJ,YACE,QAAA,YAAA,QAAA,K5BGA,aAAA,EACA,WAAA,KGDE,cAAA,OyBEJ,WACE,SAAA,SACA,QAAA,MACA,QAAA,MAAA,OACA,YAAA,KACA,YAAA,KACA,MAAA,QACA,iBAAA,KACA,OAAA,IAAA,MAAA,QARF,iBAWI,QAAA,EACA,MAAA,QACA,gBAAA,KACA,iBAAA,QACA,aAAA,QAfJ,iBAmBI,QAAA,EACA,QAAA,EACA,WAAA,EAAA,EAAA,EAAA,MAAA,oBArBJ,yCA0BI,OAAA,QAIJ,kCAGM,YAAA,EzBRF,uBAAA,OACA,0BAAA,OyBIJ,iCzBnBI,wBAAA,OACA,2BAAA,OyBkBJ,6BAcI,QAAA,EACA,MAAA,KACA,iBAAA,QACA,aAAA,QAjBJ,+BAqBI,MAAA,QACA,eAAA,KAEA,OAAA,KACA,iBAAA,KACA,aAAA,QC3DF,0BACE,QAAA,OAAA,OACA,UAAA,QACA,YAAA,IAKE,iD1BoBF,uBAAA,MACA,0BAAA,M0BhBE,gD1BCF,wBAAA,MACA,2BAAA,M0BfF,0BACE,QAAA,OAAA,MACA,UAAA,QACA,YAAA,IAKE,iD1BoBF,uBAAA,MACA,0BAAA,M0BhBE,gD1BCF,wBAAA,MACA,2BAAA,M2BbJ,OACE,QAAA,aACA,QAAA,MAAA,KACA,UAAA,IACA,YAAA,IACA,YAAA,EACA,WAAA,OACA,YAAA,OACA,eAAA,S3BTE,cAAA,OLYF,cAAA,cgCEI,gBAAA,KAbN,aAmBI,QAAA,KAKJ,YACE,SAAA,SACA,IAAA,KAOF,YACE,cAAA,KACA,aAAA,K3BpCE,cAAA,M2B6CF,eChDA,MAAA,KACA,iBAAA,QjCcA,sBAAA,sBiCVI,MAAA,KACA,iBAAA,QD0CJ,iBChDA,MAAA,KACA,iBAAA,QjCcA,wBAAA,wBiCVI,MAAA,KACA,iBAAA,QD0CJ,eChDA,MAAA,KACA,iBAAA,QjCcA,sBAAA,sBiCVI,MAAA,KACA,iBAAA,QD0CJ,YChDA,MAAA,KACA,iBAAA,QjCcA,mBAAA,mBiCVI,MAAA,KACA,iBAAA,QD0CJ,eChDA,MAAA,QACA,iBAAA,QjCcA,sBAAA,sBiCVI,MAAA,QACA,iBAAA,QD0CJ,cChDA,MAAA,KACA,iBAAA,QjCcA,qBAAA,qBiCVI,MAAA,KACA,iBAAA,QD0CJ,aChDA,MAAA,QACA,iBAAA,QjCcA,oBAAA,oBiCVI,MAAA,QACA,iBAAA,QD0CJ,YChDA,MAAA,KACA,iBAAA,QjCcA,mBAAA,mBiCVI,MAAA,KACA,iBAAA,QCPN,WACE,QAAA,KAAA,KACA,cAAA,KACA,iBAAA,Q7BCE,cAAA,MIwDA,yByB5DJ,WAOI,QAAA,KAAA,MAIJ,iBACE,cAAA,EACA,aAAA,E7BTE,cAAA,E8BAJ,OACE,SAAA,SACA,QAAA,OAAA,QACA,cAAA,KACA,OAAA,IAAA,MAAA,Y9BJE,cAAA,O8BSJ,eAEE,MAAA,QAIF,YACE,YAAA,IAQF,mBACE,cAAA,KADF,0BAKI,SAAA,SACA,IAAA,EACA,MAAA,EACA,QAAA,OAAA,QACA,MAAA,QAUF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,iBC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,oBACE,iBAAA,QAGF,6BACE,MAAA,QDqCF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,YC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,eACE,iBAAA,QAGF,wBACE,MAAA,QDqCF,eC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,kBACE,iBAAA,QAGF,2BACE,MAAA,QDqCF,cC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,iBACE,iBAAA,QAGF,0BACE,MAAA,QDqCF,aC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,gBACE,iBAAA,QAGF,yBACE,MAAA,QDqCF,YC9CA,MAAA,QpBKE,iBAAA,QoBHF,aAAA,QAEA,eACE,iBAAA,QAGF,wBACE,MAAA,QCVJ,wCACE,KAAO,oBAAA,KAAA,EACP,GAAK,oBAAA,EAAA,GAFP,gCACE,KAAO,oBAAA,KAAA,EACP,GAAK,oBAAA,EAAA,GAGP,UACE,QAAA,YAAA,QAAA,KACA,OAAA,KACA,SAAA,OACA,UAAA,OACA,iBAAA,QhCNE,cAAA,OgCWJ,cACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,KACA,WAAA,OACA,YAAA,OACA,iBAAA,QvBhBI,WAAA,MAAA,IAAA,KAKF,kDuBIJ,cvBHM,WAAA,MuBcN,sBrBiBE,iBAAA,iKqBfA,gBAAA,KAAA,KAGF,uBACE,kBAAA,qBAAA,GAAA,OAAA,SAAA,UAAA,qBAAA,GAAA,OAAA,SChCF,OACE,QAAA,YAAA,QAAA,KACA,eAAA,MAAA,YAAA,WAGF,YACE,SAAA,EAAA,KAAA,ECFF,YACE,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OAGA,aAAA,EACA,cAAA,EASF,wBACE,MAAA,KACA,MAAA,QACA,WAAA,QvCNA,8BAAA,8BuCUE,MAAA,QACA,gBAAA,KACA,iBAAA,QATJ,+BAaI,MAAA,QACA,iBAAA,QASJ,iBACE,SAAA,SACA,QAAA,MACA,QAAA,OAAA,QAEA,cAAA,KACA,iBAAA,KACA,OAAA,IAAA,MAAA,iBAPF,6BlChCI,uBAAA,OACA,wBAAA,OkC+BJ,4BAcI,cAAA,ElChCA,2BAAA,OACA,0BAAA,OLTF,uBAAA,uBuC6CE,QAAA,EACA,gBAAA,KApBJ,0BAAA,0BAyBI,MAAA,QACA,eAAA,KACA,iBAAA,KA3BJ,wBAgCI,QAAA,EACA,MAAA,KACA,iBAAA,QACA,aAAA,QAUJ,mCAEI,aAAA,EACA,YAAA,ElCtFA,cAAA,EkCmFJ,8CAOM,cAAA,KAPN,2DAaM,WAAA,EAbN,yDAmBM,cAAA,EACA,cAAA,ECxGJ,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,2BACE,MAAA,QACA,iBAAA,QxCWF,wDAAA,wDwCPM,MAAA,QACA,iBAAA,QAPN,yDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,sBACE,MAAA,QACA,iBAAA,QxCWF,mDAAA,mDwCPM,MAAA,QACA,iBAAA,QAPN,oDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,yBACE,MAAA,QACA,iBAAA,QxCWF,sDAAA,sDwCPM,MAAA,QACA,iBAAA,QAPN,uDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,wBACE,MAAA,QACA,iBAAA,QxCWF,qDAAA,qDwCPM,MAAA,QACA,iBAAA,QAPN,sDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,uBACE,MAAA,QACA,iBAAA,QxCWF,oDAAA,oDwCPM,MAAA,QACA,iBAAA,QAPN,qDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QAbN,sBACE,MAAA,QACA,iBAAA,QxCWF,mDAAA,mDwCPM,MAAA,QACA,iBAAA,QAPN,oDAWM,MAAA,KACA,iBAAA,QACA,aAAA,QChBR,OACE,MAAA,MACA,UAAA,OACA,YAAA,IACA,YAAA,EACA,MAAA,KACA,YAAA,EAAA,IAAA,EAAA,KACA,QAAA,GzCKA,ayCDE,MAAA,KACA,gBAAA,KAZJ,qCAqBI,OAAA,QzCLF,2CAAA,2CyCCI,QAAA,IAcN,aACE,QAAA,EACA,iBAAA,YACA,OAAA,EACA,mBAAA,KAAA,gBAAA,KAAA,WAAA,KAMF,iBACE,eAAA,KC1CF,OACE,UAAA,MACA,SAAA,OACA,UAAA,QACA,iBAAA,sBACA,gBAAA,YACA,OAAA,IAAA,MAAA,eACA,cAAA,OACA,WAAA,EAAA,OAAA,OAAA,eACA,wBAAA,WAAA,gBAAA,WACA,QAAA,EAVF,wBAaI,cAAA,OAbJ,eAiBI,QAAA,EAjBJ,YAqBI,QAAA,MACA,QAAA,EAtBJ,YA0BI,QAAA,KAIJ,cACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,QAAA,OAAA,OACA,MAAA,QACA,iBAAA,sBACA,gBAAA,YACA,cAAA,IAAA,MAAA,gBAGF,YACE,QAAA,OCnCF,YAEE,SAAA,OAFF,mBAKI,WAAA,OACA,WAAA,KAKJ,OACE,SAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,KACA,MAAA,KACA,OAAA,KACA,SAAA,OAGA,QAAA,EAOF,cACE,SAAA,SACA,MAAA,KACA,OAAA,MAEA,eAAA,KAGA,0B7BrCI,WAAA,kBAAA,IAAA,SAAA,WAAA,UAAA,IAAA,SAAA,WAAA,UAAA,IAAA,QAAA,CAAA,kBAAA,IAAA,S6BuCF,kBAAA,mBAAA,UAAA,mB7BlCA,kD6BgCF,0B7B/BI,WAAA,M6BmCJ,0BACE,kBAAA,KAAA,UAAA,KAIJ,uBACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,WAAA,yBAHF,+BAOI,QAAA,MACA,OAAA,0BACA,QAAA,GAKJ,eACE,SAAA,SACA,QAAA,YAAA,QAAA,KACA,mBAAA,OAAA,eAAA,OACA,MAAA,KAEA,eAAA,KACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,etCvEE,cAAA,MsC2EF,QAAA,EAIF,gBACE,SAAA,MACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,MAAA,MACA,OAAA,MACA,iBAAA,KAPF,qBAUW,QAAA,EAVX,qBAWW,QAAA,GAKX,cACE,QAAA,YAAA,QAAA,KACA,eAAA,MAAA,YAAA,WACA,cAAA,QAAA,gBAAA,cACA,QAAA,KAAA,KACA,cAAA,IAAA,MAAA,QtC9FE,uBAAA,MACA,wBAAA,MsCwFJ,qBASI,QAAA,KAAA,KAEA,OAAA,MAAA,MAAA,MAAA,KAKJ,aACE,cAAA,EACA,YAAA,IAKF,YACE,SAAA,SAGA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,QAAA,KAIF,cACE,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,IAAA,gBAAA,SACA,QAAA,KACA,WAAA,IAAA,MAAA,QtChHE,2BAAA,MACA,0BAAA,MsC0GJ,iCASyB,YAAA,OATzB,gCAUwB,aAAA,OAIxB,yBACE,SAAA,SACA,IAAA,QACA,MAAA,KACA,OAAA,KACA,SAAA,OlC1FE,yBkCzBJ,cA0HI,UAAA,MACA,OAAA,QAAA,KA1GJ,uBA8GI,WAAA,2BA9GJ,+BAiHM,OAAA,4BAQJ,UAAY,UAAA,OlCjHV,yBkCqHF,U7Cm+KA,U6Cj+KE,UAAA,OlCvHA,0BkC4HF,UAAY,UAAA,QCvLd,SACE,SAAA,SACA,QAAA,KACA,QAAA,MACA,OAAA,ECJA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KDNA,UAAA,QAEA,UAAA,WACA,QAAA,EAXF,cAaW,QAAA,GAbX,gBAgBI,SAAA,SACA,QAAA,MACA,MAAA,MACA,OAAA,MAnBJ,wBAsBM,SAAA,SACA,QAAA,GACA,aAAA,YACA,aAAA,MAKN,mCAAA,gBACE,QAAA,MAAA,EADF,0CAAA,uBAII,OAAA,EAJJ,kDAAA,+BAOM,IAAA,EACA,aAAA,MAAA,MAAA,EACA,iBAAA,KAKN,qCAAA,kBACE,QAAA,EAAA,MADF,4CAAA,yBAII,KAAA,EACA,MAAA,MACA,OAAA,MANJ,oDAAA,iCASM,MAAA,EACA,aAAA,MAAA,MAAA,MAAA,EACA,mBAAA,KAKN,sCAAA,mBACE,QAAA,MAAA,EADF,6CAAA,0BAII,IAAA,EAJJ,qDAAA,kCAOM,OAAA,EACA,aAAA,EAAA,MAAA,MACA,oBAAA,KAKN,oCAAA,iBACE,QAAA,EAAA,MADF,2CAAA,wBAII,MAAA,EACA,MAAA,MACA,OAAA,MANJ,mDAAA,gCASM,KAAA,EACA,aAAA,MAAA,EAAA,MAAA,MACA,kBAAA,KAqBN,eACE,UAAA,MACA,QAAA,OAAA,MACA,MAAA,KACA,WAAA,OACA,iBAAA,KvC5GE,cAAA,OyCJJ,SACE,SAAA,SACA,IAAA,EACA,KAAA,EACA,QAAA,KACA,QAAA,MACA,UAAA,MDLA,YAAA,aAAA,CAAA,kBAAA,CAAA,UAAA,CAAA,MAAA,CAAA,gBAAA,CAAA,KAAA,CAAA,WAAA,CAAA,UAAA,CAAA,mBAAA,CAAA,gBAAA,CAAA,iBAAA,CAAA,mBAEA,WAAA,OACA,YAAA,IACA,YAAA,IACA,WAAA,KACA,WAAA,MACA,gBAAA,KACA,YAAA,KACA,eAAA,KACA,eAAA,OACA,WAAA,OACA,aAAA,OACA,YAAA,OACA,WAAA,KCLA,UAAA,QAEA,UAAA,WACA,iBAAA,KACA,gBAAA,YACA,OAAA,IAAA,MAAA,ezCXE,cAAA,MyCJJ,gBAoBI,SAAA,SACA,QAAA,MACA,MAAA,KACA,OAAA,MACA,OAAA,EAAA,MAxBJ,uBAAA,wBA4BM,SAAA,SACA,QAAA,MACA,QAAA,GACA,aAAA,YACA,aAAA,MAKN,mCAAA,gBACE,cAAA,MADF,0CAAA,uBAII,OAAA,yBhD4xLJ,iDgDhyLA,kDhD+xLA,8BgD/xLA,+BASI,aAAA,MAAA,MAAA,EATJ,kDAAA,+BAaI,OAAA,EACA,iBAAA,gBhD6xLJ,iDgD3yLA,8BAkBI,OAAA,IACA,iBAAA,KAIJ,qCAAA,kBACE,YAAA,MADF,4CAAA,yBAII,KAAA,yBACA,MAAA,MACA,OAAA,KACA,OAAA,MAAA,EhD+xLJ,mDgDtyLA,oDhDqyLA,gCgDryLA,iCAYI,aAAA,MAAA,MAAA,MAAA,EAZJ,oDAAA,iCAgBI,KAAA,EACA,mBAAA,gBhDgyLJ,mDgDjzLA,gCAqBI,KAAA,IACA,mBAAA,KAIJ,sCAAA,mBACE,WAAA,MADF,6CAAA,0BAII,IAAA,yBhDkyLJ,oDgDtyLA,qDhDqyLA,iCgDryLA,kCASI,aAAA,EAAA,MAAA,MAAA,MATJ,qDAAA,kCAaI,IAAA,EACA,oBAAA,gBhDmyLJ,oDgDjzLA,iCAkBI,IAAA,IACA,oBAAA,KAnBJ,8DAAA,2CAwBI,SAAA,SACA,IAAA,EACA,KAAA,IACA,QAAA,MACA,MAAA,KACA,YAAA,OACA,QAAA,GACA,cAAA,IAAA,MAAA,QAIJ,oCAAA,iBACE,aAAA,MADF,2CAAA,wBAII,MAAA,yBACA,MAAA,MACA,OAAA,KACA,OAAA,MAAA,EhDoyLJ,kDgD3yLA,mDhD0yLA,+BgD1yLA,gCAYI,aAAA,MAAA,EAAA,MAAA,MAZJ,mDAAA,gCAgBI,MAAA,EACA,kBAAA,gBhDqyLJ,kDgDtzLA,+BAqBI,MAAA,IACA,kBAAA,KAqBJ,gBACE,QAAA,MAAA,OACA,cAAA,EACA,UAAA,KACA,MAAA,QACA,iBAAA,QACA,cAAA,IAAA,MAAA,QzChKE,uBAAA,kBACA,wBAAA,kByCyJJ,sBAWI,QAAA,KAIJ,cACE,QAAA,MAAA,OACA,MAAA,QCxKF,UACE,SAAA,SAGF,wBACE,iBAAA,MAAA,aAAA,MAGF,gBACE,SAAA,SACA,MAAA,KACA,SAAA,OCvBA,uBACE,QAAA,MACA,MAAA,KACA,QAAA,GDwBJ,eACE,SAAA,SACA,QAAA,KACA,MAAA,KACA,MAAA,KACA,aAAA,MACA,4BAAA,OAAA,oBAAA,OjC5BI,WAAA,kBAAA,IAAA,YAAA,WAAA,UAAA,IAAA,YAAA,WAAA,UAAA,IAAA,WAAA,CAAA,kBAAA,IAAA,YAKF,kDiCiBJ,ejChBM,WAAA,MhBq+LN,oBACA,oBiD58LA,sBAGE,QAAA,MjD88LF,4BiD38LA,6CAEE,kBAAA,iBAAA,UAAA,iBjD+8LF,2BiD58LA,8CAEE,kBAAA,kBAAA,UAAA,kBAQF,8BAEI,QAAA,EACA,oBAAA,QACA,kBAAA,KAAA,UAAA,KjD28LJ,sDACA,uDiDh9LA,qCAUI,QAAA,EACA,QAAA,EAXJ,0CjDs9LA,2CiDt8LI,QAAA,EACA,QAAA,EjCtEE,WAAA,GAAA,IAAA,QAKF,kDiCgDJ,0CjD89LE,2CgB7gMI,WAAA,MhBmhMN,uBiDz8LA,uBAEE,SAAA,SACA,IAAA,EACA,OAAA,EACA,QAAA,EAEA,QAAA,YAAA,QAAA,KACA,eAAA,OAAA,YAAA,OACA,cAAA,OAAA,gBAAA,OACA,MAAA,IACA,MAAA,KACA,WAAA,OACA,QAAA,GjC7FI,WAAA,QAAA,KAAA,KAKF,kDhBwiMF,uBiD79LF,uBjC1EM,WAAA,MhB8iMN,6BADA,6BEziME,6BAAA,6B+CwFE,MAAA,KACA,gBAAA,KACA,QAAA,EACA,QAAA,GAGJ,uBACE,KAAA,EAKF,uBACE,MAAA,EjDq9LF,4BiD98LA,4BAEE,QAAA,aACA,MAAA,KACA,OAAA,KACA,WAAA,YAAA,UAAA,OAAA,OACA,gBAAA,KAAA,KAEF,4BACE,iBAAA,kLAEF,4BACE,iBAAA,kLASF,qBACE,SAAA,SACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,GACA,QAAA,YAAA,QAAA,KACA,cAAA,OAAA,gBAAA,OACA,aAAA,EAEA,aAAA,IACA,YAAA,IACA,WAAA,KAZF,wBAeI,WAAA,YACA,SAAA,EAAA,EAAA,KAAA,KAAA,EAAA,EAAA,KACA,MAAA,KACA,OAAA,IACA,aAAA,IACA,YAAA,IACA,YAAA,OACA,OAAA,QACA,iBAAA,KACA,gBAAA,YAEA,WAAA,KAAA,MAAA,YACA,cAAA,KAAA,MAAA,YACA,QAAA,GjCvKE,WAAA,QAAA,IAAA,KAKF,kDiCsIJ,wBjCrIM,WAAA,MiCqIN,6BAiCI,QAAA,EASJ,kBACE,SAAA,SACA,MAAA,IACA,OAAA,KACA,KAAA,IACA,QAAA,GACA,YAAA,KACA,eAAA,KACA,MAAA,KACA,WAAA,OEhMF,kCACE,GAAK,kBAAA,eAAA,UAAA,gBADP,0BACE,GAAK,kBAAA,eAAA,UAAA,gBAGP,gBACE,QAAA,aACA,MAAA,KACA,OAAA,KACA,eAAA,YACA,OAAA,MAAA,MAAA,aACA,mBAAA,YACA,cAAA,IACA,kBAAA,eAAA,KAAA,OAAA,SAAA,UAAA,eAAA,KAAA,OAAA,SAGF,mBACE,MAAA,KACA,OAAA,KACA,aAAA,KAOF,gCACE,GACE,kBAAA,SAAA,UAAA,SAEF,IACE,QAAA,GALJ,wBACE,GACE,kBAAA,SAAA,UAAA,SAEF,IACE,QAAA,GAIJ,cACE,QAAA,aACA,MAAA,KACA,OAAA,KACA,eAAA,YACA,iBAAA,aACA,cAAA,IACA,QAAA,EACA,kBAAA,aAAA,KAAA,OAAA,SAAA,UAAA,aAAA,KAAA,OAAA,SAGF,iBACE,MAAA,KACA,OAAA,KCjDF,gBAAqB,eAAA,mBACrB,WAAqB,eAAA,cACrB,cAAqB,eAAA,iBACrB,cAAqB,eAAA,iBACrB,mBAAqB,eAAA,sBACrB,gBAAqB,eAAA,mBCFnB,YACE,iBAAA,kBnDUF,mBAAA,mBFquMF,wBADA,wBqDzuMM,iBAAA,kBANJ,cACE,iBAAA,kBnDUF,qBAAA,qBF+uMF,0BADA,0BqDnvMM,iBAAA,kBANJ,YACE,iBAAA,kBnDUF,mBAAA,mBFyvMF,wBADA,wBqD7vMM,iBAAA,kBANJ,SACE,iBAAA,kBnDUF,gBAAA,gBFmwMF,qBADA,qBqDvwMM,iBAAA,kBANJ,YACE,iBAAA,kBnDUF,mBAAA,mBF6wMF,wBADA,wBqDjxMM,iBAAA,kBANJ,WACE,iBAAA,kBnDUF,kBAAA,kBFuxMF,uBADA,uBqD3xMM,iBAAA,kBANJ,UACE,iBAAA,kBnDUF,iBAAA,iBFiyMF,sBADA,sBqDryMM,iBAAA,kBANJ,SACE,iBAAA,kBnDUF,gBAAA,gBF2yMF,qBADA,qBqD/yMM,iBAAA,kBCCN,UACE,iBAAA,eAGF,gBACE,iBAAA,sBCXF,QAAkB,OAAA,IAAA,MAAA,kBAClB,YAAkB,WAAA,IAAA,MAAA,kBAClB,cAAkB,aAAA,IAAA,MAAA,kBAClB,eAAkB,cAAA,IAAA,MAAA,kBAClB,aAAkB,YAAA,IAAA,MAAA,kBAElB,UAAmB,OAAA,YACnB,cAAmB,WAAA,YACnB,gBAAmB,aAAA,YACnB,iBAAmB,cAAA,YACnB,eAAmB,YAAA,YAGjB,gBACE,aAAA,kBADF,kBACE,aAAA,kBADF,gBACE,aAAA,kBADF,aACE,aAAA,kBADF,gBACE,aAAA,kBADF,eACE,aAAA,kBADF,cACE,aAAA,kBADF,aACE,aAAA,kBAIJ,cACE,aAAA,eAOF,SACE,cAAA,iBAEF,aACE,uBAAA,iBACA,wBAAA,iBAEF,eACE,wBAAA,iBACA,2BAAA,iBAEF,gBACE,2BAAA,iBACA,0BAAA,iBAEF,cACE,uBAAA,iBACA,0BAAA,iBAGF,gBACE,cAAA,cAGF,cACE,cAAA,gBAGF,WACE,cAAA,YL5DA,iBACE,QAAA,MACA,MAAA,KACA,QAAA,GMMA,QAA2B,QAAA,eAC3B,UAA2B,QAAA,iBAC3B,gBAA2B,QAAA,uBAC3B,SAA2B,QAAA,gBAC3B,SAA2B,QAAA,gBAC3B,aAA2B,QAAA,oBAC3B,cAA2B,QAAA,qBAC3B,QAA2B,QAAA,sBAAA,QAAA,eAC3B,eAA2B,QAAA,6BAAA,QAAA,sB7C0C3B,yB6ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB7C0C3B,yB6ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB7C0C3B,yB6ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uB7C0C3B,0B6ClDA,WAA2B,QAAA,eAC3B,aAA2B,QAAA,iBAC3B,mBAA2B,QAAA,uBAC3B,YAA2B,QAAA,gBAC3B,YAA2B,QAAA,gBAC3B,gBAA2B,QAAA,oBAC3B,iBAA2B,QAAA,qBAC3B,WAA2B,QAAA,sBAAA,QAAA,eAC3B,kBAA2B,QAAA,6BAAA,QAAA,uBAS/B,aACE,cAAwB,QAAA,eACxB,gBAAwB,QAAA,iBACxB,sBAAwB,QAAA,uBACxB,eAAwB,QAAA,gBACxB,eAAwB,QAAA,gBACxB,mBAAwB,QAAA,oBACxB,oBAAwB,QAAA,qBACxB,cAAwB,QAAA,sBAAA,QAAA,eACxB,qBAAwB,QAAA,6BAAA,QAAA,uBClC1B,kBACE,SAAA,SACA,QAAA,MACA,MAAA,KACA,QAAA,EACA,SAAA,OALF,0BAQI,QAAA,MACA,QAAA,GATJ,yCzDgpNA,wBADA,yBAEA,yBACA,wByDjoNI,SAAA,SACA,IAAA,EACA,OAAA,EACA,KAAA,EACA,MAAA,KACA,OAAA,KACA,OAAA,EAQF,gCAEI,YAAA,WAFJ,gCAEI,YAAA,OAFJ,+BAEI,YAAA,YAFJ,+BAEI,YAAA,KCzBF,UAAgC,mBAAA,cAAA,eAAA,cAChC,aAAgC,mBAAA,iBAAA,eAAA,iBAChC,kBAAgC,mBAAA,sBAAA,eAAA,sBAChC,qBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,WAA8B,cAAA,eAAA,UAAA,eAC9B,aAA8B,cAAA,iBAAA,UAAA,iBAC9B,mBAA8B,cAAA,uBAAA,UAAA,uBAC9B,WAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,aAA8B,kBAAA,YAAA,UAAA,YAC9B,aAA8B,kBAAA,YAAA,UAAA,YAC9B,eAA8B,kBAAA,YAAA,YAAA,YAC9B,eAA8B,kBAAA,YAAA,YAAA,YAE9B,uBAAoC,cAAA,gBAAA,gBAAA,qBACpC,qBAAoC,cAAA,cAAA,gBAAA,mBACpC,wBAAoC,cAAA,iBAAA,gBAAA,iBACpC,yBAAoC,cAAA,kBAAA,gBAAA,wBACpC,wBAAoC,cAAA,qBAAA,gBAAA,uBAEpC,mBAAiC,eAAA,gBAAA,YAAA,qBACjC,iBAAiC,eAAA,cAAA,YAAA,mBACjC,oBAAiC,eAAA,iBAAA,YAAA,iBACjC,sBAAiC,eAAA,mBAAA,YAAA,mBACjC,qBAAiC,eAAA,kBAAA,YAAA,kBAEjC,qBAAkC,mBAAA,gBAAA,cAAA,qBAClC,mBAAkC,mBAAA,cAAA,cAAA,mBAClC,sBAAkC,mBAAA,iBAAA,cAAA,iBAClC,uBAAkC,mBAAA,kBAAA,cAAA,wBAClC,sBAAkC,mBAAA,qBAAA,cAAA,uBAClC,uBAAkC,mBAAA,kBAAA,cAAA,kBAElC,iBAAgC,oBAAA,eAAA,WAAA,eAChC,kBAAgC,oBAAA,gBAAA,WAAA,qBAChC,gBAAgC,oBAAA,cAAA,WAAA,mBAChC,mBAAgC,oBAAA,iBAAA,WAAA,iBAChC,qBAAgC,oBAAA,mBAAA,WAAA,mBAChC,oBAAgC,oBAAA,kBAAA,WAAA,kB/CYhC,yB+ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB/CYhC,yB+ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB/CYhC,yB+ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mB/CYhC,0B+ClDA,aAAgC,mBAAA,cAAA,eAAA,cAChC,gBAAgC,mBAAA,iBAAA,eAAA,iBAChC,qBAAgC,mBAAA,sBAAA,eAAA,sBAChC,wBAAgC,mBAAA,yBAAA,eAAA,yBAEhC,cAA8B,cAAA,eAAA,UAAA,eAC9B,gBAA8B,cAAA,iBAAA,UAAA,iBAC9B,sBAA8B,cAAA,uBAAA,UAAA,uBAC9B,cAA8B,SAAA,EAAA,EAAA,eAAA,KAAA,EAAA,EAAA,eAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,gBAA8B,kBAAA,YAAA,UAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAC9B,kBAA8B,kBAAA,YAAA,YAAA,YAE9B,0BAAoC,cAAA,gBAAA,gBAAA,qBACpC,wBAAoC,cAAA,cAAA,gBAAA,mBACpC,2BAAoC,cAAA,iBAAA,gBAAA,iBACpC,4BAAoC,cAAA,kBAAA,gBAAA,wBACpC,2BAAoC,cAAA,qBAAA,gBAAA,uBAEpC,sBAAiC,eAAA,gBAAA,YAAA,qBACjC,oBAAiC,eAAA,cAAA,YAAA,mBACjC,uBAAiC,eAAA,iBAAA,YAAA,iBACjC,yBAAiC,eAAA,mBAAA,YAAA,mBACjC,wBAAiC,eAAA,kBAAA,YAAA,kBAEjC,wBAAkC,mBAAA,gBAAA,cAAA,qBAClC,sBAAkC,mBAAA,cAAA,cAAA,mBAClC,yBAAkC,mBAAA,iBAAA,cAAA,iBAClC,0BAAkC,mBAAA,kBAAA,cAAA,wBAClC,yBAAkC,mBAAA,qBAAA,cAAA,uBAClC,0BAAkC,mBAAA,kBAAA,cAAA,kBAElC,oBAAgC,oBAAA,eAAA,WAAA,eAChC,qBAAgC,oBAAA,gBAAA,WAAA,qBAChC,mBAAgC,oBAAA,cAAA,WAAA,mBAChC,sBAAgC,oBAAA,iBAAA,WAAA,iBAChC,wBAAgC,oBAAA,mBAAA,WAAA,mBAChC,uBAAgC,oBAAA,kBAAA,WAAA,mBC5ChC,YCDF,MAAA,eDEE,aCCF,MAAA,gBDAE,YCGF,MAAA,ejDmDE,yBgDxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gBjDmDE,yBgDxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gBjDmDE,yBgDxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gBjDmDE,0BgDxDA,eCDF,MAAA,eDEE,gBCCF,MAAA,gBDAE,eCGF,MAAA,gBCNA,eAAsB,SAAA,eAAtB,iBAAsB,SAAA,iBCCtB,iBAAyB,SAAA,iBAAzB,mBAAyB,SAAA,mBAAzB,mBAAyB,SAAA,mBAAzB,gBAAyB,SAAA,gBAAzB,iBAAyB,SAAA,yBAAA,SAAA,iBAK3B,WACE,SAAA,MACA,IAAA,EACA,MAAA,EACA,KAAA,EACA,QAAA,KAGF,cACE,SAAA,MACA,MAAA,EACA,OAAA,EACA,KAAA,EACA,QAAA,KAI4B,2DAD9B,YAEI,SAAA,eAAA,SAAA,OACA,IAAA,EACA,QAAA,MCzBJ,SCEE,SAAA,SACA,MAAA,IACA,OAAA,IACA,QAAA,EACA,SAAA,OACA,KAAA,cACA,YAAA,OACA,OAAA,EAUA,0BAAA,yBAEE,SAAA,OACA,MAAA,KACA,OAAA,KACA,SAAA,QACA,KAAA,KACA,YAAA,OC5BJ,WAAa,WAAA,EAAA,QAAA,OAAA,2BACb,QAAU,WAAA,EAAA,MAAA,KAAA,0BACV,WAAa,WAAA,EAAA,KAAA,KAAA,2BACb,aAAe,WAAA,eCCX,MAAuB,MAAA,cAAvB,MAAuB,MAAA,cAAvB,MAAuB,MAAA,cAAvB,OAAuB,MAAA,eAAvB,QAAuB,MAAA,eAAvB,MAAuB,OAAA,cAAvB,MAAuB,OAAA,cAAvB,MAAuB,OAAA,cAAvB,OAAuB,OAAA,eAAvB,QAAuB,OAAA,eAI3B,QAAU,UAAA,eACV,QAAU,WAAA,eAIV,YAAc,UAAA,gBACd,YAAc,WAAA,gBAEd,QAAU,MAAA,gBACV,QAAU,OAAA,gBCTF,KAAgC,OAAA,YAChC,MnEolPR,MmEllPU,WAAA,YAEF,MnEqlPR,MmEnlPU,aAAA,YAEF,MnEslPR,MmEplPU,cAAA,YAEF,MnEulPR,MmErlPU,YAAA,YAfF,KAAgC,OAAA,iBAChC,MnE4mPR,MmE1mPU,WAAA,iBAEF,MnE6mPR,MmE3mPU,aAAA,iBAEF,MnE8mPR,MmE5mPU,cAAA,iBAEF,MnE+mPR,MmE7mPU,YAAA,iBAfF,KAAgC,OAAA,gBAChC,MnEooPR,MmEloPU,WAAA,gBAEF,MnEqoPR,MmEnoPU,aAAA,gBAEF,MnEsoPR,MmEpoPU,cAAA,gBAEF,MnEuoPR,MmEroPU,YAAA,gBAfF,KAAgC,OAAA,eAChC,MnE4pPR,MmE1pPU,WAAA,eAEF,MnE6pPR,MmE3pPU,aAAA,eAEF,MnE8pPR,MmE5pPU,cAAA,eAEF,MnE+pPR,MmE7pPU,YAAA,eAfF,KAAgC,OAAA,iBAChC,MnEorPR,MmElrPU,WAAA,iBAEF,MnEqrPR,MmEnrPU,aAAA,iBAEF,MnEsrPR,MmEprPU,cAAA,iBAEF,MnEurPR,MmErrPU,YAAA,iBAfF,KAAgC,OAAA,eAChC,MnE4sPR,MmE1sPU,WAAA,eAEF,MnE6sPR,MmE3sPU,aAAA,eAEF,MnE8sPR,MmE5sPU,cAAA,eAEF,MnE+sPR,MmE7sPU,YAAA,eAfF,KAAgC,QAAA,YAChC,MnEouPR,MmEluPU,YAAA,YAEF,MnEquPR,MmEnuPU,cAAA,YAEF,MnEsuPR,MmEpuPU,eAAA,YAEF,MnEuuPR,MmEruPU,aAAA,YAfF,KAAgC,QAAA,iBAChC,MnE4vPR,MmE1vPU,YAAA,iBAEF,MnE6vPR,MmE3vPU,cAAA,iBAEF,MnE8vPR,MmE5vPU,eAAA,iBAEF,MnE+vPR,MmE7vPU,aAAA,iBAfF,KAAgC,QAAA,gBAChC,MnEoxPR,MmElxPU,YAAA,gBAEF,MnEqxPR,MmEnxPU,cAAA,gBAEF,MnEsxPR,MmEpxPU,eAAA,gBAEF,MnEuxPR,MmErxPU,aAAA,gBAfF,KAAgC,QAAA,eAChC,MnE4yPR,MmE1yPU,YAAA,eAEF,MnE6yPR,MmE3yPU,cAAA,eAEF,MnE8yPR,MmE5yPU,eAAA,eAEF,MnE+yPR,MmE7yPU,aAAA,eAfF,KAAgC,QAAA,iBAChC,MnEo0PR,MmEl0PU,YAAA,iBAEF,MnEq0PR,MmEn0PU,cAAA,iBAEF,MnEs0PR,MmEp0PU,eAAA,iBAEF,MnEu0PR,MmEr0PU,aAAA,iBAfF,KAAgC,QAAA,eAChC,MnE41PR,MmE11PU,YAAA,eAEF,MnE61PR,MmE31PU,cAAA,eAEF,MnE81PR,MmE51PU,eAAA,eAEF,MnE+1PR,MmE71PU,aAAA,eAQF,MAAwB,OAAA,kBACxB,OnE61PR,OmE31PU,WAAA,kBAEF,OnE81PR,OmE51PU,aAAA,kBAEF,OnE+1PR,OmE71PU,cAAA,kBAEF,OnEg2PR,OmE91PU,YAAA,kBAfF,MAAwB,OAAA,iBACxB,OnEq3PR,OmEn3PU,WAAA,iBAEF,OnEs3PR,OmEp3PU,aAAA,iBAEF,OnEu3PR,OmEr3PU,cAAA,iBAEF,OnEw3PR,OmEt3PU,YAAA,iBAfF,MAAwB,OAAA,gBACxB,OnE64PR,OmE34PU,WAAA,gBAEF,OnE84PR,OmE54PU,aAAA,gBAEF,OnE+4PR,OmE74PU,cAAA,gBAEF,OnEg5PR,OmE94PU,YAAA,gBAfF,MAAwB,OAAA,kBACxB,OnEq6PR,OmEn6PU,WAAA,kBAEF,OnEs6PR,OmEp6PU,aAAA,kBAEF,OnEu6PR,OmEr6PU,cAAA,kBAEF,OnEw6PR,OmEt6PU,YAAA,kBAfF,MAAwB,OAAA,gBACxB,OnE67PR,OmE37PU,WAAA,gBAEF,OnE87PR,OmE57PU,aAAA,gBAEF,OnE+7PR,OmE77PU,cAAA,gBAEF,OnEg8PR,OmE97PU,YAAA,gBAMN,QAAmB,OAAA,eACnB,SnEg8PJ,SmE97PM,WAAA,eAEF,SnEi8PJ,SmE/7PM,aAAA,eAEF,SnEk8PJ,SmEh8PM,cAAA,eAEF,SnEm8PJ,SmEj8PM,YAAA,exDTF,yBwDlDI,QAAgC,OAAA,YAChC,SnEogQN,SmElgQQ,WAAA,YAEF,SnEogQN,SmElgQQ,aAAA,YAEF,SnEogQN,SmElgQQ,cAAA,YAEF,SnEogQN,SmElgQQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,SnEuhQN,SmErhQQ,WAAA,iBAEF,SnEuhQN,SmErhQQ,aAAA,iBAEF,SnEuhQN,SmErhQQ,cAAA,iBAEF,SnEuhQN,SmErhQQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,SnE0iQN,SmExiQQ,WAAA,gBAEF,SnE0iQN,SmExiQQ,aAAA,gBAEF,SnE0iQN,SmExiQQ,cAAA,gBAEF,SnE0iQN,SmExiQQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,SnE6jQN,SmE3jQQ,WAAA,eAEF,SnE6jQN,SmE3jQQ,aAAA,eAEF,SnE6jQN,SmE3jQQ,cAAA,eAEF,SnE6jQN,SmE3jQQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,SnEglQN,SmE9kQQ,WAAA,iBAEF,SnEglQN,SmE9kQQ,aAAA,iBAEF,SnEglQN,SmE9kQQ,cAAA,iBAEF,SnEglQN,SmE9kQQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,SnEmmQN,SmEjmQQ,WAAA,eAEF,SnEmmQN,SmEjmQQ,aAAA,eAEF,SnEmmQN,SmEjmQQ,cAAA,eAEF,SnEmmQN,SmEjmQQ,YAAA,eAfF,QAAgC,QAAA,YAChC,SnEsnQN,SmEpnQQ,YAAA,YAEF,SnEsnQN,SmEpnQQ,cAAA,YAEF,SnEsnQN,SmEpnQQ,eAAA,YAEF,SnEsnQN,SmEpnQQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,SnEyoQN,SmEvoQQ,YAAA,iBAEF,SnEyoQN,SmEvoQQ,cAAA,iBAEF,SnEyoQN,SmEvoQQ,eAAA,iBAEF,SnEyoQN,SmEvoQQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,SnE4pQN,SmE1pQQ,YAAA,gBAEF,SnE4pQN,SmE1pQQ,cAAA,gBAEF,SnE4pQN,SmE1pQQ,eAAA,gBAEF,SnE4pQN,SmE1pQQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,SnE+qQN,SmE7qQQ,YAAA,eAEF,SnE+qQN,SmE7qQQ,cAAA,eAEF,SnE+qQN,SmE7qQQ,eAAA,eAEF,SnE+qQN,SmE7qQQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,SnEksQN,SmEhsQQ,YAAA,iBAEF,SnEksQN,SmEhsQQ,cAAA,iBAEF,SnEksQN,SmEhsQQ,eAAA,iBAEF,SnEksQN,SmEhsQQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,SnEqtQN,SmEntQQ,YAAA,eAEF,SnEqtQN,SmEntQQ,cAAA,eAEF,SnEqtQN,SmEntQQ,eAAA,eAEF,SnEqtQN,SmEntQQ,aAAA,eAQF,SAAwB,OAAA,kBACxB,UnEitQN,UmE/sQQ,WAAA,kBAEF,UnEitQN,UmE/sQQ,aAAA,kBAEF,UnEitQN,UmE/sQQ,cAAA,kBAEF,UnEitQN,UmE/sQQ,YAAA,kBAfF,SAAwB,OAAA,iBACxB,UnEouQN,UmEluQQ,WAAA,iBAEF,UnEouQN,UmEluQQ,aAAA,iBAEF,UnEouQN,UmEluQQ,cAAA,iBAEF,UnEouQN,UmEluQQ,YAAA,iBAfF,SAAwB,OAAA,gBACxB,UnEuvQN,UmErvQQ,WAAA,gBAEF,UnEuvQN,UmErvQQ,aAAA,gBAEF,UnEuvQN,UmErvQQ,cAAA,gBAEF,UnEuvQN,UmErvQQ,YAAA,gBAfF,SAAwB,OAAA,kBACxB,UnE0wQN,UmExwQQ,WAAA,kBAEF,UnE0wQN,UmExwQQ,aAAA,kBAEF,UnE0wQN,UmExwQQ,cAAA,kBAEF,UnE0wQN,UmExwQQ,YAAA,kBAfF,SAAwB,OAAA,gBACxB,UnE6xQN,UmE3xQQ,WAAA,gBAEF,UnE6xQN,UmE3xQQ,aAAA,gBAEF,UnE6xQN,UmE3xQQ,cAAA,gBAEF,UnE6xQN,UmE3xQQ,YAAA,gBAMN,WAAmB,OAAA,eACnB,YnE2xQF,YmEzxQI,WAAA,eAEF,YnE2xQF,YmEzxQI,aAAA,eAEF,YnE2xQF,YmEzxQI,cAAA,eAEF,YnE2xQF,YmEzxQI,YAAA,gBxDTF,yBwDlDI,QAAgC,OAAA,YAChC,SnE61QN,SmE31QQ,WAAA,YAEF,SnE61QN,SmE31QQ,aAAA,YAEF,SnE61QN,SmE31QQ,cAAA,YAEF,SnE61QN,SmE31QQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,SnEg3QN,SmE92QQ,WAAA,iBAEF,SnEg3QN,SmE92QQ,aAAA,iBAEF,SnEg3QN,SmE92QQ,cAAA,iBAEF,SnEg3QN,SmE92QQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,SnEm4QN,SmEj4QQ,WAAA,gBAEF,SnEm4QN,SmEj4QQ,aAAA,gBAEF,SnEm4QN,SmEj4QQ,cAAA,gBAEF,SnEm4QN,SmEj4QQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,SnEs5QN,SmEp5QQ,WAAA,eAEF,SnEs5QN,SmEp5QQ,aAAA,eAEF,SnEs5QN,SmEp5QQ,cAAA,eAEF,SnEs5QN,SmEp5QQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,SnEy6QN,SmEv6QQ,WAAA,iBAEF,SnEy6QN,SmEv6QQ,aAAA,iBAEF,SnEy6QN,SmEv6QQ,cAAA,iBAEF,SnEy6QN,SmEv6QQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,SnE47QN,SmE17QQ,WAAA,eAEF,SnE47QN,SmE17QQ,aAAA,eAEF,SnE47QN,SmE17QQ,cAAA,eAEF,SnE47QN,SmE17QQ,YAAA,eAfF,QAAgC,QAAA,YAChC,SnE+8QN,SmE78QQ,YAAA,YAEF,SnE+8QN,SmE78QQ,cAAA,YAEF,SnE+8QN,SmE78QQ,eAAA,YAEF,SnE+8QN,SmE78QQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,SnEk+QN,SmEh+QQ,YAAA,iBAEF,SnEk+QN,SmEh+QQ,cAAA,iBAEF,SnEk+QN,SmEh+QQ,eAAA,iBAEF,SnEk+QN,SmEh+QQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,SnEq/QN,SmEn/QQ,YAAA,gBAEF,SnEq/QN,SmEn/QQ,cAAA,gBAEF,SnEq/QN,SmEn/QQ,eAAA,gBAEF,SnEq/QN,SmEn/QQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,SnEwgRN,SmEtgRQ,YAAA,eAEF,SnEwgRN,SmEtgRQ,cAAA,eAEF,SnEwgRN,SmEtgRQ,eAAA,eAEF,SnEwgRN,SmEtgRQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,SnE2hRN,SmEzhRQ,YAAA,iBAEF,SnE2hRN,SmEzhRQ,cAAA,iBAEF,SnE2hRN,SmEzhRQ,eAAA,iBAEF,SnE2hRN,SmEzhRQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,SnE8iRN,SmE5iRQ,YAAA,eAEF,SnE8iRN,SmE5iRQ,cAAA,eAEF,SnE8iRN,SmE5iRQ,eAAA,eAEF,SnE8iRN,SmE5iRQ,aAAA,eAQF,SAAwB,OAAA,kBACxB,UnE0iRN,UmExiRQ,WAAA,kBAEF,UnE0iRN,UmExiRQ,aAAA,kBAEF,UnE0iRN,UmExiRQ,cAAA,kBAEF,UnE0iRN,UmExiRQ,YAAA,kBAfF,SAAwB,OAAA,iBACxB,UnE6jRN,UmE3jRQ,WAAA,iBAEF,UnE6jRN,UmE3jRQ,aAAA,iBAEF,UnE6jRN,UmE3jRQ,cAAA,iBAEF,UnE6jRN,UmE3jRQ,YAAA,iBAfF,SAAwB,OAAA,gBACxB,UnEglRN,UmE9kRQ,WAAA,gBAEF,UnEglRN,UmE9kRQ,aAAA,gBAEF,UnEglRN,UmE9kRQ,cAAA,gBAEF,UnEglRN,UmE9kRQ,YAAA,gBAfF,SAAwB,OAAA,kBACxB,UnEmmRN,UmEjmRQ,WAAA,kBAEF,UnEmmRN,UmEjmRQ,aAAA,kBAEF,UnEmmRN,UmEjmRQ,cAAA,kBAEF,UnEmmRN,UmEjmRQ,YAAA,kBAfF,SAAwB,OAAA,gBACxB,UnEsnRN,UmEpnRQ,WAAA,gBAEF,UnEsnRN,UmEpnRQ,aAAA,gBAEF,UnEsnRN,UmEpnRQ,cAAA,gBAEF,UnEsnRN,UmEpnRQ,YAAA,gBAMN,WAAmB,OAAA,eACnB,YnEonRF,YmElnRI,WAAA,eAEF,YnEonRF,YmElnRI,aAAA,eAEF,YnEonRF,YmElnRI,cAAA,eAEF,YnEonRF,YmElnRI,YAAA,gBxDTF,yBwDlDI,QAAgC,OAAA,YAChC,SnEsrRN,SmEprRQ,WAAA,YAEF,SnEsrRN,SmEprRQ,aAAA,YAEF,SnEsrRN,SmEprRQ,cAAA,YAEF,SnEsrRN,SmEprRQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,SnEysRN,SmEvsRQ,WAAA,iBAEF,SnEysRN,SmEvsRQ,aAAA,iBAEF,SnEysRN,SmEvsRQ,cAAA,iBAEF,SnEysRN,SmEvsRQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,SnE4tRN,SmE1tRQ,WAAA,gBAEF,SnE4tRN,SmE1tRQ,aAAA,gBAEF,SnE4tRN,SmE1tRQ,cAAA,gBAEF,SnE4tRN,SmE1tRQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,SnE+uRN,SmE7uRQ,WAAA,eAEF,SnE+uRN,SmE7uRQ,aAAA,eAEF,SnE+uRN,SmE7uRQ,cAAA,eAEF,SnE+uRN,SmE7uRQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,SnEkwRN,SmEhwRQ,WAAA,iBAEF,SnEkwRN,SmEhwRQ,aAAA,iBAEF,SnEkwRN,SmEhwRQ,cAAA,iBAEF,SnEkwRN,SmEhwRQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,SnEqxRN,SmEnxRQ,WAAA,eAEF,SnEqxRN,SmEnxRQ,aAAA,eAEF,SnEqxRN,SmEnxRQ,cAAA,eAEF,SnEqxRN,SmEnxRQ,YAAA,eAfF,QAAgC,QAAA,YAChC,SnEwyRN,SmEtyRQ,YAAA,YAEF,SnEwyRN,SmEtyRQ,cAAA,YAEF,SnEwyRN,SmEtyRQ,eAAA,YAEF,SnEwyRN,SmEtyRQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,SnE2zRN,SmEzzRQ,YAAA,iBAEF,SnE2zRN,SmEzzRQ,cAAA,iBAEF,SnE2zRN,SmEzzRQ,eAAA,iBAEF,SnE2zRN,SmEzzRQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,SnE80RN,SmE50RQ,YAAA,gBAEF,SnE80RN,SmE50RQ,cAAA,gBAEF,SnE80RN,SmE50RQ,eAAA,gBAEF,SnE80RN,SmE50RQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,SnEi2RN,SmE/1RQ,YAAA,eAEF,SnEi2RN,SmE/1RQ,cAAA,eAEF,SnEi2RN,SmE/1RQ,eAAA,eAEF,SnEi2RN,SmE/1RQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,SnEo3RN,SmEl3RQ,YAAA,iBAEF,SnEo3RN,SmEl3RQ,cAAA,iBAEF,SnEo3RN,SmEl3RQ,eAAA,iBAEF,SnEo3RN,SmEl3RQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,SnEu4RN,SmEr4RQ,YAAA,eAEF,SnEu4RN,SmEr4RQ,cAAA,eAEF,SnEu4RN,SmEr4RQ,eAAA,eAEF,SnEu4RN,SmEr4RQ,aAAA,eAQF,SAAwB,OAAA,kBACxB,UnEm4RN,UmEj4RQ,WAAA,kBAEF,UnEm4RN,UmEj4RQ,aAAA,kBAEF,UnEm4RN,UmEj4RQ,cAAA,kBAEF,UnEm4RN,UmEj4RQ,YAAA,kBAfF,SAAwB,OAAA,iBACxB,UnEs5RN,UmEp5RQ,WAAA,iBAEF,UnEs5RN,UmEp5RQ,aAAA,iBAEF,UnEs5RN,UmEp5RQ,cAAA,iBAEF,UnEs5RN,UmEp5RQ,YAAA,iBAfF,SAAwB,OAAA,gBACxB,UnEy6RN,UmEv6RQ,WAAA,gBAEF,UnEy6RN,UmEv6RQ,aAAA,gBAEF,UnEy6RN,UmEv6RQ,cAAA,gBAEF,UnEy6RN,UmEv6RQ,YAAA,gBAfF,SAAwB,OAAA,kBACxB,UnE47RN,UmE17RQ,WAAA,kBAEF,UnE47RN,UmE17RQ,aAAA,kBAEF,UnE47RN,UmE17RQ,cAAA,kBAEF,UnE47RN,UmE17RQ,YAAA,kBAfF,SAAwB,OAAA,gBACxB,UnE+8RN,UmE78RQ,WAAA,gBAEF,UnE+8RN,UmE78RQ,aAAA,gBAEF,UnE+8RN,UmE78RQ,cAAA,gBAEF,UnE+8RN,UmE78RQ,YAAA,gBAMN,WAAmB,OAAA,eACnB,YnE68RF,YmE38RI,WAAA,eAEF,YnE68RF,YmE38RI,aAAA,eAEF,YnE68RF,YmE38RI,cAAA,eAEF,YnE68RF,YmE38RI,YAAA,gBxDTF,0BwDlDI,QAAgC,OAAA,YAChC,SnE+gSN,SmE7gSQ,WAAA,YAEF,SnE+gSN,SmE7gSQ,aAAA,YAEF,SnE+gSN,SmE7gSQ,cAAA,YAEF,SnE+gSN,SmE7gSQ,YAAA,YAfF,QAAgC,OAAA,iBAChC,SnEkiSN,SmEhiSQ,WAAA,iBAEF,SnEkiSN,SmEhiSQ,aAAA,iBAEF,SnEkiSN,SmEhiSQ,cAAA,iBAEF,SnEkiSN,SmEhiSQ,YAAA,iBAfF,QAAgC,OAAA,gBAChC,SnEqjSN,SmEnjSQ,WAAA,gBAEF,SnEqjSN,SmEnjSQ,aAAA,gBAEF,SnEqjSN,SmEnjSQ,cAAA,gBAEF,SnEqjSN,SmEnjSQ,YAAA,gBAfF,QAAgC,OAAA,eAChC,SnEwkSN,SmEtkSQ,WAAA,eAEF,SnEwkSN,SmEtkSQ,aAAA,eAEF,SnEwkSN,SmEtkSQ,cAAA,eAEF,SnEwkSN,SmEtkSQ,YAAA,eAfF,QAAgC,OAAA,iBAChC,SnE2lSN,SmEzlSQ,WAAA,iBAEF,SnE2lSN,SmEzlSQ,aAAA,iBAEF,SnE2lSN,SmEzlSQ,cAAA,iBAEF,SnE2lSN,SmEzlSQ,YAAA,iBAfF,QAAgC,OAAA,eAChC,SnE8mSN,SmE5mSQ,WAAA,eAEF,SnE8mSN,SmE5mSQ,aAAA,eAEF,SnE8mSN,SmE5mSQ,cAAA,eAEF,SnE8mSN,SmE5mSQ,YAAA,eAfF,QAAgC,QAAA,YAChC,SnEioSN,SmE/nSQ,YAAA,YAEF,SnEioSN,SmE/nSQ,cAAA,YAEF,SnEioSN,SmE/nSQ,eAAA,YAEF,SnEioSN,SmE/nSQ,aAAA,YAfF,QAAgC,QAAA,iBAChC,SnEopSN,SmElpSQ,YAAA,iBAEF,SnEopSN,SmElpSQ,cAAA,iBAEF,SnEopSN,SmElpSQ,eAAA,iBAEF,SnEopSN,SmElpSQ,aAAA,iBAfF,QAAgC,QAAA,gBAChC,SnEuqSN,SmErqSQ,YAAA,gBAEF,SnEuqSN,SmErqSQ,cAAA,gBAEF,SnEuqSN,SmErqSQ,eAAA,gBAEF,SnEuqSN,SmErqSQ,aAAA,gBAfF,QAAgC,QAAA,eAChC,SnE0rSN,SmExrSQ,YAAA,eAEF,SnE0rSN,SmExrSQ,cAAA,eAEF,SnE0rSN,SmExrSQ,eAAA,eAEF,SnE0rSN,SmExrSQ,aAAA,eAfF,QAAgC,QAAA,iBAChC,SnE6sSN,SmE3sSQ,YAAA,iBAEF,SnE6sSN,SmE3sSQ,cAAA,iBAEF,SnE6sSN,SmE3sSQ,eAAA,iBAEF,SnE6sSN,SmE3sSQ,aAAA,iBAfF,QAAgC,QAAA,eAChC,SnEguSN,SmE9tSQ,YAAA,eAEF,SnEguSN,SmE9tSQ,cAAA,eAEF,SnEguSN,SmE9tSQ,eAAA,eAEF,SnEguSN,SmE9tSQ,aAAA,eAQF,SAAwB,OAAA,kBACxB,UnE4tSN,UmE1tSQ,WAAA,kBAEF,UnE4tSN,UmE1tSQ,aAAA,kBAEF,UnE4tSN,UmE1tSQ,cAAA,kBAEF,UnE4tSN,UmE1tSQ,YAAA,kBAfF,SAAwB,OAAA,iBACxB,UnE+uSN,UmE7uSQ,WAAA,iBAEF,UnE+uSN,UmE7uSQ,aAAA,iBAEF,UnE+uSN,UmE7uSQ,cAAA,iBAEF,UnE+uSN,UmE7uSQ,YAAA,iBAfF,SAAwB,OAAA,gBACxB,UnEkwSN,UmEhwSQ,WAAA,gBAEF,UnEkwSN,UmEhwSQ,aAAA,gBAEF,UnEkwSN,UmEhwSQ,cAAA,gBAEF,UnEkwSN,UmEhwSQ,YAAA,gBAfF,SAAwB,OAAA,kBACxB,UnEqxSN,UmEnxSQ,WAAA,kBAEF,UnEqxSN,UmEnxSQ,aAAA,kBAEF,UnEqxSN,UmEnxSQ,cAAA,kBAEF,UnEqxSN,UmEnxSQ,YAAA,kBAfF,SAAwB,OAAA,gBACxB,UnEwySN,UmEtySQ,WAAA,gBAEF,UnEwySN,UmEtySQ,aAAA,gBAEF,UnEwySN,UmEtySQ,cAAA,gBAEF,UnEwySN,UmEtySQ,YAAA,gBAMN,WAAmB,OAAA,eACnB,YnEsySF,YmEpySI,WAAA,eAEF,YnEsySF,YmEpySI,aAAA,eAEF,YnEsySF,YmEpySI,cAAA,eAEF,YnEsySF,YmEpySI,YAAA,gBC/DN,gBAAkB,YAAA,cAAA,CAAA,KAAA,CAAA,MAAA,CAAA,QAAA,CAAA,iBAAA,CAAA,aAAA,CAAA,UAIlB,cAAiB,WAAA,kBACjB,WAAiB,YAAA,iBACjB,aAAiB,YAAA,iBACjB,eCTE,SAAA,OACA,cAAA,SACA,YAAA,ODeE,WAAwB,WAAA,eACxB,YAAwB,WAAA,gBACxB,aAAwB,WAAA,iBzDqCxB,yByDvCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBzDqCxB,yByDvCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBzDqCxB,yByDvCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBzDqCxB,0ByDvCA,cAAwB,WAAA,eACxB,eAAwB,WAAA,gBACxB,gBAAwB,WAAA,kBAM5B,gBAAmB,eAAA,oBACnB,gBAAmB,eAAA,oBACnB,iBAAmB,eAAA,qBAInB,mBAAuB,YAAA,cACvB,qBAAuB,YAAA,kBACvB,oBAAuB,YAAA,cACvB,kBAAuB,YAAA,cACvB,oBAAuB,YAAA,iBACvB,aAAuB,WAAA,iBAIvB,YAAc,MAAA,eEvCZ,cACE,MAAA,kBpEUF,qBAAA,qBoENI,MAAA,kBALJ,gBACE,MAAA,kBpEUF,uBAAA,uBoENI,MAAA,kBALJ,cACE,MAAA,kBpEUF,qBAAA,qBoENI,MAAA,kBALJ,WACE,MAAA,kBpEUF,kBAAA,kBoENI,MAAA,kBALJ,cACE,MAAA,kBpEUF,qBAAA,qBoENI,MAAA,kBALJ,aACE,MAAA,kBpEUF,oBAAA,oBoENI,MAAA,kBALJ,YACE,MAAA,kBpEUF,mBAAA,mBoENI,MAAA,kBALJ,WACE,MAAA,kBpEUF,kBAAA,kBoENI,MAAA,kBFwCN,WAAa,MAAA,kBACb,YAAc,MAAA,kBAEd,eAAiB,MAAA,yBACjB,eAAiB,MAAA,+BAIjB,WGvDE,KAAA,CAAA,CAAA,EAAA,EACA,MAAA,YACA,YAAA,KACA,iBAAA,YACA,OAAA,EHuDF,sBAAwB,gBAAA,eAIxB,YAAc,MAAA,kBI9Dd,SCCE,WAAA,kBDGF,WCHE,WAAA,iBCMA,a3EOF,ECikTE,QADA,S0EjkTI,YAAA,eAEA,WAAA,eAGF,YAEI,gBAAA,UASJ,mBACE,QAAA,KAAA,YAAA,I3E+LN,I2EhLM,YAAA,mB1EgjTJ,W0E9iTE,IAEE,OAAA,IAAA,MAAA,QACA,kBAAA,MAQF,MACE,QAAA,mB1E0iTJ,I0EviTE,GAEE,kBAAA,M1EyiTJ,GACA,G0EviTE,EAGE,QAAA,EACA,OAAA,EAGF,G1EqiTF,G0EniTI,iBAAA,MAQF,MACE,KAAA,G3E5CN,K2E+CM,UAAA,gBjEvFJ,WiE0FI,UAAA,gB7C9EN,Q6CmFM,QAAA,KxC/FN,OwCkGM,OAAA,IAAA,MAAA,K7DnGN,O6DuGM,gBAAA,mBADF,U1E+hTF,U0E1hTM,iBAAA,e1E8hTN,mBa9lTF,mB6DuEQ,OAAA,IAAA,MAAA,kB7DaR,Y6DRM,MAAA,Q1E2hTJ,wBAFA,ec/oTA,edgpTA,qB0EphTM,aAAA,Q7DhBR,sB6DqBM,MAAA,QACA,aAAA","sourcesContent":["/*!\n * Bootstrap v4.2.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"utilities\";\n@import \"print\";\n",":root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -webkit-tap-highlight-color: rgba($black, 0); // 5\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\n// TODO: remove in v5\n// stylelint-disable-next-line selector-list-comma-newline-after\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use\n// the `inherit` value on things like `<th>` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n font-size: $font-size-base;\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Suppress the focus outline on elements that cannot be accessed via keyboard.\n// This prevents an unwanted focus outline from appearing around elements that\n// might still respond to pointer events.\n//\n// Credit: https://github.com/suitcss/base\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `<h1>`-`<h6>` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable-next-line selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `<p>`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Remove the bottom border in Firefox 39-.\n// 5. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 4\n text-decoration-skip-ink: none; // 5\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n font-size: 80%; // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href)\n// which have not been made explicitly keyboard-focusable (without tabindex).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n\n @include hover-focus {\n color: inherit;\n text-decoration: none;\n }\n\n &:focus {\n outline: 0;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n font-size: 1em; // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg {\n // Workaround for the SVG overflow bug in IE10/11 is still required.\n // See https://github.com/twbs/bootstrap/issues/26878\n overflow: hidden;\n vertical-align: middle;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `<td>` alignment by inheriting from the `<body>`, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n// controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\nbutton,\n[type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button; // 2\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box; // 1. Add the correct box sizing in IE 10-\n padding: 0; // 2. Remove the padding in IE 10-\n}\n\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n // Remove the default appearance of temporal inputs to avoid a Mobile Safari\n // bug where setting a custom line-height prevents text from being vertically\n // centered within the input.\n // See https://bugs.webkit.org/show_bug.cgi?id=139848\n // and https://github.com/twbs/bootstrap/issues/11266\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto; // Remove the default vertical scrollbar in IE.\n // Textareas should really only resize vertically so they don't break their (horizontal) containers.\n resize: vertical;\n}\n\nfieldset {\n // Browsers set a default `min-width: min-content;` on fieldsets,\n // unlike e.g. `<div>`s, which have `min-width: 0;` by default.\n // So we reset that to ensure fieldsets behave more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359\n // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements\n min-width: 0;\n // Reset the default outline behavior of fieldsets so they don't affect page layout.\n padding: 0;\n margin: 0;\n border: 0;\n}\n\n// 1. Correct the text wrapping in Edge and IE.\n// 2. Correct the color inheritance from `fieldset` elements in IE.\nlegend {\n display: block;\n width: 100%;\n max-width: 100%; // 1\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit; // 2\n white-space: normal; // 1\n}\n\nprogress {\n vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.\n}\n\n// Correct the cursor style of increment and decrement buttons in Chrome.\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n outline-offset: -2px; // 2. Correct the outline style in Safari.\n -webkit-appearance: none;\n}\n\n//\n// Remove the inner padding and cancel buttons in Chrome and Safari on macOS.\n//\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// 1. Correct the inability to style clickable types in iOS and Safari.\n// 2. Change font properties to `inherit` in Safari.\n//\n\n::-webkit-file-upload-button {\n font: inherit; // 2\n -webkit-appearance: button; // 1\n}\n\n//\n// Correct element displays\n//\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item; // Add the correct display in all browsers\n cursor: pointer;\n}\n\ntemplate {\n display: none; // Add the correct display in IE\n}\n\n// Always hide an element with the `hidden` HTML attribute (from PureCSS).\n// Needed for proper display in IE 10-.\n[hidden] {\n display: none !important;\n}\n","/*!\n * Bootstrap v4.2.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-family: inherit;\n font-weight: 500;\n line-height: 1.2;\n color: inherit;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n -ms-flex-order: -1;\n order: -1;\n}\n\n.order-last {\n -ms-flex-order: 13;\n order: 13;\n}\n\n.order-0 {\n -ms-flex-order: 0;\n order: 0;\n}\n\n.order-1 {\n -ms-flex-order: 1;\n order: 1;\n}\n\n.order-2 {\n -ms-flex-order: 2;\n order: 2;\n}\n\n.order-3 {\n -ms-flex-order: 3;\n order: 3;\n}\n\n.order-4 {\n -ms-flex-order: 4;\n order: 4;\n}\n\n.order-5 {\n -ms-flex-order: 5;\n order: 5;\n}\n\n.order-6 {\n -ms-flex-order: 6;\n order: 6;\n}\n\n.order-7 {\n -ms-flex-order: 7;\n order: 7;\n}\n\n.order-8 {\n -ms-flex-order: 8;\n order: 8;\n}\n\n.order-9 {\n -ms-flex-order: 9;\n order: 9;\n}\n\n.order-10 {\n -ms-flex-order: 10;\n order: 10;\n}\n\n.order-11 {\n -ms-flex-order: 11;\n order: 11;\n}\n\n.order-12 {\n -ms-flex-order: 12;\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-sm-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-sm-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-sm-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-sm-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-sm-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-sm-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-sm-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-sm-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-sm-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-sm-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-sm-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-sm-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-sm-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-sm-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-md-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-md-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-md-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-md-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-md-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-md-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-md-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-md-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-md-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-md-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-md-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-md-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-md-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-md-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-lg-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-lg-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-lg-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-lg-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-lg-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-lg-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-lg-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-lg-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-lg-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-lg-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-lg-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-lg-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-lg-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-lg-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-xl-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-xl-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-xl-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-xl-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-xl-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-xl-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-xl-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-xl-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-xl-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-xl-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-xl-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-xl-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-xl-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-xl-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n background-color: transparent;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table .table {\n background-color: #fff;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #212529;\n border-color: #32383e;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #212529;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #32383e;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::-webkit-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-moz-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.8125rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(2.875rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n -ms-flex-align: center;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: 2.25rem;\n background-repeat: no-repeat;\n background-position: center right calc(2.25rem / 4);\n background-size: calc(2.25rem / 2) calc(2.25rem / 2);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: 2.25rem;\n background-position: top calc(2.25rem / 4) right calc(2.25rem / 4);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: 3.4375rem;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") no-repeat center right 1.75rem/1.125rem 1.125rem;\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-select:valid ~ .valid-feedback,\n.was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: 2.25rem;\n background-repeat: no-repeat;\n background-position: center right calc(2.25rem / 4);\n background-size: calc(2.25rem / 2) calc(2.25rem / 2);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\");\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: 2.25rem;\n background-position: top calc(2.25rem / 4) right calc(2.25rem / 4);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: 3.4375rem;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\") no-repeat center right 1.75rem/1.125rem 1.125rem;\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-select:invalid ~ .invalid-feedback,\n.was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:first-child {\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.dropdown-item:last-child {\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: -ms-inline-flexbox;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: stretch;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: -ms-flexbox;\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(2.875rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.8125rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n -webkit-transform: translateX(0.75rem);\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(128, 189, 255, 0.5);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n opacity: 0;\n}\n\n.custom-select-sm {\n height: calc(1.8125rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(2.875rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: 2.25rem;\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: calc(1rem + 0.4rem);\n padding: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -moz-appearance: none;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n -ms-flex-preferred-size: 100%;\n flex-basis: 100%;\n -ms-flex-positive: 1;\n flex-grow: 1;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n color: inherit;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n -ms-flex-direction: column;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n }\n .card-group > .card {\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-top,\n .card-group > .card:first-child .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-bottom,\n .card-group > .card:first-child .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-top,\n .card-group > .card:last-child .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-bottom,\n .card-group > .card:last-child .card-footer {\n border-bottom-left-radius: 0;\n }\n .card-group > .card:only-child {\n border-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-top,\n .card-group > .card:only-child .card-header {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-bottom,\n .card-group > .card:only-child .card-footer {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) {\n border-radius: 0;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer {\n border-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n -webkit-column-count: 3;\n -moz-column-count: 3;\n column-count: 3;\n -webkit-column-gap: 1.25rem;\n -moz-column-gap: 1.25rem;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion .card {\n overflow: hidden;\n}\n\n.accordion .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion .card .card-header {\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: -ms-flexbox;\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-link:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: -ms-flexbox;\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n -webkit-animation: progress-bar-stripes 1s linear infinite;\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n.media {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n}\n\n.media-body {\n -ms-flex: 1;\n flex: 1;\n}\n\n.list-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item:hover, .list-group-item:focus {\n z-index: 1;\n text-decoration: none;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush .list-group-item:last-child {\n margin-bottom: -1px;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n margin-bottom: 0;\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 0.25rem;\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n -webkit-backdrop-filter: blur(10px);\n backdrop-filter: blur(10px);\n opacity: 0;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: -webkit-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;\n -webkit-transform: translate(0, -50px);\n transform: translate(0, -50px);\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n -webkit-transform: none;\n transform: none;\n}\n\n.modal-dialog-centered {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n min-height: calc(100% - (0.5rem * 2));\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - (0.5rem * 2));\n content: \"\";\n}\n\n.modal-content {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #e9ecef;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: end;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #e9ecef;\n border-bottom-right-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-centered {\n min-height: calc(100% - (1.75rem * 2));\n }\n .modal-dialog-centered::before {\n height: calc(100vh - (1.75rem * 2));\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top .arrow, .bs-popover-auto[x-placement^=\"top\"] .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before,\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0;\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before {\n bottom: 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n bottom: 1px;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right .arrow, .bs-popover-auto[x-placement^=\"right\"] .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before,\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0.5rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before {\n left: 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n left: 1px;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^=\"bottom\"] .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before,\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n border-width: 0 0.5rem 0.5rem 0.5rem;\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before {\n top: 0;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n top: 1px;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left .arrow, .bs-popover-auto[x-placement^=\"left\"] .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before,\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n border-width: 0.5rem 0 0.5rem 0.5rem;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before {\n right: 0;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n right: 1px;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n color: inherit;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n -ms-touch-action: pan-y;\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n transition: -webkit-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n -webkit-transform: translateX(100%);\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n -webkit-transform: translateX(-100%);\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n -webkit-transform: none;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: 0s 0.6s opacity;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n -ms-flex: 0 1 auto;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@-webkit-keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n -webkit-animation: spinner-border .75s linear infinite;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@-webkit-keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n@keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n -webkit-animation: spinner-grow .75s linear infinite;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n}\n\n.d-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-md-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-print-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-3by4::before {\n padding-top: 133.333333%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n}\n\n.flex-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n}\n\n.justify-content-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n}\n\n.align-items-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n}\n\n.align-items-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n}\n\n.align-items-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n}\n\n.align-items-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n}\n\n.align-content-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n}\n\n.align-content-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n}\n\n.align-content-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n}\n\n.align-content-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n}\n\n.align-content-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n}\n\n.align-self-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n}\n\n.align-self-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n}\n\n.align-self-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n}\n\n.align-self-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n}\n\n.align-self-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-sm-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-sm-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-sm-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-sm-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-sm-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-sm-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-sm-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-sm-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-md-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-md-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-md-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-md-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-md-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-md-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-md-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-md-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-md-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-md-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-md-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-md-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-md-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-md-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-md-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-md-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-lg-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-lg-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-lg-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-lg-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-lg-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-lg-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-lg-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-lg-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-xl-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-xl-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-xl-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-xl-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-xl-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-xl-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-xl-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-xl-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports ((position: -webkit-sticky) or (position: sticky)) {\n .sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*!\n * Bootstrap v4.2.1 (https://getbootstrap.com/)\n * Copyright 2011-2018 The Bootstrap Authors\n * Copyright 2011-2018 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-family: inherit;\n font-weight: 500;\n line-height: 1.2;\n color: inherit;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n order: -1;\n}\n\n.order-last {\n order: 13;\n}\n\n.order-0 {\n order: 0;\n}\n\n.order-1 {\n order: 1;\n}\n\n.order-2 {\n order: 2;\n}\n\n.order-3 {\n order: 3;\n}\n\n.order-4 {\n order: 4;\n}\n\n.order-5 {\n order: 5;\n}\n\n.order-6 {\n order: 6;\n}\n\n.order-7 {\n order: 7;\n}\n\n.order-8 {\n order: 8;\n}\n\n.order-9 {\n order: 9;\n}\n\n.order-10 {\n order: 10;\n}\n\n.order-11 {\n order: 11;\n}\n\n.order-12 {\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n order: -1;\n }\n .order-sm-last {\n order: 13;\n }\n .order-sm-0 {\n order: 0;\n }\n .order-sm-1 {\n order: 1;\n }\n .order-sm-2 {\n order: 2;\n }\n .order-sm-3 {\n order: 3;\n }\n .order-sm-4 {\n order: 4;\n }\n .order-sm-5 {\n order: 5;\n }\n .order-sm-6 {\n order: 6;\n }\n .order-sm-7 {\n order: 7;\n }\n .order-sm-8 {\n order: 8;\n }\n .order-sm-9 {\n order: 9;\n }\n .order-sm-10 {\n order: 10;\n }\n .order-sm-11 {\n order: 11;\n }\n .order-sm-12 {\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n order: -1;\n }\n .order-md-last {\n order: 13;\n }\n .order-md-0 {\n order: 0;\n }\n .order-md-1 {\n order: 1;\n }\n .order-md-2 {\n order: 2;\n }\n .order-md-3 {\n order: 3;\n }\n .order-md-4 {\n order: 4;\n }\n .order-md-5 {\n order: 5;\n }\n .order-md-6 {\n order: 6;\n }\n .order-md-7 {\n order: 7;\n }\n .order-md-8 {\n order: 8;\n }\n .order-md-9 {\n order: 9;\n }\n .order-md-10 {\n order: 10;\n }\n .order-md-11 {\n order: 11;\n }\n .order-md-12 {\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n order: -1;\n }\n .order-lg-last {\n order: 13;\n }\n .order-lg-0 {\n order: 0;\n }\n .order-lg-1 {\n order: 1;\n }\n .order-lg-2 {\n order: 2;\n }\n .order-lg-3 {\n order: 3;\n }\n .order-lg-4 {\n order: 4;\n }\n .order-lg-5 {\n order: 5;\n }\n .order-lg-6 {\n order: 6;\n }\n .order-lg-7 {\n order: 7;\n }\n .order-lg-8 {\n order: 8;\n }\n .order-lg-9 {\n order: 9;\n }\n .order-lg-10 {\n order: 10;\n }\n .order-lg-11 {\n order: 11;\n }\n .order-lg-12 {\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n order: -1;\n }\n .order-xl-last {\n order: 13;\n }\n .order-xl-0 {\n order: 0;\n }\n .order-xl-1 {\n order: 1;\n }\n .order-xl-2 {\n order: 2;\n }\n .order-xl-3 {\n order: 3;\n }\n .order-xl-4 {\n order: 4;\n }\n .order-xl-5 {\n order: 5;\n }\n .order-xl-6 {\n order: 6;\n }\n .order-xl-7 {\n order: 7;\n }\n .order-xl-8 {\n order: 8;\n }\n .order-xl-9 {\n order: 9;\n }\n .order-xl-10 {\n order: 10;\n }\n .order-xl-11 {\n order: 11;\n }\n .order-xl-12 {\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n background-color: transparent;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table .table {\n background-color: #fff;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #212529;\n border-color: #32383e;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #212529;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #32383e;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.8125rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(2.875rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: inline-flex;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: 2.25rem;\n background-repeat: no-repeat;\n background-position: center right calc(2.25rem / 4);\n background-size: calc(2.25rem / 2) calc(2.25rem / 2);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: 2.25rem;\n background-position: top calc(2.25rem / 4) right calc(2.25rem / 4);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: 3.4375rem;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") no-repeat center right 1.75rem/1.125rem 1.125rem;\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-select:valid ~ .valid-feedback,\n.was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: 2.25rem;\n background-repeat: no-repeat;\n background-position: center right calc(2.25rem / 4);\n background-size: calc(2.25rem / 2) calc(2.25rem / 2);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\");\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: 2.25rem;\n background-position: top calc(2.25rem / 4) right calc(2.25rem / 4);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: 3.4375rem;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23d9534f' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\") no-repeat center right 1.75rem/1.125rem 1.125rem;\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-select:invalid ~ .invalid-feedback,\n.was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n align-items: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\n.btn:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:first-child {\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.dropdown-item:last-child {\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: flex;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(2.875rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.8125rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background-repeat: no-repeat;\n background-position: center center;\n background-size: 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(128, 189, 255, 0.5);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n opacity: 0;\n}\n\n.custom-select-sm {\n height: calc(1.8125rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(2.875rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(2.25rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(2.25rem + 2px);\n padding: 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: 2.25rem;\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: calc(1rem + 0.4rem);\n padding: 0;\n background-color: transparent;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-flow: row nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n color: inherit;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: flex;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: flex;\n flex: 1 0 0%;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: flex;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:first-child {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-top,\n .card-group > .card:first-child .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:first-child .card-img-bottom,\n .card-group > .card:first-child .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:last-child {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-top,\n .card-group > .card:last-child .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:last-child .card-img-bottom,\n .card-group > .card:last-child .card-footer {\n border-bottom-left-radius: 0;\n }\n .card-group > .card:only-child {\n border-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-top,\n .card-group > .card:only-child .card-header {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n }\n .card-group > .card:only-child .card-img-bottom,\n .card-group > .card:only-child .card-footer {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) {\n border-radius: 0;\n }\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header,\n .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer {\n border-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n column-count: 3;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion .card {\n overflow: hidden;\n}\n\n.accordion .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion .card .card-header {\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-link:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n.media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item:hover, .list-group-item:focus {\n z-index: 1;\n text-decoration: none;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush .list-group-item:last-child {\n margin-bottom: -1px;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n margin-bottom: 0;\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled) {\n cursor: pointer;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n appearance: none;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 0.25rem;\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n backdrop-filter: blur(10px);\n opacity: 0;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -50px);\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n transform: none;\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - (0.5rem * 2));\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - (0.5rem * 2));\n content: \"\";\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #e9ecef;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #e9ecef;\n border-bottom-right-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-centered {\n min-height: calc(100% - (1.75rem * 2));\n }\n .modal-dialog-centered::before {\n height: calc(100vh - (1.75rem * 2));\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top .arrow, .bs-popover-auto[x-placement^=\"top\"] .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before,\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0;\n}\n\n.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^=\"top\"] .arrow::before {\n bottom: 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-top .arrow::after,\n.bs-popover-auto[x-placement^=\"top\"] .arrow::after {\n bottom: 1px;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right .arrow, .bs-popover-auto[x-placement^=\"right\"] .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before,\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n border-width: 0.5rem 0.5rem 0.5rem 0;\n}\n\n.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^=\"right\"] .arrow::before {\n left: 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-right .arrow::after,\n.bs-popover-auto[x-placement^=\"right\"] .arrow::after {\n left: 1px;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^=\"bottom\"] .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before,\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n border-width: 0 0.5rem 0.5rem 0.5rem;\n}\n\n.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] .arrow::before {\n top: 0;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-bottom .arrow::after,\n.bs-popover-auto[x-placement^=\"bottom\"] .arrow::after {\n top: 1px;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left .arrow, .bs-popover-auto[x-placement^=\"left\"] .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before,\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n border-width: 0.5rem 0 0.5rem 0.5rem;\n}\n\n.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^=\"left\"] .arrow::before {\n right: 0;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n\n.bs-popover-left .arrow::after,\n.bs-popover-auto[x-placement^=\"left\"] .arrow::after {\n right: 1px;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n color: inherit;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n backface-visibility: hidden;\n transition: transform 0.6s ease-in-out;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: 0s 0.6s opacity;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media screen and (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@keyframes spinner-border {\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-3by4::before {\n padding-top: 133.333333%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports (position: sticky) {\n .sticky-top {\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */","// Hover mixin and `$enable-hover-media-query` are deprecated.\n//\n// Originally added during our alphas and maintained during betas, this mixin was\n// designed to prevent `:hover` stickiness on iOS-an issue where hover styles\n// would persist after initial touch.\n//\n// For backward compatibility, we've kept these mixins and updated them to\n// always return their regular pseudo-classes instead of a shimmed media query.\n//\n// Issue: https://github.com/twbs/bootstrap/issues/25195\n\n@mixin hover {\n &:hover { @content; }\n}\n\n@mixin hover-focus {\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin plain-hover-focus {\n &,\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin hover-focus-active {\n &:hover,\n &:focus,\n &:active {\n @content;\n }\n}\n","// stylelint-disable declaration-no-important, selector-list-comma-newline-after\n\n//\n// Headings\n//\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1, .h1 { font-size: $h1-font-size; }\nh2, .h2 { font-size: $h2-font-size; }\nh3, .h3 { font-size: $h3-font-size; }\nh4, .h4 { font-size: $h4-font-size; }\nh5, .h5 { font-size: $h5-font-size; }\nh6, .h6 { font-size: $h6-font-size; }\n\n.lead {\n font-size: $lead-font-size;\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n.display-1 {\n font-size: $display1-size;\n font-weight: $display1-weight;\n line-height: $display-line-height;\n}\n.display-2 {\n font-size: $display2-size;\n font-weight: $display2-weight;\n line-height: $display-line-height;\n}\n.display-3 {\n font-size: $display3-size;\n font-weight: $display3-weight;\n line-height: $display-line-height;\n}\n.display-4 {\n font-size: $display4-size;\n font-weight: $display4-weight;\n line-height: $display-line-height;\n}\n\n\n//\n// Horizontal rules\n//\n\nhr {\n margin-top: $hr-margin-y;\n margin-bottom: $hr-margin-y;\n border: 0;\n border-top: $hr-border-width solid $hr-border-color;\n}\n\n\n//\n// Emphasis\n//\n\nsmall,\n.small {\n font-size: $small-font-size;\n font-weight: $font-weight-normal;\n}\n\nmark,\n.mark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled;\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $spacer;\n font-size: $blockquote-font-size;\n}\n\n.blockquote-footer {\n display: block;\n font-size: $blockquote-small-font-size;\n color: $blockquote-small-color;\n\n &::before {\n content: \"\\2014\\00A0\"; // em dash, nbsp\n }\n}\n","// Lists\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n@mixin list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n","// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n @include img-fluid;\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n padding: $thumbnail-padding;\n background-color: $thumbnail-bg;\n border: $thumbnail-border-width solid $thumbnail-border-color;\n @include border-radius($thumbnail-border-radius);\n @include box-shadow($thumbnail-box-shadow);\n\n // Keep them at most 100% wide\n @include img-fluid;\n}\n\n//\n// Figures\n//\n\n.figure {\n // Ensures the caption's text aligns with the image.\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: $spacer / 2;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: $figure-caption-font-size;\n color: $figure-caption-color;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n@mixin img-fluid {\n // Part 1: Set a maximum relative to the parent\n max-width: 100%;\n // Part 2: Override the height to auto, otherwise images will be stretched\n // when setting a width and height attribute on the img element.\n height: auto;\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size.\n\n// stylelint-disable indentation, media-query-list-comma-newline-after\n@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {\n background-image: url($file-1x);\n\n // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio,\n // but doesn't convert dppx=>dpi.\n // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard.\n // Compatibility info: https://caniuse.com/#feat=css-media-resolution\n @media only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx\n only screen and (min-resolution: 2dppx) { // Standardized\n background-image: url($file-2x);\n background-size: $width-1x $height-1x;\n }\n}\n","// Single side border-radius\n\n@mixin border-radius($radius: $border-radius) {\n @if $enable-rounded {\n border-radius: $radius;\n }\n}\n\n@mixin border-top-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-top-right-radius: $radius;\n }\n}\n\n@mixin border-right-radius($radius) {\n @if $enable-rounded {\n border-top-right-radius: $radius;\n border-bottom-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-radius($radius) {\n @if $enable-rounded {\n border-bottom-right-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n\n@mixin border-left-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n","// Inline code\ncode {\n font-size: $code-font-size;\n color: $code-color;\n word-break: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n font-size: $kbd-font-size;\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n @include box-shadow($kbd-box-shadow);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: $nested-kbd-font-weight;\n @include box-shadow(none);\n }\n}\n\n// Blocks of code\npre {\n display: block;\n font-size: $code-font-size;\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: $pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n@if $enable-grid-classes {\n .container {\n @include make-container();\n @include make-container-max-widths();\n }\n}\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but with 100% width for\n// fluid, full width layouts.\n\n@if $enable-grid-classes {\n .container-fluid {\n @include make-container();\n }\n}\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n@if $enable-grid-classes {\n .row {\n @include make-row();\n }\n\n // Remove the negative margin from default .row, then the horizontal padding\n // from all immediate children columns (to prevent runaway style inheritance).\n .no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n > .col,\n > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n }\n}\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n@if $enable-grid-classes {\n @include make-grid-columns();\n}\n","/// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n@mixin make-container($gutter: $grid-gutter-width) {\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n margin-right: auto;\n margin-left: auto;\n}\n\n\n// For each breakpoint, define the maximum width of the container in a media query\n@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {\n @each $breakpoint, $container-max-width in $max-widths {\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n max-width: $container-max-width;\n }\n }\n}\n\n@mixin make-row($gutter: $grid-gutter-width) {\n display: flex;\n flex-wrap: wrap;\n margin-right: -$gutter / 2;\n margin-left: -$gutter / 2;\n}\n\n@mixin make-col-ready($gutter: $grid-gutter-width) {\n position: relative;\n // Prevent columns from becoming too narrow when at smaller grid tiers by\n // always setting `width: 100%;`. This works because we use `flex` values\n // later on to override this initial width.\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n}\n\n@mixin make-col($size, $columns: $grid-columns) {\n flex: 0 0 percentage($size / $columns);\n // Add a `max-width` to ensure content within each column does not blow out\n // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari\n // do not appear to require this.\n max-width: percentage($size / $columns);\n}\n\n@mixin make-col-offset($size, $columns: $grid-columns) {\n $num: $size / $columns;\n margin-left: if($num == 0, 0, percentage($num));\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @return if($n != null and $n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width. Null for the largest (last) breakpoint.\n// The maximum value is calculated as the minimum of the next one less 0.02px\n// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $next: breakpoint-next($name, $breakpoints);\n @return if($next, breakpoint-min($next, $breakpoints) - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($lower, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($upper, $breakpoints) {\n @content;\n }\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $max: breakpoint-max($name, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($name, $breakpoints) {\n @content;\n }\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `$grid-columns`.\n\n@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {\n // Common properties for all breakpoints\n %grid-column {\n position: relative;\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n }\n\n @each $breakpoint in map-keys($breakpoints) {\n $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n // Allow columns to stretch full width below their breakpoints\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @extend %grid-column;\n }\n }\n .col#{$infix},\n .col#{$infix}-auto {\n @extend %grid-column;\n }\n\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n // Provide basic `.col-{bp}` classes for equal-width flexbox columns\n .col#{$infix} {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col#{$infix}-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%; // Reset earlier grid tiers\n }\n\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @include make-col($i, $columns);\n }\n }\n\n .order#{$infix}-first { order: -1; }\n\n .order#{$infix}-last { order: $columns + 1; }\n\n @for $i from 0 through $columns {\n .order#{$infix}-#{$i} { order: $i; }\n }\n\n // `$columns - 1` because offsetting by the width of an entire row isn't possible\n @for $i from 0 through ($columns - 1) {\n @if not ($infix == \"\" and $i == 0) { // Avoid emitting useless .offset-0\n .offset#{$infix}-#{$i} {\n @include make-col-offset($i, $columns);\n }\n }\n }\n }\n }\n}\n","//\n// Basic Bootstrap table\n//\n\n.table {\n width: 100%;\n margin-bottom: $spacer;\n background-color: $table-bg; // Reset for nesting within parents with `background-color`.\n\n th,\n td {\n padding: $table-cell-padding;\n vertical-align: top;\n border-top: $table-border-width solid $table-border-color;\n }\n\n thead th {\n vertical-align: bottom;\n border-bottom: (2 * $table-border-width) solid $table-border-color;\n }\n\n tbody + tbody {\n border-top: (2 * $table-border-width) solid $table-border-color;\n }\n\n .table {\n background-color: $body-bg;\n }\n}\n\n\n//\n// Condensed table w/ half padding\n//\n\n.table-sm {\n th,\n td {\n padding: $table-cell-padding-sm;\n }\n}\n\n\n// Border versions\n//\n// Add or remove borders all around the table and between all the columns.\n\n.table-bordered {\n border: $table-border-width solid $table-border-color;\n\n th,\n td {\n border: $table-border-width solid $table-border-color;\n }\n\n thead {\n th,\n td {\n border-bottom-width: 2 * $table-border-width;\n }\n }\n}\n\n.table-borderless {\n th,\n td,\n thead th,\n tbody + tbody {\n border: 0;\n }\n}\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n tbody tr:nth-of-type(#{$table-striped-order}) {\n background-color: $table-accent-bg;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-hover-bg;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n@each $color, $value in $theme-colors {\n @include table-row-variant($color, theme-color-level($color, $table-bg-level), theme-color-level($color, $table-border-level));\n}\n\n@include table-row-variant(active, $table-active-bg);\n\n\n// Dark styles\n//\n// Same table markup, but inverted color scheme: dark background and light text.\n\n// stylelint-disable-next-line no-duplicate-selectors\n.table {\n .thead-dark {\n th {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n border-color: $table-dark-border-color;\n }\n }\n\n .thead-light {\n th {\n color: $table-head-color;\n background-color: $table-head-bg;\n border-color: $table-border-color;\n }\n }\n}\n\n.table-dark {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n\n th,\n td,\n thead th {\n border-color: $table-dark-border-color;\n }\n\n &.table-bordered {\n border: 0;\n }\n\n &.table-striped {\n tbody tr:nth-of-type(odd) {\n background-color: $table-dark-accent-bg;\n }\n }\n\n &.table-hover {\n tbody tr {\n @include hover {\n background-color: $table-dark-hover-bg;\n }\n }\n }\n}\n\n\n// Responsive tables\n//\n// Generate series of `.table-responsive-*` classes for configuring the screen\n// size of where your table will overflow.\n\n.table-responsive {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $next: breakpoint-next($breakpoint, $grid-breakpoints);\n $infix: breakpoint-infix($next, $grid-breakpoints);\n\n &#{$infix} {\n @include media-breakpoint-down($breakpoint) {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n -ms-overflow-style: -ms-autohiding-scrollbar; // See https://github.com/twbs/bootstrap/pull/10057\n\n // Prevent double border on horizontal scroll due to use of `display: block;`\n > .table-bordered {\n border: 0;\n }\n }\n }\n }\n}\n","// Tables\n\n@mixin table-row-variant($state, $background, $border: null) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table-#{$state} {\n &,\n > th,\n > td {\n background-color: $background;\n }\n\n @if $border != null {\n th,\n td,\n thead th,\n tbody + tbody {\n border-color: $border;\n }\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover {\n $hover-background: darken($background, 5%);\n\n .table-#{$state} {\n @include hover {\n background-color: $hover-background;\n\n > td,\n > th {\n background-color: $hover-background;\n }\n }\n }\n }\n}\n","// stylelint-disable selector-no-qualifying-type\n\n//\n// Textual form controls\n//\n\n.form-control {\n display: block;\n width: 100%;\n height: $input-height;\n padding: $input-padding-y $input-padding-x;\n font-size: $input-font-size;\n font-weight: $input-font-weight;\n line-height: $input-line-height;\n color: $input-color;\n background-color: $input-bg;\n background-clip: padding-box;\n border: $input-border-width solid $input-border-color;\n\n // Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.\n @if $enable-rounded {\n // Manually use the if/else instead of the mixin to account for iOS override\n border-radius: $input-border-radius;\n } @else {\n // Otherwise undo the iOS default\n border-radius: 0;\n }\n\n @include box-shadow($input-box-shadow);\n @include transition($input-transition);\n\n // Unstyle the caret on `<select>`s in IE10+.\n &::-ms-expand {\n background-color: transparent;\n border: 0;\n }\n\n // Customize the `:focus` state to imitate native WebKit styles.\n @include form-control-focus();\n\n // Placeholder\n &::placeholder {\n color: $input-placeholder-color;\n // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.\n opacity: 1;\n }\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &:disabled,\n &[readonly] {\n background-color: $input-disabled-bg;\n // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.\n opacity: 1;\n }\n}\n\nselect.form-control {\n &:focus::-ms-value {\n // Suppress the nested default white text on blue background highlight given to\n // the selected option text when the (still closed) <select> receives focus\n // in IE and (under certain conditions) Edge, as it looks bad and cannot be made to\n // match the appearance of the native widget.\n // See https://github.com/twbs/bootstrap/issues/19398.\n color: $input-color;\n background-color: $input-bg;\n }\n}\n\n// Make file inputs better match text inputs by forcing them to new lines.\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n\n//\n// Labels\n//\n\n// For use with horizontal and inline forms, when you need the label (or legend)\n// text to align with the form controls.\n.col-form-label {\n padding-top: calc(#{$input-padding-y} + #{$input-border-width});\n padding-bottom: calc(#{$input-padding-y} + #{$input-border-width});\n margin-bottom: 0; // Override the `<label>/<legend>` default\n font-size: inherit; // Override the `<legend>` default\n line-height: $input-line-height;\n}\n\n.col-form-label-lg {\n padding-top: calc(#{$input-padding-y-lg} + #{$input-border-width});\n padding-bottom: calc(#{$input-padding-y-lg} + #{$input-border-width});\n font-size: $input-font-size-lg;\n line-height: $input-line-height-lg;\n}\n\n.col-form-label-sm {\n padding-top: calc(#{$input-padding-y-sm} + #{$input-border-width});\n padding-bottom: calc(#{$input-padding-y-sm} + #{$input-border-width});\n font-size: $input-font-size-sm;\n line-height: $input-line-height-sm;\n}\n\n\n// Readonly controls as plain text\n//\n// Apply class to a readonly input to make it appear like regular plain\n// text (without any border, background color, focus indicator)\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: $input-padding-y;\n padding-bottom: $input-padding-y;\n margin-bottom: 0; // match inputs if this class comes on inputs with default margins\n line-height: $input-line-height;\n color: $input-plaintext-color;\n background-color: transparent;\n border: solid transparent;\n border-width: $input-border-width 0;\n\n &.form-control-sm,\n &.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// Repeated in `_input_group.scss` to avoid Sass extend issues.\n\n.form-control-sm {\n height: $input-height-sm;\n padding: $input-padding-y-sm $input-padding-x-sm;\n font-size: $input-font-size-sm;\n line-height: $input-line-height-sm;\n @include border-radius($input-border-radius-sm);\n}\n\n.form-control-lg {\n height: $input-height-lg;\n padding: $input-padding-y-lg $input-padding-x-lg;\n font-size: $input-font-size-lg;\n line-height: $input-line-height-lg;\n @include border-radius($input-border-radius-lg);\n}\n\n// stylelint-disable-next-line no-duplicate-selectors\nselect.form-control {\n &[size],\n &[multiple] {\n height: auto;\n }\n}\n\n// stylelint-disable-next-line no-duplicate-selectors\ntextarea.form-control {\n height: auto;\n}\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: $form-group-margin-bottom;\n}\n\n.form-text {\n display: block;\n margin-top: $form-text-margin-top;\n}\n\n\n// Form grid\n//\n// Special replacement for our grid system's `.row` for tighter form layouts.\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -$form-grid-gutter-width / 2;\n margin-left: -$form-grid-gutter-width / 2;\n\n > .col,\n > [class*=\"col-\"] {\n padding-right: $form-grid-gutter-width / 2;\n padding-left: $form-grid-gutter-width / 2;\n }\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.form-check {\n position: relative;\n display: block;\n padding-left: $form-check-input-gutter;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: $form-check-input-margin-y;\n margin-left: -$form-check-input-gutter;\n\n &:disabled ~ .form-check-label {\n color: $text-muted;\n }\n}\n\n.form-check-label {\n margin-bottom: 0; // Override default `<label>` bottom margin\n}\n\n.form-check-inline {\n display: inline-flex;\n align-items: center;\n padding-left: 0; // Override base .form-check\n margin-right: $form-check-inline-margin-x;\n\n // Undo .form-check-input defaults and add some `margin-right`.\n .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: $form-check-inline-input-margin-x;\n margin-left: 0;\n }\n}\n\n\n// Form validation\n//\n// Provide feedback to users when form field values are valid or invalid. Works\n// primarily for client-side validation via scoped `:invalid` and `:valid`\n// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for\n// server side validation.\n\n@include form-validation-state(\"valid\", $form-feedback-valid-color);\n@include form-validation-state(\"invalid\", $form-feedback-invalid-color);\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height)\n\n // Because we use flex, the initial sizing of checkboxes is collapsed and\n // doesn't occupy the full-width (which is what we want for xs grid tier),\n // so we force that here.\n .form-check {\n width: 100%;\n }\n\n // Kick in the inline\n @include media-breakpoint-up(sm) {\n label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n\n // Inline-block all the things for \"inline\"\n .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n\n // Allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-plaintext {\n display: inline-block;\n }\n\n .input-group,\n .custom-select {\n width: auto;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-check-input {\n position: relative;\n margin-top: 0;\n margin-right: $form-check-input-margin-x;\n margin-left: 0;\n }\n\n .custom-control {\n align-items: center;\n justify-content: center;\n }\n .custom-control-label {\n margin-bottom: 0;\n }\n }\n}\n","// stylelint-disable property-blacklist\n@mixin transition($transition...) {\n @if $enable-transitions {\n @if length($transition) == 0 {\n transition: $transition-base;\n } @else {\n transition: $transition;\n }\n }\n\n @if $enable-prefers-reduced-motion-media-query {\n @media screen and (prefers-reduced-motion: reduce) {\n transition: none;\n }\n }\n}\n","// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `$input-focus-border-color` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n@mixin form-control-focus() {\n &:focus {\n color: $input-focus-color;\n background-color: $input-focus-bg;\n border-color: $input-focus-border-color;\n outline: 0;\n // Avoid using mixin so we can pass custom focus shadow properly\n @if $enable-shadows {\n box-shadow: $input-box-shadow, $input-focus-box-shadow;\n } @else {\n box-shadow: $input-focus-box-shadow;\n }\n }\n}\n\n\n@mixin form-validation-state($state, $color) {\n .#{$state}-feedback {\n display: none;\n width: 100%;\n margin-top: $form-feedback-margin-top;\n font-size: $form-feedback-font-size;\n color: $color;\n }\n\n .#{$state}-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%; // Contain to parent when possible\n padding: $form-feedback-tooltip-padding-y $form-feedback-tooltip-padding-x;\n margin-top: .1rem;\n font-size: $form-feedback-tooltip-font-size;\n line-height: $form-feedback-tooltip-line-height;\n color: color-yiq($color);\n background-color: rgba($color, $form-feedback-tooltip-opacity);\n @include border-radius($form-feedback-tooltip-border-radius);\n }\n\n .form-control {\n .was-validated &:#{$state},\n &.is-#{$state} {\n border-color: $color;\n\n @if $enable-validation-icons {\n padding-right: $input-height-inner;\n background-repeat: no-repeat;\n background-position: center right calc(#{$input-height-inner} / 4);\n background-size: calc(#{$input-height-inner} / 2) calc(#{$input-height-inner} / 2);\n\n @if $state == \"valid\" {\n background-image: $form-feedback-icon-valid;\n } @else {\n background-image: $form-feedback-icon-invalid;\n }\n }\n\n &:focus {\n border-color: $color;\n box-shadow: 0 0 0 $input-focus-width rgba($color, .25);\n }\n\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n }\n }\n\n // stylelint-disable-next-line selector-no-qualifying-type\n textarea.form-control {\n .was-validated &:#{$state},\n &.is-#{$state} {\n @if $enable-validation-icons {\n padding-right: $input-height-inner;\n background-position: top calc(#{$input-height-inner} / 4) right calc(#{$input-height-inner} / 4);\n }\n }\n }\n\n .custom-select {\n .was-validated &:#{$state},\n &.is-#{$state} {\n border-color: $color;\n\n @if $enable-validation-icons {\n $form-feedback-icon: if($state == \"valid\", $form-feedback-icon-valid, $form-feedback-icon-invalid);\n padding-right: $custom-select-feedback-icon-padding-right;\n background: $custom-select-background, $form-feedback-icon no-repeat $custom-select-feedback-icon-position / $custom-select-feedback-icon-size;\n }\n\n &:focus {\n border-color: $color;\n box-shadow: 0 0 0 $input-focus-width rgba($color, .25);\n }\n\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n }\n }\n\n\n .form-control-file {\n .was-validated &:#{$state},\n &.is-#{$state} {\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n }\n }\n\n .form-check-input {\n .was-validated &:#{$state},\n &.is-#{$state} {\n ~ .form-check-label {\n color: $color;\n }\n\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n }\n }\n\n .custom-control-input {\n .was-validated &:#{$state},\n &.is-#{$state} {\n ~ .custom-control-label {\n color: $color;\n\n &::before {\n border-color: $color;\n }\n }\n\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n\n &:checked {\n ~ .custom-control-label::before {\n border-color: lighten($color, 10%);\n @include gradient-bg(lighten($color, 10%));\n }\n }\n\n &:focus {\n ~ .custom-control-label::before {\n box-shadow: 0 0 0 $input-focus-width rgba($color, .25);\n }\n\n &:not(:checked) ~ .custom-control-label::before {\n border-color: $color;\n }\n }\n }\n }\n\n // custom file\n .custom-file-input {\n .was-validated &:#{$state},\n &.is-#{$state} {\n ~ .custom-file-label {\n border-color: $color;\n }\n\n ~ .#{$state}-feedback,\n ~ .#{$state}-tooltip {\n display: block;\n }\n\n &:focus {\n ~ .custom-file-label {\n border-color: $color;\n box-shadow: 0 0 0 $input-focus-width rgba($color, .25);\n }\n }\n }\n }\n}\n","// Gradients\n\n@mixin gradient-bg($color) {\n @if $enable-gradients {\n background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x;\n } @else {\n background-color: $color;\n }\n}\n\n// Horizontal gradient, from left to right\n//\n// Creates two color stops, start and end, by specifying a color and position for each color stop.\n@mixin gradient-x($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) {\n background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent);\n background-repeat: repeat-x;\n}\n\n// Vertical gradient, from top to bottom\n//\n// Creates two color stops, start and end, by specifying a color and position for each color stop.\n@mixin gradient-y($start-color: $gray-700, $end-color: $gray-800, $start-percent: 0%, $end-percent: 100%) {\n background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent);\n background-repeat: repeat-x;\n}\n\n@mixin gradient-directional($start-color: $gray-700, $end-color: $gray-800, $deg: 45deg) {\n background-image: linear-gradient($deg, $start-color, $end-color);\n background-repeat: repeat-x;\n}\n@mixin gradient-x-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {\n background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);\n background-repeat: no-repeat;\n}\n@mixin gradient-y-three-colors($start-color: $blue, $mid-color: $purple, $color-stop: 50%, $end-color: $red) {\n background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);\n background-repeat: no-repeat;\n}\n@mixin gradient-radial($inner-color: $gray-700, $outer-color: $gray-800) {\n background-image: radial-gradient(circle, $inner-color, $outer-color);\n background-repeat: no-repeat;\n}\n@mixin gradient-striped($color: rgba($white, .15), $angle: 45deg) {\n background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent);\n}\n","// stylelint-disable selector-no-qualifying-type\n\n//\n// Base styles\n//\n\n.btn {\n display: inline-block;\n font-weight: $btn-font-weight;\n color: $body-color;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n background-color: transparent;\n border: $btn-border-width solid transparent;\n @include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-line-height, $btn-border-radius);\n @include transition($btn-transition);\n\n @include hover {\n color: $body-color;\n text-decoration: none;\n }\n\n &:focus,\n &.focus {\n outline: 0;\n box-shadow: $btn-focus-box-shadow;\n }\n\n // Disabled comes first so active can properly restyle\n &.disabled,\n &:disabled {\n opacity: $btn-disabled-opacity;\n @include box-shadow(none);\n }\n\n // Opinionated: add \"hand\" cursor to non-disabled .btn elements\n &:not(:disabled):not(.disabled) {\n cursor: pointer;\n }\n\n &:not(:disabled):not(.disabled):active,\n &:not(:disabled):not(.disabled).active {\n @include box-shadow($btn-active-box-shadow);\n\n &:focus {\n @include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);\n }\n }\n}\n\n// Future-proof disabling of clicks on `<a>` elements\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n\n//\n// Alternate buttons\n//\n\n@each $color, $value in $theme-colors {\n .btn-#{$color} {\n @include button-variant($value, $value);\n }\n}\n\n@each $color, $value in $theme-colors {\n .btn-outline-#{$color} {\n @include button-outline-variant($value);\n }\n}\n\n\n//\n// Link buttons\n//\n\n// Make a button look and behave like a link\n.btn-link {\n font-weight: $font-weight-normal;\n color: $link-color;\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n\n &:focus,\n &.focus {\n text-decoration: $link-hover-decoration;\n box-shadow: none;\n }\n\n &:disabled,\n &.disabled {\n color: $btn-link-disabled-color;\n pointer-events: none;\n }\n\n // No need for an active state here\n}\n\n\n//\n// Button Sizes\n//\n\n.btn-lg {\n @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);\n}\n\n.btn-sm {\n @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);\n}\n\n\n//\n// Block button\n//\n\n.btn-block {\n display: block;\n width: 100%;\n\n // Vertically space out multiple block buttons\n + .btn-block {\n margin-top: $btn-block-spacing-y;\n }\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n@mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) {\n color: color-yiq($background);\n @include gradient-bg($background);\n border-color: $border;\n @include box-shadow($btn-box-shadow);\n\n @include hover {\n color: color-yiq($hover-background);\n @include gradient-bg($hover-background);\n border-color: $hover-border;\n }\n\n &:focus,\n &.focus {\n // Avoid using mixin so we can pass custom focus shadow properly\n @if $enable-shadows {\n box-shadow: $btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);\n } @else {\n box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);\n }\n }\n\n // Disabled comes first so active can properly restyle\n &.disabled,\n &:disabled {\n color: color-yiq($background);\n background-color: $background;\n border-color: $border;\n // Remove CSS gradients if they're enabled\n @if $enable-gradients {\n background-image: none;\n }\n }\n\n &:not(:disabled):not(.disabled):active,\n &:not(:disabled):not(.disabled).active,\n .show > &.dropdown-toggle {\n color: color-yiq($active-background);\n background-color: $active-background;\n @if $enable-gradients {\n background-image: none; // Remove the gradient for the pressed/active state\n }\n border-color: $active-border;\n\n &:focus {\n // Avoid using mixin so we can pass custom focus shadow properly\n @if $enable-shadows {\n box-shadow: $btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);\n } @else {\n box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);\n }\n }\n }\n}\n\n@mixin button-outline-variant($color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) {\n color: $color;\n border-color: $color;\n\n @include hover {\n color: $color-hover;\n background-color: $active-background;\n border-color: $active-border;\n }\n\n &:focus,\n &.focus {\n box-shadow: 0 0 0 $btn-focus-width rgba($color, .5);\n }\n\n &.disabled,\n &:disabled {\n color: $color;\n background-color: transparent;\n }\n\n &:not(:disabled):not(.disabled):active,\n &:not(:disabled):not(.disabled).active,\n .show > &.dropdown-toggle {\n color: color-yiq($active-background);\n background-color: $active-background;\n border-color: $active-border;\n\n &:focus {\n // Avoid using mixin so we can pass custom focus shadow properly\n @if $enable-shadows and $btn-active-box-shadow != none {\n box-shadow: $btn-active-box-shadow, 0 0 0 $btn-focus-width rgba($color, .5);\n } @else {\n box-shadow: 0 0 0 $btn-focus-width rgba($color, .5);\n }\n }\n }\n}\n\n// Button sizes\n@mixin button-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) {\n padding: $padding-y $padding-x;\n font-size: $font-size;\n line-height: $line-height;\n // Manually declare to provide an override to the browser default\n @if $enable-rounded {\n border-radius: $border-radius;\n } @else {\n border-radius: 0;\n }\n}\n","// stylelint-disable selector-no-qualifying-type\n\n.fade {\n @include transition($transition-fade);\n\n &:not(.show) {\n opacity: 0;\n }\n}\n\n.collapse {\n &:not(.show) {\n display: none;\n }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n @include transition($transition-collapse);\n}\n","// The dropdown wrapper (`<div>`)\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n // Generate the caret automatically\n @include caret;\n}\n\n// The dropdown menu\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: $zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: $dropdown-min-width;\n padding: $dropdown-padding-y 0;\n margin: $dropdown-spacer 0 0; // override default ul\n font-size: $font-size-base; // Redeclare because nesting can cause inheritance issues\n color: $body-color;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n list-style: none;\n background-color: $dropdown-bg;\n background-clip: padding-box;\n border: $dropdown-border-width solid $dropdown-border-color;\n @include border-radius($dropdown-border-radius);\n @include box-shadow($dropdown-box-shadow);\n}\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .dropdown-menu#{$infix}-right {\n right: 0;\n left: auto;\n }\n }\n}\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .dropdown-menu#{$infix}-left {\n right: auto;\n left: 0;\n }\n }\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n// Just add .dropup after the standard .dropdown class and you're set.\n.dropup {\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: $dropdown-spacer;\n }\n\n .dropdown-toggle {\n @include caret(up);\n }\n}\n\n.dropright {\n .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: $dropdown-spacer;\n }\n\n .dropdown-toggle {\n @include caret(right);\n &::after {\n vertical-align: 0;\n }\n }\n}\n\n.dropleft {\n .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: $dropdown-spacer;\n }\n\n .dropdown-toggle {\n @include caret(left);\n &::before {\n vertical-align: 0;\n }\n }\n}\n\n// When enabled Popper.js, reset basic dropdown position\n// stylelint-disable-next-line no-duplicate-selectors\n.dropdown-menu {\n &[x-placement^=\"top\"],\n &[x-placement^=\"right\"],\n &[x-placement^=\"bottom\"],\n &[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n }\n}\n\n// Dividers (basically an `<hr>`) within the dropdown\n.dropdown-divider {\n @include nav-divider($dropdown-divider-bg);\n}\n\n// Links, buttons, and more within the dropdown menu\n//\n// `<button>`-specific styles are denoted with `// For <button>s`\n.dropdown-item {\n display: block;\n width: 100%; // For `<button>`s\n padding: $dropdown-item-padding-y $dropdown-item-padding-x;\n clear: both;\n font-weight: $font-weight-normal;\n color: $dropdown-link-color;\n text-align: inherit; // For `<button>`s\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n background-color: transparent; // For `<button>`s\n border: 0; // For `<button>`s\n\n &:first-child {\n @include border-top-radius($dropdown-inner-border-radius);\n }\n\n &:last-child {\n @include border-bottom-radius($dropdown-inner-border-radius);\n }\n\n @include hover-focus {\n color: $dropdown-link-hover-color;\n text-decoration: none;\n @include gradient-bg($dropdown-link-hover-bg);\n }\n\n &.active,\n &:active {\n color: $dropdown-link-active-color;\n text-decoration: none;\n @include gradient-bg($dropdown-link-active-bg);\n }\n\n &.disabled,\n &:disabled {\n color: $dropdown-link-disabled-color;\n pointer-events: none;\n background-color: transparent;\n // Remove CSS gradients if they're enabled\n @if $enable-gradients {\n background-image: none;\n }\n }\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: $dropdown-padding-y $dropdown-item-padding-x;\n margin-bottom: 0; // for use with heading elements\n font-size: $font-size-sm;\n color: $dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Dropdown text\n.dropdown-item-text {\n display: block;\n padding: $dropdown-item-padding-y $dropdown-item-padding-x;\n color: $dropdown-link-color;\n}\n","@mixin caret-down {\n border-top: $caret-width solid;\n border-right: $caret-width solid transparent;\n border-bottom: 0;\n border-left: $caret-width solid transparent;\n}\n\n@mixin caret-up {\n border-top: 0;\n border-right: $caret-width solid transparent;\n border-bottom: $caret-width solid;\n border-left: $caret-width solid transparent;\n}\n\n@mixin caret-right {\n border-top: $caret-width solid transparent;\n border-right: 0;\n border-bottom: $caret-width solid transparent;\n border-left: $caret-width solid;\n}\n\n@mixin caret-left {\n border-top: $caret-width solid transparent;\n border-right: $caret-width solid;\n border-bottom: $caret-width solid transparent;\n}\n\n@mixin caret($direction: down) {\n @if $enable-caret {\n &::after {\n display: inline-block;\n margin-left: $caret-width * .85;\n vertical-align: $caret-width * .85;\n content: \"\";\n @if $direction == down {\n @include caret-down;\n } @else if $direction == up {\n @include caret-up;\n } @else if $direction == right {\n @include caret-right;\n }\n }\n\n @if $direction == left {\n &::after {\n display: none;\n }\n\n &::before {\n display: inline-block;\n margin-right: $caret-width * .85;\n vertical-align: $caret-width * .85;\n content: \"\";\n @include caret-left;\n }\n }\n\n &:empty::after {\n margin-left: 0;\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n@mixin nav-divider($color: $nav-divider-color, $margin-y: $nav-divider-margin-y) {\n height: 0;\n margin: $margin-y 0;\n overflow: hidden;\n border-top: 1px solid $color;\n}\n","// stylelint-disable selector-no-qualifying-type\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle; // match .btn alignment given font-size hack above\n\n > .btn {\n position: relative;\n flex: 1 1 auto;\n\n // Bring the hover, focused, and \"active\" buttons to the front to overlay\n // the borders properly\n @include hover {\n z-index: 1;\n }\n &:focus,\n &:active,\n &.active {\n z-index: 1;\n }\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n\n .input-group {\n width: auto;\n }\n}\n\n.btn-group {\n // Prevent double borders when buttons are next to each other\n > .btn:not(:first-child),\n > .btn-group:not(:first-child) {\n margin-left: -$btn-border-width;\n }\n\n // Reset rounded corners\n > .btn:not(:last-child):not(.dropdown-toggle),\n > .btn-group:not(:last-child) > .btn {\n @include border-right-radius(0);\n }\n\n > .btn:not(:first-child),\n > .btn-group:not(:first-child) > .btn {\n @include border-left-radius(0);\n }\n}\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-sm > .btn { @extend .btn-sm; }\n.btn-group-lg > .btn { @extend .btn-lg; }\n\n\n//\n// Split button dropdowns\n//\n\n.dropdown-toggle-split {\n padding-right: $btn-padding-x * .75;\n padding-left: $btn-padding-x * .75;\n\n &::after,\n .dropup &::after,\n .dropright &::after {\n margin-left: 0;\n }\n\n .dropleft &::before {\n margin-right: 0;\n }\n}\n\n.btn-sm + .dropdown-toggle-split {\n padding-right: $btn-padding-x-sm * .75;\n padding-left: $btn-padding-x-sm * .75;\n}\n\n.btn-lg + .dropdown-toggle-split {\n padding-right: $btn-padding-x-lg * .75;\n padding-left: $btn-padding-x-lg * .75;\n}\n\n\n// The clickable button for toggling the menu\n// Set the same inset shadow as the :active state\n.btn-group.show .dropdown-toggle {\n @include box-shadow($btn-active-box-shadow);\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n @include box-shadow(none);\n }\n}\n\n\n//\n// Vertical button groups\n//\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n\n > .btn,\n > .btn-group {\n width: 100%;\n }\n\n > .btn:not(:first-child),\n > .btn-group:not(:first-child) {\n margin-top: -$btn-border-width;\n }\n\n // Reset rounded corners\n > .btn:not(:last-child):not(.dropdown-toggle),\n > .btn-group:not(:last-child) > .btn {\n @include border-bottom-radius(0);\n }\n\n > .btn:not(:first-child),\n > .btn-group:not(:first-child) > .btn {\n @include border-top-radius(0);\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n.btn-group-toggle {\n > .btn,\n > .btn-group > .btn {\n margin-bottom: 0; // Override default `<label>` value\n\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n }\n }\n}\n","// stylelint-disable selector-no-qualifying-type\n\n//\n// Base styles\n//\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap; // For form validation feedback\n align-items: stretch;\n width: 100%;\n\n > .form-control,\n > .form-control-plaintext,\n > .custom-select,\n > .custom-file {\n position: relative; // For focus state's z-index\n flex: 1 1 auto;\n // Add width 1% and flex-basis auto to ensure that button will not wrap out\n // the column. Applies to IE Edge+ and Firefox. Chrome does not require this.\n width: 1%;\n margin-bottom: 0;\n\n + .form-control,\n + .custom-select,\n + .custom-file {\n margin-left: -$input-border-width;\n }\n }\n\n // Bring the \"active\" form control to the top of surrounding elements\n > .form-control:focus,\n > .custom-select:focus,\n > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n }\n\n // Bring the custom file input above the label\n > .custom-file .custom-file-input:focus {\n z-index: 4;\n }\n\n > .form-control,\n > .custom-select {\n &:not(:last-child) { @include border-right-radius(0); }\n &:not(:first-child) { @include border-left-radius(0); }\n }\n\n // Custom file inputs have more complex markup, thus requiring different\n // border-radius overrides.\n > .custom-file {\n display: flex;\n align-items: center;\n\n &:not(:last-child) .custom-file-label,\n &:not(:last-child) .custom-file-label::after { @include border-right-radius(0); }\n &:not(:first-child) .custom-file-label { @include border-left-radius(0); }\n }\n}\n\n\n// Prepend and append\n//\n// While it requires one extra layer of HTML for each, dedicated prepend and\n// append elements allow us to 1) be less clever, 2) simplify our selectors, and\n// 3) support HTML5 form validation.\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n\n // Ensure buttons are always above inputs for more visually pleasing borders.\n // This isn't needed for `.input-group-text` since it shares the same border-color\n // as our inputs.\n .btn {\n position: relative;\n z-index: 2;\n\n &:focus {\n z-index: 3;\n }\n }\n\n .btn + .btn,\n .btn + .input-group-text,\n .input-group-text + .input-group-text,\n .input-group-text + .btn {\n margin-left: -$input-border-width;\n }\n}\n\n.input-group-prepend { margin-right: -$input-border-width; }\n.input-group-append { margin-left: -$input-border-width; }\n\n\n// Textual addons\n//\n// Serves as a catch-all element for any text or radio/checkbox input you wish\n// to prepend or append to an input.\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: $input-padding-y $input-padding-x;\n margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom\n font-size: $font-size-base; // Match inputs\n font-weight: $font-weight-normal;\n line-height: $input-line-height;\n color: $input-group-addon-color;\n text-align: center;\n white-space: nowrap;\n background-color: $input-group-addon-bg;\n border: $input-border-width solid $input-group-addon-border-color;\n @include border-radius($input-border-radius);\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n\n// Sizing\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: $input-height-lg;\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: $input-padding-y-lg $input-padding-x-lg;\n font-size: $input-font-size-lg;\n line-height: $input-line-height-lg;\n @include border-radius($input-border-radius-lg);\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: $input-height-sm;\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: $input-padding-y-sm $input-padding-x-sm;\n font-size: $input-font-size-sm;\n line-height: $input-line-height-sm;\n @include border-radius($input-border-radius-sm);\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: $custom-select-padding-x + $custom-select-indicator-padding;\n}\n\n\n// Prepend and append rounded corners\n//\n// These rulesets must come after the sizing ones to properly override sm and lg\n// border-radius values when extending. They're more specific than we'd like\n// with the `.input-group >` part, but without it, we cannot override the sizing.\n\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n @include border-right-radius(0);\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n @include border-left-radius(0);\n}\n","// Embedded icons from Open Iconic.\n// Released under MIT and copyright 2014 Waybury.\n// https://useiconic.com/open\n\n\n// Checkboxes and radios\n//\n// Base class takes care of all the key behavioral aspects.\n\n.custom-control {\n position: relative;\n display: block;\n min-height: $font-size-base * $line-height-base;\n padding-left: $custom-control-gutter + $custom-control-indicator-size;\n}\n\n.custom-control-inline {\n display: inline-flex;\n margin-right: $custom-control-spacer-x;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1; // Put the input behind the label so it doesn't overlay text\n opacity: 0;\n\n &:checked ~ .custom-control-label::before {\n color: $custom-control-indicator-checked-color;\n border-color: $custom-control-indicator-checked-border-color;\n @include gradient-bg($custom-control-indicator-checked-bg);\n @include box-shadow($custom-control-indicator-checked-box-shadow);\n }\n\n &:focus ~ .custom-control-label::before {\n // the mixin is not used here to make sure there is feedback\n @if $enable-shadows {\n box-shadow: $input-box-shadow, $input-focus-box-shadow;\n } @else {\n box-shadow: $custom-control-indicator-focus-box-shadow;\n }\n }\n\n &:focus:not(:checked) ~ .custom-control-label::before {\n border-color: $custom-control-indicator-focus-border-color;\n }\n\n &:not(:disabled):active ~ .custom-control-label::before {\n color: $custom-control-indicator-active-color;\n background-color: $custom-control-indicator-active-bg;\n border-color: $custom-control-indicator-active-border-color;\n @include box-shadow($custom-control-indicator-active-box-shadow);\n }\n\n &:disabled {\n ~ .custom-control-label {\n color: $custom-control-label-disabled-color;\n\n &::before {\n background-color: $custom-control-indicator-disabled-bg;\n }\n }\n }\n}\n\n// Custom control indicators\n//\n// Build the custom controls out of pseudo-elements.\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n\n // Background-color and (when enabled) gradient\n &::before {\n position: absolute;\n top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;\n left: -($custom-control-gutter + $custom-control-indicator-size);\n display: block;\n width: $custom-control-indicator-size;\n height: $custom-control-indicator-size;\n pointer-events: none;\n content: \"\";\n background-color: $custom-control-indicator-bg;\n border: $custom-control-indicator-border-color solid $custom-control-indicator-border-width;\n @include box-shadow($custom-control-indicator-box-shadow);\n }\n\n // Foreground (icon)\n &::after {\n position: absolute;\n top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2;\n left: -($custom-control-gutter + $custom-control-indicator-size);\n display: block;\n width: $custom-control-indicator-size;\n height: $custom-control-indicator-size;\n content: \"\";\n background-repeat: no-repeat;\n background-position: center center;\n background-size: $custom-control-indicator-bg-size;\n }\n}\n\n\n// Checkboxes\n//\n// Tweak just a few things for checkboxes.\n\n.custom-checkbox {\n .custom-control-label::before {\n @include border-radius($custom-checkbox-indicator-border-radius);\n }\n\n .custom-control-input:checked ~ .custom-control-label {\n &::after {\n background-image: $custom-checkbox-indicator-icon-checked;\n }\n }\n\n .custom-control-input:indeterminate ~ .custom-control-label {\n &::before {\n border-color: $custom-checkbox-indicator-indeterminate-border-color;\n @include gradient-bg($custom-checkbox-indicator-indeterminate-bg);\n @include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow);\n }\n &::after {\n background-image: $custom-checkbox-indicator-icon-indeterminate;\n }\n }\n\n .custom-control-input:disabled {\n &:checked ~ .custom-control-label::before {\n background-color: $custom-control-indicator-checked-disabled-bg;\n }\n &:indeterminate ~ .custom-control-label::before {\n background-color: $custom-control-indicator-checked-disabled-bg;\n }\n }\n}\n\n// Radios\n//\n// Tweak just a few things for radios.\n\n.custom-radio {\n .custom-control-label::before {\n border-radius: $custom-radio-indicator-border-radius;\n }\n\n .custom-control-input:checked ~ .custom-control-label {\n &::after {\n background-image: $custom-radio-indicator-icon-checked;\n }\n }\n\n .custom-control-input:disabled {\n &:checked ~ .custom-control-label::before {\n background-color: $custom-control-indicator-checked-disabled-bg;\n }\n }\n}\n\n\n// switches\n//\n// Tweak a few things for switches\n\n.custom-switch {\n padding-left: $custom-switch-width + $custom-control-gutter;\n\n .custom-control-label {\n &::before {\n left: -($custom-switch-width + $custom-control-gutter);\n width: $custom-switch-width;\n pointer-events: all;\n border-radius: $custom-switch-indicator-border-radius;\n }\n\n &::after {\n top: calc(#{(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2)} + #{$custom-control-indicator-border-width * 2});\n left: calc(#{-($custom-switch-width + $custom-control-gutter)} + #{$custom-control-indicator-border-width * 2});\n width: $custom-switch-indicator-size;\n height: $custom-switch-indicator-size;\n background-color: $custom-control-indicator-border-color;\n border-radius: $custom-switch-indicator-border-radius;\n @include transition(transform .15s ease-in-out, $custom-forms-transition);\n }\n }\n\n .custom-control-input:checked ~ .custom-control-label {\n &::after {\n background-color: $custom-control-indicator-bg;\n transform: translateX($custom-switch-width - $custom-control-indicator-size);\n }\n }\n\n .custom-control-input:disabled {\n &:checked ~ .custom-control-label::before {\n background-color: $custom-control-indicator-checked-disabled-bg;\n }\n }\n}\n\n\n// Select\n//\n// Replaces the browser default select with a custom one, mostly pulled from\n// https://primer.github.io/.\n//\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: $custom-select-height;\n padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;\n font-weight: $custom-select-font-weight;\n line-height: $custom-select-line-height;\n color: $custom-select-color;\n vertical-align: middle;\n background: $custom-select-background;\n background-color: $custom-select-bg;\n border: $custom-select-border-width solid $custom-select-border-color;\n @if $enable-rounded {\n border-radius: $custom-select-border-radius;\n } @else {\n border-radius: 0;\n }\n @include box-shadow($custom-select-box-shadow);\n appearance: none;\n\n &:focus {\n border-color: $custom-select-focus-border-color;\n outline: 0;\n @if $enable-shadows {\n box-shadow: $custom-select-box-shadow, $custom-select-focus-box-shadow;\n } @else {\n box-shadow: $custom-select-focus-box-shadow;\n }\n\n &::-ms-value {\n // For visual consistency with other platforms/browsers,\n // suppress the default white text on blue background highlight given to\n // the selected option text when the (still closed) <select> receives focus\n // in IE and (under certain conditions) Edge.\n // See https://github.com/twbs/bootstrap/issues/19398.\n color: $input-color;\n background-color: $input-bg;\n }\n }\n\n &[multiple],\n &[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: $custom-select-padding-x;\n background-image: none;\n }\n\n &:disabled {\n color: $custom-select-disabled-color;\n background-color: $custom-select-disabled-bg;\n }\n\n // Hides the default caret in IE11\n &::-ms-expand {\n opacity: 0;\n }\n}\n\n.custom-select-sm {\n height: $custom-select-height-sm;\n padding-top: $custom-select-padding-y-sm;\n padding-bottom: $custom-select-padding-y-sm;\n padding-left: $custom-select-padding-x-sm;\n font-size: $custom-select-font-size-sm;\n}\n\n.custom-select-lg {\n height: $custom-select-height-lg;\n padding-top: $custom-select-padding-y-lg;\n padding-bottom: $custom-select-padding-y-lg;\n padding-left: $custom-select-padding-x-lg;\n font-size: $custom-select-font-size-lg;\n}\n\n\n// File\n//\n// Custom file input.\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: $custom-file-height;\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: $custom-file-height;\n margin: 0;\n opacity: 0;\n\n &:focus ~ .custom-file-label {\n border-color: $custom-file-focus-border-color;\n box-shadow: $custom-file-focus-box-shadow;\n }\n\n &:disabled ~ .custom-file-label {\n background-color: $custom-file-disabled-bg;\n }\n\n @each $lang, $value in $custom-file-text {\n &:lang(#{$lang}) ~ .custom-file-label::after {\n content: $value;\n }\n }\n\n ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n }\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: $custom-file-height;\n padding: $custom-file-padding-y $custom-file-padding-x;\n font-weight: $custom-file-font-weight;\n line-height: $custom-file-line-height;\n color: $custom-file-color;\n background-color: $custom-file-bg;\n border: $custom-file-border-width solid $custom-file-border-color;\n @include border-radius($custom-file-border-radius);\n @include box-shadow($custom-file-box-shadow);\n\n &::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: $custom-file-height-inner;\n padding: $custom-file-padding-y $custom-file-padding-x;\n line-height: $custom-file-line-height;\n color: $custom-file-button-color;\n content: \"Browse\";\n @include gradient-bg($custom-file-button-bg);\n border-left: inherit;\n @include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0);\n }\n}\n\n// Range\n//\n// Style range inputs the same across browsers. Vendor-specific rules for pseudo\n// elements cannot be mixed. As such, there are no shared styles for focus or\n// active states on prefixed selectors.\n\n.custom-range {\n width: 100%;\n height: calc(#{$custom-range-thumb-height} + #{$custom-range-thumb-focus-box-shadow-width * 2});\n padding: 0; // Need to reset padding\n background-color: transparent;\n appearance: none;\n\n &:focus {\n outline: none;\n\n // Pseudo-elements must be split across multiple rulesets to have an effect.\n // No box-shadow() mixin for focus accessibility.\n &::-webkit-slider-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }\n &::-moz-range-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }\n &::-ms-thumb { box-shadow: $custom-range-thumb-focus-box-shadow; }\n }\n\n &::-moz-focus-outer {\n border: 0;\n }\n\n &::-webkit-slider-thumb {\n width: $custom-range-thumb-width;\n height: $custom-range-thumb-height;\n margin-top: ($custom-range-track-height - $custom-range-thumb-height) / 2; // Webkit specific\n @include gradient-bg($custom-range-thumb-bg);\n border: $custom-range-thumb-border;\n @include border-radius($custom-range-thumb-border-radius);\n @include box-shadow($custom-range-thumb-box-shadow);\n @include transition($custom-forms-transition);\n appearance: none;\n\n &:active {\n @include gradient-bg($custom-range-thumb-active-bg);\n }\n }\n\n &::-webkit-slider-runnable-track {\n width: $custom-range-track-width;\n height: $custom-range-track-height;\n color: transparent; // Why?\n cursor: $custom-range-track-cursor;\n background-color: $custom-range-track-bg;\n border-color: transparent;\n @include border-radius($custom-range-track-border-radius);\n @include box-shadow($custom-range-track-box-shadow);\n }\n\n &::-moz-range-thumb {\n width: $custom-range-thumb-width;\n height: $custom-range-thumb-height;\n @include gradient-bg($custom-range-thumb-bg);\n border: $custom-range-thumb-border;\n @include border-radius($custom-range-thumb-border-radius);\n @include box-shadow($custom-range-thumb-box-shadow);\n @include transition($custom-forms-transition);\n appearance: none;\n\n &:active {\n @include gradient-bg($custom-range-thumb-active-bg);\n }\n }\n\n &::-moz-range-track {\n width: $custom-range-track-width;\n height: $custom-range-track-height;\n color: transparent;\n cursor: $custom-range-track-cursor;\n background-color: $custom-range-track-bg;\n border-color: transparent; // Firefox specific?\n @include border-radius($custom-range-track-border-radius);\n @include box-shadow($custom-range-track-box-shadow);\n }\n\n &::-ms-thumb {\n width: $custom-range-thumb-width;\n height: $custom-range-thumb-height;\n margin-top: 0; // Edge specific\n margin-right: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.\n margin-left: $custom-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden.\n @include gradient-bg($custom-range-thumb-bg);\n border: $custom-range-thumb-border;\n @include border-radius($custom-range-thumb-border-radius);\n @include box-shadow($custom-range-thumb-box-shadow);\n @include transition($custom-forms-transition);\n appearance: none;\n\n &:active {\n @include gradient-bg($custom-range-thumb-active-bg);\n }\n }\n\n &::-ms-track {\n width: $custom-range-track-width;\n height: $custom-range-track-height;\n color: transparent;\n cursor: $custom-range-track-cursor;\n background-color: transparent;\n border-color: transparent;\n border-width: $custom-range-thumb-height / 2;\n @include box-shadow($custom-range-track-box-shadow);\n }\n\n &::-ms-fill-lower {\n background-color: $custom-range-track-bg;\n @include border-radius($custom-range-track-border-radius);\n }\n\n &::-ms-fill-upper {\n margin-right: 15px; // arbitrary?\n background-color: $custom-range-track-bg;\n @include border-radius($custom-range-track-border-radius);\n }\n\n &:disabled {\n &::-webkit-slider-thumb {\n background-color: $custom-range-thumb-disabled-bg;\n }\n\n &::-webkit-slider-runnable-track {\n cursor: default;\n }\n\n &::-moz-range-thumb {\n background-color: $custom-range-thumb-disabled-bg;\n }\n\n &::-moz-range-track {\n cursor: default;\n }\n\n &::-ms-thumb {\n background-color: $custom-range-thumb-disabled-bg;\n }\n }\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n @include transition($custom-forms-transition);\n}\n","// Base class\n//\n// Kickstart any navigation component with a set of style resets. Works with\n// `<nav>`s or `<ul>`s.\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: $nav-link-padding-y $nav-link-padding-x;\n\n @include hover-focus {\n text-decoration: none;\n }\n\n // Disabled state lightens text\n &.disabled {\n color: $nav-link-disabled-color;\n pointer-events: none;\n cursor: default;\n }\n}\n\n//\n// Tabs\n//\n\n.nav-tabs {\n border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;\n\n .nav-item {\n margin-bottom: -$nav-tabs-border-width;\n }\n\n .nav-link {\n border: $nav-tabs-border-width solid transparent;\n @include border-top-radius($nav-tabs-border-radius);\n\n @include hover-focus {\n border-color: $nav-tabs-link-hover-border-color;\n }\n\n &.disabled {\n color: $nav-link-disabled-color;\n background-color: transparent;\n border-color: transparent;\n }\n }\n\n .nav-link.active,\n .nav-item.show .nav-link {\n color: $nav-tabs-link-active-color;\n background-color: $nav-tabs-link-active-bg;\n border-color: $nav-tabs-link-active-border-color;\n }\n\n .dropdown-menu {\n // Make dropdown border overlap tab border\n margin-top: -$nav-tabs-border-width;\n // Remove the top rounded corners here since there is a hard edge above the menu\n @include border-top-radius(0);\n }\n}\n\n\n//\n// Pills\n//\n\n.nav-pills {\n .nav-link {\n @include border-radius($nav-pills-border-radius);\n }\n\n .nav-link.active,\n .show > .nav-link {\n color: $nav-pills-link-active-color;\n background-color: $nav-pills-link-active-bg;\n }\n}\n\n\n//\n// Justified variants\n//\n\n.nav-fill {\n .nav-item {\n flex: 1 1 auto;\n text-align: center;\n }\n}\n\n.nav-justified {\n .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n }\n}\n\n\n// Tabbable tabs\n//\n// Hide tabbable panes to start, show them when `.active`\n\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n","// Contents\n//\n// Navbar\n// Navbar brand\n// Navbar nav\n// Navbar text\n// Navbar divider\n// Responsive navbar\n// Navbar position\n// Navbar themes\n\n\n// Navbar\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap; // allow us to do the line break for collapsing content\n align-items: center;\n justify-content: space-between; // space out brand from logo\n padding: $navbar-padding-y $navbar-padding-x;\n\n // Because flex properties aren't inherited, we need to redeclare these first\n // few properties so that content nested within behave properly.\n > .container,\n > .container-fluid {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n }\n}\n\n\n// Navbar brand\n//\n// Used for brand, project, or site names.\n\n.navbar-brand {\n display: inline-block;\n padding-top: $navbar-brand-padding-y;\n padding-bottom: $navbar-brand-padding-y;\n margin-right: $navbar-padding-x;\n font-size: $navbar-brand-font-size;\n line-height: inherit;\n white-space: nowrap;\n\n @include hover-focus {\n text-decoration: none;\n }\n}\n\n\n// Navbar nav\n//\n// Custom navbar navigation (doesn't require `.nav`, but does make use of `.nav-link`).\n\n.navbar-nav {\n display: flex;\n flex-direction: column; // cannot use `inherit` to get the `.navbar`s value\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n\n .nav-link {\n padding-right: 0;\n padding-left: 0;\n }\n\n .dropdown-menu {\n position: static;\n float: none;\n }\n}\n\n\n// Navbar text\n//\n//\n\n.navbar-text {\n display: inline-block;\n padding-top: $nav-link-padding-y;\n padding-bottom: $nav-link-padding-y;\n}\n\n\n// Responsive navbar\n//\n// Custom styles for responsive collapsing and toggling of navbar contents.\n// Powered by the collapse Bootstrap JavaScript plugin.\n\n// When collapsed, prevent the toggleable navbar contents from appearing in\n// the default flexbox row orientation. Requires the use of `flex-wrap: wrap`\n// on the `.navbar` parent.\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n // For always expanded or extra full navbars, ensure content aligns itself\n // properly vertically. Can be easily overridden with flex utilities.\n align-items: center;\n}\n\n// Button for toggling the navbar when in its collapsed state\n.navbar-toggler {\n padding: $navbar-toggler-padding-y $navbar-toggler-padding-x;\n font-size: $navbar-toggler-font-size;\n line-height: 1;\n background-color: transparent; // remove default button style\n border: $border-width solid transparent; // remove default button style\n @include border-radius($navbar-toggler-border-radius);\n\n @include hover-focus {\n text-decoration: none;\n }\n\n // Opinionated: add \"hand\" cursor to non-disabled .navbar-toggler elements\n &:not(:disabled):not(.disabled) {\n cursor: pointer;\n }\n}\n\n// Keep as a separate element so folks can easily override it with another icon\n// or image file as needed.\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n// Generate series of `.navbar-expand-*` responsive classes for configuring\n// where your navbar collapses.\n.navbar-expand {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $next: breakpoint-next($breakpoint, $grid-breakpoints);\n $infix: breakpoint-infix($next, $grid-breakpoints);\n\n &#{$infix} {\n @include media-breakpoint-down($breakpoint) {\n > .container,\n > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n }\n\n @include media-breakpoint-up($next) {\n flex-flow: row nowrap;\n justify-content: flex-start;\n\n .navbar-nav {\n flex-direction: row;\n\n .dropdown-menu {\n position: absolute;\n }\n\n .nav-link {\n padding-right: $navbar-nav-link-padding-x;\n padding-left: $navbar-nav-link-padding-x;\n }\n }\n\n // For nesting containers, have to redeclare for alignment purposes\n > .container,\n > .container-fluid {\n flex-wrap: nowrap;\n }\n\n .navbar-collapse {\n display: flex !important; // stylelint-disable-line declaration-no-important\n\n // Changes flex-bases to auto because of an IE10 bug\n flex-basis: auto;\n }\n\n .navbar-toggler {\n display: none;\n }\n }\n }\n }\n}\n\n\n// Navbar themes\n//\n// Styles for switching between navbars with light or dark background.\n\n// Dark links against a light background\n.navbar-light {\n .navbar-brand {\n color: $navbar-light-brand-color;\n\n @include hover-focus {\n color: $navbar-light-brand-hover-color;\n }\n }\n\n .navbar-nav {\n .nav-link {\n color: $navbar-light-color;\n\n @include hover-focus {\n color: $navbar-light-hover-color;\n }\n\n &.disabled {\n color: $navbar-light-disabled-color;\n }\n }\n\n .show > .nav-link,\n .active > .nav-link,\n .nav-link.show,\n .nav-link.active {\n color: $navbar-light-active-color;\n }\n }\n\n .navbar-toggler {\n color: $navbar-light-color;\n border-color: $navbar-light-toggler-border-color;\n }\n\n .navbar-toggler-icon {\n background-image: $navbar-light-toggler-icon-bg;\n }\n\n .navbar-text {\n color: $navbar-light-color;\n a {\n color: $navbar-light-active-color;\n\n @include hover-focus {\n color: $navbar-light-active-color;\n }\n }\n }\n}\n\n// White links against a dark background\n.navbar-dark {\n .navbar-brand {\n color: $navbar-dark-brand-color;\n\n @include hover-focus {\n color: $navbar-dark-brand-hover-color;\n }\n }\n\n .navbar-nav {\n .nav-link {\n color: $navbar-dark-color;\n\n @include hover-focus {\n color: $navbar-dark-hover-color;\n }\n\n &.disabled {\n color: $navbar-dark-disabled-color;\n }\n }\n\n .show > .nav-link,\n .active > .nav-link,\n .nav-link.show,\n .nav-link.active {\n color: $navbar-dark-active-color;\n }\n }\n\n .navbar-toggler {\n color: $navbar-dark-color;\n border-color: $navbar-dark-toggler-border-color;\n }\n\n .navbar-toggler-icon {\n background-image: $navbar-dark-toggler-icon-bg;\n }\n\n .navbar-text {\n color: $navbar-dark-color;\n a {\n color: $navbar-dark-active-color;\n\n @include hover-focus {\n color: $navbar-dark-active-color;\n }\n }\n }\n}\n","//\n// Base styles\n//\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: $card-bg;\n background-clip: border-box;\n border: $card-border-width solid $card-border-color;\n @include border-radius($card-border-radius);\n\n > hr {\n margin-right: 0;\n margin-left: 0;\n }\n\n > .list-group:first-child {\n .list-group-item:first-child {\n @include border-top-radius($card-border-radius);\n }\n }\n\n > .list-group:last-child {\n .list-group-item:last-child {\n @include border-bottom-radius($card-border-radius);\n }\n }\n}\n\n.card-body {\n // Enable `flex-grow: 1` for decks and groups so that card blocks take up\n // as much space as possible, ensuring footers are aligned to the bottom.\n flex: 1 1 auto;\n padding: $card-spacer-x;\n}\n\n.card-title {\n margin-bottom: $card-spacer-y;\n}\n\n.card-subtitle {\n margin-top: -$card-spacer-y / 2;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link {\n @include hover {\n text-decoration: none;\n }\n\n + .card-link {\n margin-left: $card-spacer-x;\n }\n}\n\n//\n// Optional textual caps\n//\n\n.card-header {\n padding: $card-spacer-y $card-spacer-x;\n margin-bottom: 0; // Removes the default margin-bottom of <hN>\n color: $card-cap-color;\n background-color: $card-cap-bg;\n border-bottom: $card-border-width solid $card-border-color;\n\n &:first-child {\n @include border-radius($card-inner-border-radius $card-inner-border-radius 0 0);\n }\n\n + .list-group {\n .list-group-item:first-child {\n border-top: 0;\n }\n }\n}\n\n.card-footer {\n padding: $card-spacer-y $card-spacer-x;\n background-color: $card-cap-bg;\n border-top: $card-border-width solid $card-border-color;\n\n &:last-child {\n @include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius);\n }\n}\n\n\n//\n// Header navs\n//\n\n.card-header-tabs {\n margin-right: -$card-spacer-x / 2;\n margin-bottom: -$card-spacer-y;\n margin-left: -$card-spacer-x / 2;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -$card-spacer-x / 2;\n margin-left: -$card-spacer-x / 2;\n}\n\n// Card image\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: $card-img-overlay-padding;\n}\n\n.card-img {\n width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch\n @include border-radius($card-inner-border-radius);\n}\n\n// Card image caps\n.card-img-top {\n width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch\n @include border-top-radius($card-inner-border-radius);\n}\n\n.card-img-bottom {\n width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch\n @include border-bottom-radius($card-inner-border-radius);\n}\n\n\n// Card deck\n\n.card-deck {\n display: flex;\n flex-direction: column;\n\n .card {\n margin-bottom: $card-deck-margin;\n }\n\n @include media-breakpoint-up(sm) {\n flex-flow: row wrap;\n margin-right: -$card-deck-margin;\n margin-left: -$card-deck-margin;\n\n .card {\n display: flex;\n // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4\n flex: 1 0 0%;\n flex-direction: column;\n margin-right: $card-deck-margin;\n margin-bottom: 0; // Override the default\n margin-left: $card-deck-margin;\n }\n }\n}\n\n\n//\n// Card groups\n//\n\n.card-group {\n display: flex;\n flex-direction: column;\n\n // The child selector allows nested `.card` within `.card-group`\n // to display properly.\n > .card {\n margin-bottom: $card-group-margin;\n }\n\n @include media-breakpoint-up(sm) {\n flex-flow: row wrap;\n // The child selector allows nested `.card` within `.card-group`\n // to display properly.\n > .card {\n // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4\n flex: 1 0 0%;\n margin-bottom: 0;\n\n + .card {\n margin-left: 0;\n border-left: 0;\n }\n\n // Handle rounded corners\n @if $enable-rounded {\n &:first-child {\n @include border-right-radius(0);\n\n .card-img-top,\n .card-header {\n border-top-right-radius: 0;\n }\n .card-img-bottom,\n .card-footer {\n border-bottom-right-radius: 0;\n }\n }\n\n &:last-child {\n @include border-left-radius(0);\n\n .card-img-top,\n .card-header {\n border-top-left-radius: 0;\n }\n .card-img-bottom,\n .card-footer {\n border-bottom-left-radius: 0;\n }\n }\n\n &:only-child {\n @include border-radius($card-border-radius);\n\n .card-img-top,\n .card-header {\n @include border-top-radius($card-border-radius);\n }\n .card-img-bottom,\n .card-footer {\n @include border-bottom-radius($card-border-radius);\n }\n }\n\n &:not(:first-child):not(:last-child):not(:only-child) {\n @include border-radius(0);\n\n .card-img-top,\n .card-img-bottom,\n .card-header,\n .card-footer {\n @include border-radius(0);\n }\n }\n }\n }\n }\n}\n\n\n//\n// Columns\n//\n\n.card-columns {\n .card {\n margin-bottom: $card-columns-margin;\n }\n\n @include media-breakpoint-up(sm) {\n column-count: $card-columns-count;\n column-gap: $card-columns-gap;\n orphans: 1;\n widows: 1;\n\n .card {\n display: inline-block; // Don't let them vertically span multiple columns\n width: 100%; // Don't let their width change\n }\n }\n}\n\n\n//\n// Accordion\n//\n\n.accordion {\n .card {\n overflow: hidden;\n\n &:not(:first-of-type) {\n .card-header:first-child {\n border-radius: 0;\n }\n\n &:not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n }\n }\n\n &:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n }\n\n &:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n }\n\n .card-header {\n margin-bottom: -$card-border-width;\n }\n }\n}\n",".breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: $breadcrumb-padding-y $breadcrumb-padding-x;\n margin-bottom: $breadcrumb-margin-bottom;\n list-style: none;\n background-color: $breadcrumb-bg;\n @include border-radius($breadcrumb-border-radius);\n}\n\n.breadcrumb-item {\n // The separator between breadcrumbs (by default, a forward-slash: \"/\")\n + .breadcrumb-item {\n padding-left: $breadcrumb-item-padding;\n\n &::before {\n display: inline-block; // Suppress underlining of the separator in modern browsers\n padding-right: $breadcrumb-item-padding;\n color: $breadcrumb-divider-color;\n content: $breadcrumb-divider;\n }\n }\n\n // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built\n // without `<ul>`s. The `::before` pseudo-element generates an element\n // *within* the .breadcrumb-item and thereby inherits the `text-decoration`.\n //\n // To trick IE into suppressing the underline, we give the pseudo-element an\n // underline and then immediately remove it.\n + .breadcrumb-item:hover::before {\n text-decoration: underline;\n }\n // stylelint-disable-next-line no-duplicate-selectors\n + .breadcrumb-item:hover::before {\n text-decoration: none;\n }\n\n &.active {\n color: $breadcrumb-active-color;\n }\n}\n",".pagination {\n display: flex;\n @include list-unstyled();\n @include border-radius();\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: $pagination-padding-y $pagination-padding-x;\n margin-left: -$pagination-border-width;\n line-height: $pagination-line-height;\n color: $pagination-color;\n background-color: $pagination-bg;\n border: $pagination-border-width solid $pagination-border-color;\n\n &:hover {\n z-index: 2;\n color: $pagination-hover-color;\n text-decoration: none;\n background-color: $pagination-hover-bg;\n border-color: $pagination-hover-border-color;\n }\n\n &:focus {\n z-index: 2;\n outline: $pagination-focus-outline;\n box-shadow: $pagination-focus-box-shadow;\n }\n\n // Opinionated: add \"hand\" cursor to non-disabled .page-link elements\n &:not(:disabled):not(.disabled) {\n cursor: pointer;\n }\n}\n\n.page-item {\n &:first-child {\n .page-link {\n margin-left: 0;\n @include border-left-radius($border-radius);\n }\n }\n &:last-child {\n .page-link {\n @include border-right-radius($border-radius);\n }\n }\n\n &.active .page-link {\n z-index: 1;\n color: $pagination-active-color;\n background-color: $pagination-active-bg;\n border-color: $pagination-active-border-color;\n }\n\n &.disabled .page-link {\n color: $pagination-disabled-color;\n pointer-events: none;\n // Opinionated: remove the \"hand\" cursor set previously for .page-link\n cursor: auto;\n background-color: $pagination-disabled-bg;\n border-color: $pagination-disabled-border-color;\n }\n}\n\n\n//\n// Sizing\n//\n\n.pagination-lg {\n @include pagination-size($pagination-padding-y-lg, $pagination-padding-x-lg, $font-size-lg, $line-height-lg, $border-radius-lg);\n}\n\n.pagination-sm {\n @include pagination-size($pagination-padding-y-sm, $pagination-padding-x-sm, $font-size-sm, $line-height-sm, $border-radius-sm);\n}\n","// Pagination\n\n@mixin pagination-size($padding-y, $padding-x, $font-size, $line-height, $border-radius) {\n .page-link {\n padding: $padding-y $padding-x;\n font-size: $font-size;\n line-height: $line-height;\n }\n\n .page-item {\n &:first-child {\n .page-link {\n @include border-left-radius($border-radius);\n }\n }\n &:last-child {\n .page-link {\n @include border-right-radius($border-radius);\n }\n }\n }\n}\n","// Base class\n//\n// Requires one of the contextual, color modifier classes for `color` and\n// `background-color`.\n\n.badge {\n display: inline-block;\n padding: $badge-padding-y $badge-padding-x;\n font-size: $badge-font-size;\n font-weight: $badge-font-weight;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n @include border-radius($badge-border-radius);\n\n @at-root a#{&} {\n @include hover-focus {\n text-decoration: none;\n }\n }\n\n // Empty badges collapse automatically\n &:empty {\n display: none;\n }\n}\n\n// Quick fix for badges in buttons\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n// Pill badges\n//\n// Make them extra rounded with a modifier to replace v3's badges.\n\n.badge-pill {\n padding-right: $badge-pill-padding-x;\n padding-left: $badge-pill-padding-x;\n @include border-radius($badge-pill-border-radius);\n}\n\n// Colors\n//\n// Contextual variations (linked badges get darker on :hover).\n\n@each $color, $value in $theme-colors {\n .badge-#{$color} {\n @include badge-variant($value);\n }\n}\n","@mixin badge-variant($bg) {\n color: color-yiq($bg);\n background-color: $bg;\n\n @at-root a#{&} {\n @include hover-focus {\n color: color-yiq($bg);\n background-color: darken($bg, 10%);\n }\n }\n}\n",".jumbotron {\n padding: $jumbotron-padding ($jumbotron-padding / 2);\n margin-bottom: $jumbotron-padding;\n background-color: $jumbotron-bg;\n @include border-radius($border-radius-lg);\n\n @include media-breakpoint-up(sm) {\n padding: ($jumbotron-padding * 2) $jumbotron-padding;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n @include border-radius(0);\n}\n","//\n// Base styles\n//\n\n.alert {\n position: relative;\n padding: $alert-padding-y $alert-padding-x;\n margin-bottom: $alert-margin-bottom;\n border: $alert-border-width solid transparent;\n @include border-radius($alert-border-radius);\n}\n\n// Headings for larger alerts\n.alert-heading {\n // Specified to prevent conflicts of changing $headings-color\n color: inherit;\n}\n\n// Provide class for links that match alerts\n.alert-link {\n font-weight: $alert-link-font-weight;\n}\n\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissible {\n padding-right: $close-font-size + $alert-padding-x * 2;\n\n // Adjust close link position\n .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: $alert-padding-y $alert-padding-x;\n color: inherit;\n }\n}\n\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n@each $color, $value in $theme-colors {\n .alert-#{$color} {\n @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level));\n }\n}\n","@mixin alert-variant($background, $border, $color) {\n color: $color;\n @include gradient-bg($background);\n border-color: $border;\n\n hr {\n border-top-color: darken($border, 5%);\n }\n\n .alert-link {\n color: darken($color, 10%);\n }\n}\n","@keyframes progress-bar-stripes {\n from { background-position: $progress-height 0; }\n to { background-position: 0 0; }\n}\n\n.progress {\n display: flex;\n height: $progress-height;\n overflow: hidden; // force rounded corners by cropping it\n font-size: $progress-font-size;\n background-color: $progress-bg;\n @include border-radius($progress-border-radius);\n @include box-shadow($progress-box-shadow);\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n color: $progress-bar-color;\n text-align: center;\n white-space: nowrap;\n background-color: $progress-bar-bg;\n @include transition($progress-bar-transition);\n}\n\n.progress-bar-striped {\n @include gradient-striped();\n background-size: $progress-height $progress-height;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes $progress-bar-animation-timing;\n}\n",".media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n","// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n display: flex;\n flex-direction: column;\n\n // No need to set list-style: none; since .list-group-item is block level\n padding-left: 0; // reset padding because ul and ol\n margin-bottom: 0;\n}\n\n\n// Interactive list items\n//\n// Use anchor or button elements instead of `li`s or `div`s to create interactive\n// list items. Includes an extra `.active` modifier class for selected items.\n\n.list-group-item-action {\n width: 100%; // For `<button>`s (anchors become 100% by default though)\n color: $list-group-action-color;\n text-align: inherit; // For `<button>`s (anchors inherit)\n\n // Hover state\n @include hover-focus {\n color: $list-group-action-hover-color;\n text-decoration: none;\n background-color: $list-group-hover-bg;\n }\n\n &:active {\n color: $list-group-action-active-color;\n background-color: $list-group-action-active-bg;\n }\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: $list-group-item-padding-y $list-group-item-padding-x;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -$list-group-border-width;\n background-color: $list-group-bg;\n border: $list-group-border-width solid $list-group-border-color;\n\n &:first-child {\n @include border-top-radius($list-group-border-radius);\n }\n\n &:last-child {\n margin-bottom: 0;\n @include border-bottom-radius($list-group-border-radius);\n }\n\n @include hover-focus {\n z-index: 1; // Place hover/active items above their siblings for proper border styling\n text-decoration: none;\n }\n\n &.disabled,\n &:disabled {\n color: $list-group-disabled-color;\n pointer-events: none;\n background-color: $list-group-disabled-bg;\n }\n\n // Include both here for `<a>`s and `<button>`s\n &.active {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: $list-group-active-color;\n background-color: $list-group-active-bg;\n border-color: $list-group-active-border-color;\n }\n}\n\n\n// Flush list items\n//\n// Remove borders and border-radius to keep list group items edge-to-edge. Most\n// useful within other components (e.g., cards).\n\n.list-group-flush {\n .list-group-item {\n border-right: 0;\n border-left: 0;\n @include border-radius(0);\n\n &:last-child {\n margin-bottom: -$list-group-border-width;\n }\n }\n\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n }\n }\n\n &:last-child {\n .list-group-item:last-child {\n margin-bottom: 0;\n border-bottom: 0;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n@each $color, $value in $theme-colors {\n @include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));\n}\n","// List Groups\n\n@mixin list-group-item-variant($state, $background, $color) {\n .list-group-item-#{$state} {\n color: $color;\n background-color: $background;\n\n &.list-group-item-action {\n @include hover-focus {\n color: $color;\n background-color: darken($background, 5%);\n }\n\n &.active {\n color: $white;\n background-color: $color;\n border-color: $color;\n }\n }\n }\n}\n",".close {\n float: right;\n font-size: $close-font-size;\n font-weight: $close-font-weight;\n line-height: 1;\n color: $close-color;\n text-shadow: $close-text-shadow;\n opacity: .5;\n\n // Override <a>'s hover style\n @include hover {\n color: $close-color;\n text-decoration: none;\n }\n\n &:not(:disabled):not(.disabled) {\n @include hover-focus {\n opacity: .75;\n }\n\n // Opinionated: add \"hand\" cursor to non-disabled .close elements\n cursor: pointer;\n }\n}\n\n// Additional properties for button version\n// iOS requires the button element instead of an anchor tag.\n// If you want the anchor version, it requires `href=\"#\"`.\n// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n\n// stylelint-disable-next-line selector-no-qualifying-type\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n appearance: none;\n}\n\n// Future-proof disabling of clicks on `<a>` elements\n\n// stylelint-disable-next-line selector-no-qualifying-type\na.close.disabled {\n pointer-events: none;\n}\n",".toast {\n max-width: $toast-max-width;\n overflow: hidden; // cheap rounded corners on nested items\n font-size: $toast-font-size; // knock it down to 14px\n background-color: $toast-background-color;\n background-clip: padding-box;\n border: $toast-border-width solid $toast-border-color;\n border-radius: $toast-border-radius;\n box-shadow: $toast-box-shadow;\n backdrop-filter: blur(10px);\n opacity: 0;\n\n &:not(:last-child) {\n margin-bottom: $toast-padding-x;\n }\n\n &.showing {\n opacity: 1;\n }\n\n &.show {\n display: block;\n opacity: 1;\n }\n\n &.hide {\n display: none;\n }\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: $toast-padding-y $toast-padding-x;\n color: $toast-header-color;\n background-color: $toast-header-background-color;\n background-clip: padding-box;\n border-bottom: $toast-border-width solid $toast-header-border-color;\n}\n\n.toast-body {\n padding: $toast-padding-x; // apply to both vertical and horizontal\n}\n","// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and stuff\n\n\n.modal-open {\n // Kill the scroll on the body\n overflow: hidden;\n\n .modal {\n overflow-x: hidden;\n overflow-y: auto;\n }\n}\n\n// Container that the modal scrolls within\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: $zindex-modal;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n // We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a\n // gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342\n // See also https://github.com/twbs/bootstrap/issues/17695\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: $modal-dialog-margin;\n // allow clicks to pass through for custom click handling to close modal\n pointer-events: none;\n\n // When fading in the modal, animate it to slide down\n .modal.fade & {\n @include transition($modal-transition);\n transform: $modal-fade-transform;\n }\n .modal.show & {\n transform: $modal-show-transform;\n }\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - (#{$modal-dialog-margin} * 2));\n\n // Ensure `modal-dialog-centered` extends the full height of the view (IE10/11)\n &::before {\n display: block; // IE10\n height: calc(100vh - (#{$modal-dialog-margin} * 2));\n content: \"\";\n }\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%; // Ensure `.modal-content` extends the full width of the parent `.modal-dialog`\n // counteract the pointer-events: none; in the .modal-dialog\n pointer-events: auto;\n background-color: $modal-content-bg;\n background-clip: padding-box;\n border: $modal-content-border-width solid $modal-content-border-color;\n @include border-radius($modal-content-border-radius);\n @include box-shadow($modal-content-box-shadow-xs);\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: $zindex-modal-backdrop;\n width: 100vw;\n height: 100vh;\n background-color: $modal-backdrop-bg;\n\n // Fade for backdrop\n &.fade { opacity: 0; }\n &.show { opacity: $modal-backdrop-opacity; }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n display: flex;\n align-items: flex-start; // so the close btn always stays on the upper right corner\n justify-content: space-between; // Put modal header elements (title and dismiss) on opposite ends\n padding: $modal-header-padding;\n border-bottom: $modal-header-border-width solid $modal-header-border-color;\n @include border-top-radius($modal-content-border-radius);\n\n .close {\n padding: $modal-header-padding;\n // auto on the left force icon to the right even when there is no .modal-title\n margin: (-$modal-header-padding-y) (-$modal-header-padding-x) (-$modal-header-padding-y) auto;\n }\n}\n\n// Title text within header\n.modal-title {\n margin-bottom: 0;\n line-height: $modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n // Enable `flex-grow: 1` so that the body take up as much space as possible\n // when should there be a fixed height on `.modal-dialog`.\n flex: 1 1 auto;\n padding: $modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n display: flex;\n align-items: center; // vertically center\n justify-content: flex-end; // Right align buttons with flex property because text-align doesn't work on flex items\n padding: $modal-inner-padding;\n border-top: $modal-footer-border-width solid $modal-footer-border-color;\n @include border-bottom-radius($modal-content-border-radius);\n\n // Easily place margin between footer elements\n > :not(:first-child) { margin-left: .25rem; }\n > :not(:last-child) { margin-right: .25rem; }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@include media-breakpoint-up(sm) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n max-width: $modal-md;\n margin: $modal-dialog-margin-y-sm-up auto;\n }\n\n .modal-dialog-centered {\n min-height: calc(100% - (#{$modal-dialog-margin-y-sm-up} * 2));\n\n &::before {\n height: calc(100vh - (#{$modal-dialog-margin-y-sm-up} * 2));\n }\n }\n\n .modal-content {\n @include box-shadow($modal-content-box-shadow-sm-up);\n }\n\n .modal-sm { max-width: $modal-sm; }\n}\n\n@include media-breakpoint-up(lg) {\n .modal-lg,\n .modal-xl {\n max-width: $modal-lg;\n }\n}\n\n@include media-breakpoint-up(xl) {\n .modal-xl { max-width: $modal-xl; }\n}\n","// Base class\n.tooltip {\n position: absolute;\n z-index: $zindex-tooltip;\n display: block;\n margin: $tooltip-margin;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n @include reset-text();\n font-size: $tooltip-font-size;\n // Allow breaking very long words so they don't overflow the tooltip's bounds\n word-wrap: break-word;\n opacity: 0;\n\n &.show { opacity: $tooltip-opacity; }\n\n .arrow {\n position: absolute;\n display: block;\n width: $tooltip-arrow-width;\n height: $tooltip-arrow-height;\n\n &::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n }\n }\n}\n\n.bs-tooltip-top {\n padding: $tooltip-arrow-height 0;\n\n .arrow {\n bottom: 0;\n\n &::before {\n top: 0;\n border-width: $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;\n border-top-color: $tooltip-arrow-color;\n }\n }\n}\n\n.bs-tooltip-right {\n padding: 0 $tooltip-arrow-height;\n\n .arrow {\n left: 0;\n width: $tooltip-arrow-height;\n height: $tooltip-arrow-width;\n\n &::before {\n right: 0;\n border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;\n border-right-color: $tooltip-arrow-color;\n }\n }\n}\n\n.bs-tooltip-bottom {\n padding: $tooltip-arrow-height 0;\n\n .arrow {\n top: 0;\n\n &::before {\n bottom: 0;\n border-width: 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;\n border-bottom-color: $tooltip-arrow-color;\n }\n }\n}\n\n.bs-tooltip-left {\n padding: 0 $tooltip-arrow-height;\n\n .arrow {\n right: 0;\n width: $tooltip-arrow-height;\n height: $tooltip-arrow-width;\n\n &::before {\n left: 0;\n border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;\n border-left-color: $tooltip-arrow-color;\n }\n }\n}\n\n.bs-tooltip-auto {\n &[x-placement^=\"top\"] {\n @extend .bs-tooltip-top;\n }\n &[x-placement^=\"right\"] {\n @extend .bs-tooltip-right;\n }\n &[x-placement^=\"bottom\"] {\n @extend .bs-tooltip-bottom;\n }\n &[x-placement^=\"left\"] {\n @extend .bs-tooltip-left;\n }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: $tooltip-max-width;\n padding: $tooltip-padding-y $tooltip-padding-x;\n color: $tooltip-color;\n text-align: center;\n background-color: $tooltip-bg;\n @include border-radius($tooltip-border-radius);\n}\n","@mixin reset-text {\n font-family: $font-family-base;\n // We deliberately do NOT reset font-size or word-wrap.\n font-style: normal;\n font-weight: $font-weight-normal;\n line-height: $line-height-base;\n text-align: left; // Fallback for where `start` is not supported\n text-align: start; // stylelint-disable-line declaration-block-no-duplicate-properties\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n}\n",".popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: $zindex-popover;\n display: block;\n max-width: $popover-max-width;\n // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element.\n // So reset our font and text properties to avoid inheriting weird values.\n @include reset-text();\n font-size: $popover-font-size;\n // Allow breaking very long words so they don't overflow the popover's bounds\n word-wrap: break-word;\n background-color: $popover-bg;\n background-clip: padding-box;\n border: $popover-border-width solid $popover-border-color;\n @include border-radius($popover-border-radius);\n @include box-shadow($popover-box-shadow);\n\n .arrow {\n position: absolute;\n display: block;\n width: $popover-arrow-width;\n height: $popover-arrow-height;\n margin: 0 $border-radius-lg;\n\n &::before,\n &::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n }\n }\n}\n\n.bs-popover-top {\n margin-bottom: $popover-arrow-height;\n\n .arrow {\n bottom: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);\n }\n\n .arrow::before,\n .arrow::after {\n border-width: $popover-arrow-height ($popover-arrow-width / 2) 0;\n }\n\n .arrow::before {\n bottom: 0;\n border-top-color: $popover-arrow-outer-color;\n }\n\n .arrow::after {\n bottom: $popover-border-width;\n border-top-color: $popover-arrow-color;\n }\n}\n\n.bs-popover-right {\n margin-left: $popover-arrow-height;\n\n .arrow {\n left: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);\n width: $popover-arrow-height;\n height: $popover-arrow-width;\n margin: $border-radius-lg 0; // make sure the arrow does not touch the popover's rounded corners\n }\n\n .arrow::before,\n .arrow::after {\n border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;\n }\n\n .arrow::before {\n left: 0;\n border-right-color: $popover-arrow-outer-color;\n }\n\n .arrow::after {\n left: $popover-border-width;\n border-right-color: $popover-arrow-color;\n }\n}\n\n.bs-popover-bottom {\n margin-top: $popover-arrow-height;\n\n .arrow {\n top: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);\n }\n\n .arrow::before,\n .arrow::after {\n border-width: 0 ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2);\n }\n\n .arrow::before {\n top: 0;\n border-bottom-color: $popover-arrow-outer-color;\n }\n\n .arrow::after {\n top: $popover-border-width;\n border-bottom-color: $popover-arrow-color;\n }\n\n // This will remove the popover-header's border just below the arrow\n .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: $popover-arrow-width;\n margin-left: -$popover-arrow-width / 2;\n content: \"\";\n border-bottom: $popover-border-width solid $popover-header-bg;\n }\n}\n\n.bs-popover-left {\n margin-right: $popover-arrow-height;\n\n .arrow {\n right: calc((#{$popover-arrow-height} + #{$popover-border-width}) * -1);\n width: $popover-arrow-height;\n height: $popover-arrow-width;\n margin: $border-radius-lg 0; // make sure the arrow does not touch the popover's rounded corners\n }\n\n .arrow::before,\n .arrow::after {\n border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;\n }\n\n .arrow::before {\n right: 0;\n border-left-color: $popover-arrow-outer-color;\n }\n\n .arrow::after {\n right: $popover-border-width;\n border-left-color: $popover-arrow-color;\n }\n}\n\n.bs-popover-auto {\n &[x-placement^=\"top\"] {\n @extend .bs-popover-top;\n }\n &[x-placement^=\"right\"] {\n @extend .bs-popover-right;\n }\n &[x-placement^=\"bottom\"] {\n @extend .bs-popover-bottom;\n }\n &[x-placement^=\"left\"] {\n @extend .bs-popover-left;\n }\n}\n\n\n// Offset the popover to account for the popover arrow\n.popover-header {\n padding: $popover-header-padding-y $popover-header-padding-x;\n margin-bottom: 0; // Reset the default from Reboot\n font-size: $font-size-base;\n color: $popover-header-color;\n background-color: $popover-header-bg;\n border-bottom: $popover-border-width solid darken($popover-header-bg, 5%);\n $offset-border-width: calc(#{$border-radius-lg} - #{$popover-border-width});\n @include border-top-radius($offset-border-width);\n\n &:empty {\n display: none;\n }\n}\n\n.popover-body {\n padding: $popover-body-padding-y $popover-body-padding-x;\n color: $popover-body-color;\n}\n","// Notes on the classes:\n//\n// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)\n// even when their scroll action started on a carousel, but for compatibility (with Firefox)\n// we're preventing all actions instead\n// 2. The .carousel-item-left and .carousel-item-right is used to indicate where\n// the active slide is heading.\n// 3. .active.carousel-item is the current slide.\n// 4. .active.carousel-item-left and .active.carousel-item-right is the current\n// slide in its in-transition state. Only one of these occurs at a time.\n// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right\n// is the upcoming slide in transition.\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n @include clearfix();\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n backface-visibility: hidden;\n @include transition($carousel-transition);\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n\n//\n// Alternate transitions\n//\n\n.carousel-fade {\n .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n }\n\n .carousel-item.active,\n .carousel-item-next.carousel-item-left,\n .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n }\n\n .active.carousel-item-left,\n .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n @include transition(0s $carousel-transition-duration opacity);\n }\n}\n\n\n//\n// Left/right controls for nav\n//\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n // Use flex for alignment (1-3)\n display: flex; // 1. allow flex styles\n align-items: center; // 2. vertically center contents\n justify-content: center; // 3. horizontally center contents\n width: $carousel-control-width;\n color: $carousel-control-color;\n text-align: center;\n opacity: $carousel-control-opacity;\n @include transition($carousel-control-transition);\n\n // Hover/focus state\n @include hover-focus {\n color: $carousel-control-color;\n text-decoration: none;\n outline: 0;\n opacity: $carousel-control-hover-opacity;\n }\n}\n.carousel-control-prev {\n left: 0;\n @if $enable-gradients {\n background: linear-gradient(90deg, rgba($black, .25), rgba($black, .001));\n }\n}\n.carousel-control-next {\n right: 0;\n @if $enable-gradients {\n background: linear-gradient(270deg, rgba($black, .25), rgba($black, .001));\n }\n}\n\n// Icons for within\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: $carousel-control-icon-width;\n height: $carousel-control-icon-width;\n background: transparent no-repeat center center;\n background-size: 100% 100%;\n}\n.carousel-control-prev-icon {\n background-image: $carousel-control-prev-icon-bg;\n}\n.carousel-control-next-icon {\n background-image: $carousel-control-next-icon-bg;\n}\n\n\n// Optional indicator pips\n//\n// Add an ordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0; // override <ol> default\n // Use the .carousel-control's width as margin so we don't overlay those\n margin-right: $carousel-control-width;\n margin-left: $carousel-control-width;\n list-style: none;\n\n li {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: $carousel-indicator-width;\n height: $carousel-indicator-height;\n margin-right: $carousel-indicator-spacer;\n margin-left: $carousel-indicator-spacer;\n text-indent: -999px;\n cursor: pointer;\n background-color: $carousel-indicator-active-bg;\n background-clip: padding-box;\n // Use transparent borders to increase the hit area by 10px on top and bottom.\n border-top: $carousel-indicator-hit-area-height solid transparent;\n border-bottom: $carousel-indicator-hit-area-height solid transparent;\n opacity: .5;\n @include transition($carousel-indicator-transition);\n }\n\n .active {\n opacity: 1;\n }\n}\n\n\n// Optional captions\n//\n//\n\n.carousel-caption {\n position: absolute;\n right: (100% - $carousel-caption-width) / 2;\n bottom: 20px;\n left: (100% - $carousel-caption-width) / 2;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: $carousel-caption-color;\n text-align: center;\n}\n","@mixin clearfix() {\n &::after {\n display: block;\n clear: both;\n content: \"\";\n }\n}\n","//\n// Rotating border\n//\n\n@keyframes spinner-border {\n to { transform: rotate(360deg); }\n}\n\n.spinner-border {\n display: inline-block;\n width: $spinner-width;\n height: $spinner-height;\n vertical-align: text-bottom;\n border: $spinner-border-width solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: $spinner-width-sm;\n height: $spinner-height-sm;\n border-width: $spinner-border-width-sm;\n}\n\n//\n// Growing circle\n//\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: $spinner-width;\n height: $spinner-height;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: $spinner-width-sm;\n height: $spinner-height-sm;\n}\n","// stylelint-disable declaration-no-important\n\n.align-baseline { vertical-align: baseline !important; } // Browser default\n.align-top { vertical-align: top !important; }\n.align-middle { vertical-align: middle !important; }\n.align-bottom { vertical-align: bottom !important; }\n.align-text-bottom { vertical-align: text-bottom !important; }\n.align-text-top { vertical-align: text-top !important; }\n","// stylelint-disable declaration-no-important\n\n// Contextual backgrounds\n\n@mixin bg-variant($parent, $color) {\n #{$parent} {\n background-color: $color !important;\n }\n a#{$parent},\n button#{$parent} {\n @include hover-focus {\n background-color: darken($color, 10%) !important;\n }\n }\n}\n\n@mixin bg-gradient-variant($parent, $color) {\n #{$parent} {\n background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x !important;\n }\n}\n","// stylelint-disable declaration-no-important\n\n@each $color, $value in $theme-colors {\n @include bg-variant(\".bg-#{$color}\", $value);\n}\n\n@if $enable-gradients {\n @each $color, $value in $theme-colors {\n @include bg-gradient-variant(\".bg-gradient-#{$color}\", $value);\n }\n}\n\n.bg-white {\n background-color: $white !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n","// stylelint-disable declaration-no-important\n\n//\n// Border\n//\n\n.border { border: $border-width solid $border-color !important; }\n.border-top { border-top: $border-width solid $border-color !important; }\n.border-right { border-right: $border-width solid $border-color !important; }\n.border-bottom { border-bottom: $border-width solid $border-color !important; }\n.border-left { border-left: $border-width solid $border-color !important; }\n\n.border-0 { border: 0 !important; }\n.border-top-0 { border-top: 0 !important; }\n.border-right-0 { border-right: 0 !important; }\n.border-bottom-0 { border-bottom: 0 !important; }\n.border-left-0 { border-left: 0 !important; }\n\n@each $color, $value in $theme-colors {\n .border-#{$color} {\n border-color: $value !important;\n }\n}\n\n.border-white {\n border-color: $white !important;\n}\n\n//\n// Border-radius\n//\n\n.rounded {\n border-radius: $border-radius !important;\n}\n.rounded-top {\n border-top-left-radius: $border-radius !important;\n border-top-right-radius: $border-radius !important;\n}\n.rounded-right {\n border-top-right-radius: $border-radius !important;\n border-bottom-right-radius: $border-radius !important;\n}\n.rounded-bottom {\n border-bottom-right-radius: $border-radius !important;\n border-bottom-left-radius: $border-radius !important;\n}\n.rounded-left {\n border-top-left-radius: $border-radius !important;\n border-bottom-left-radius: $border-radius !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: $rounded-pill !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n","// stylelint-disable declaration-no-important\n\n//\n// Utilities for common `display` values\n//\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .d#{$infix}-none { display: none !important; }\n .d#{$infix}-inline { display: inline !important; }\n .d#{$infix}-inline-block { display: inline-block !important; }\n .d#{$infix}-block { display: block !important; }\n .d#{$infix}-table { display: table !important; }\n .d#{$infix}-table-row { display: table-row !important; }\n .d#{$infix}-table-cell { display: table-cell !important; }\n .d#{$infix}-flex { display: flex !important; }\n .d#{$infix}-inline-flex { display: inline-flex !important; }\n }\n}\n\n\n//\n// Utilities for toggling `display` in print\n//\n\n@media print {\n .d-print-none { display: none !important; }\n .d-print-inline { display: inline !important; }\n .d-print-inline-block { display: inline-block !important; }\n .d-print-block { display: block !important; }\n .d-print-table { display: table !important; }\n .d-print-table-row { display: table-row !important; }\n .d-print-table-cell { display: table-cell !important; }\n .d-print-flex { display: flex !important; }\n .d-print-inline-flex { display: inline-flex !important; }\n}\n","// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n\n &::before {\n display: block;\n content: \"\";\n }\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n }\n}\n\n@each $embed-responsive-aspect-ratio in $embed-responsive-aspect-ratios {\n $embed-responsive-aspect-ratio-x: nth($embed-responsive-aspect-ratio, 1);\n $embed-responsive-aspect-ratio-y: nth($embed-responsive-aspect-ratio, 2);\n\n .embed-responsive-#{$embed-responsive-aspect-ratio-x}by#{$embed-responsive-aspect-ratio-y} {\n &::before {\n padding-top: percentage($embed-responsive-aspect-ratio-y / $embed-responsive-aspect-ratio-x);\n }\n }\n}\n","// stylelint-disable declaration-no-important\n\n// Flex variation\n//\n// Custom styles for additional flex alignment options.\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .flex#{$infix}-row { flex-direction: row !important; }\n .flex#{$infix}-column { flex-direction: column !important; }\n .flex#{$infix}-row-reverse { flex-direction: row-reverse !important; }\n .flex#{$infix}-column-reverse { flex-direction: column-reverse !important; }\n\n .flex#{$infix}-wrap { flex-wrap: wrap !important; }\n .flex#{$infix}-nowrap { flex-wrap: nowrap !important; }\n .flex#{$infix}-wrap-reverse { flex-wrap: wrap-reverse !important; }\n .flex#{$infix}-fill { flex: 1 1 auto !important; }\n .flex#{$infix}-grow-0 { flex-grow: 0 !important; }\n .flex#{$infix}-grow-1 { flex-grow: 1 !important; }\n .flex#{$infix}-shrink-0 { flex-shrink: 0 !important; }\n .flex#{$infix}-shrink-1 { flex-shrink: 1 !important; }\n\n .justify-content#{$infix}-start { justify-content: flex-start !important; }\n .justify-content#{$infix}-end { justify-content: flex-end !important; }\n .justify-content#{$infix}-center { justify-content: center !important; }\n .justify-content#{$infix}-between { justify-content: space-between !important; }\n .justify-content#{$infix}-around { justify-content: space-around !important; }\n\n .align-items#{$infix}-start { align-items: flex-start !important; }\n .align-items#{$infix}-end { align-items: flex-end !important; }\n .align-items#{$infix}-center { align-items: center !important; }\n .align-items#{$infix}-baseline { align-items: baseline !important; }\n .align-items#{$infix}-stretch { align-items: stretch !important; }\n\n .align-content#{$infix}-start { align-content: flex-start !important; }\n .align-content#{$infix}-end { align-content: flex-end !important; }\n .align-content#{$infix}-center { align-content: center !important; }\n .align-content#{$infix}-between { align-content: space-between !important; }\n .align-content#{$infix}-around { align-content: space-around !important; }\n .align-content#{$infix}-stretch { align-content: stretch !important; }\n\n .align-self#{$infix}-auto { align-self: auto !important; }\n .align-self#{$infix}-start { align-self: flex-start !important; }\n .align-self#{$infix}-end { align-self: flex-end !important; }\n .align-self#{$infix}-center { align-self: center !important; }\n .align-self#{$infix}-baseline { align-self: baseline !important; }\n .align-self#{$infix}-stretch { align-self: stretch !important; }\n }\n}\n","@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .float#{$infix}-left { @include float-left; }\n .float#{$infix}-right { @include float-right; }\n .float#{$infix}-none { @include float-none; }\n }\n}\n","// stylelint-disable declaration-no-important\n\n@mixin float-left {\n float: left !important;\n}\n@mixin float-right {\n float: right !important;\n}\n@mixin float-none {\n float: none !important;\n}\n","// stylelint-disable declaration-no-important\n\n@each $value in $overflows {\n .overflow-#{$value} { overflow: $value !important; }\n}\n","// stylelint-disable declaration-no-important\n\n// Common values\n@each $position in $positions {\n .position-#{$position} { position: $position !important; }\n}\n\n// Shorthand\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: $zindex-fixed;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: $zindex-fixed;\n}\n\n.sticky-top {\n @supports (position: sticky) {\n position: sticky;\n top: 0;\n z-index: $zindex-sticky;\n }\n}\n","//\n// Screenreaders\n//\n\n.sr-only {\n @include sr-only();\n}\n\n.sr-only-focusable {\n @include sr-only-focusable();\n}\n","// Only display content to screen readers\n//\n// See: https://a11yproject.com/posts/how-to-hide-content/\n// See: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/\n\n@mixin sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n//\n// Useful for \"Skip to main content\" links; see https://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n//\n// Credit: HTML5 Boilerplate\n\n@mixin sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n }\n}\n","// stylelint-disable declaration-no-important\n\n.shadow-sm { box-shadow: $box-shadow-sm !important; }\n.shadow { box-shadow: $box-shadow !important; }\n.shadow-lg { box-shadow: $box-shadow-lg !important; }\n.shadow-none { box-shadow: none !important; }\n","// stylelint-disable declaration-no-important\n\n// Width and height\n\n@each $prop, $abbrev in (width: w, height: h) {\n @each $size, $length in $sizes {\n .#{$abbrev}-#{$size} { #{$prop}: $length !important; }\n }\n}\n\n.mw-100 { max-width: 100% !important; }\n.mh-100 { max-height: 100% !important; }\n\n// Viewport additional helpers\n\n.min-vw-100 { min-width: 100vw !important; }\n.min-vh-100 { min-height: 100vh !important; }\n\n.vw-100 { width: 100vw !important; }\n.vh-100 { height: 100vh !important; }\n","// stylelint-disable declaration-no-important\n\n// Margin and Padding\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n @each $prop, $abbrev in (margin: m, padding: p) {\n @each $size, $length in $spacers {\n .#{$abbrev}#{$infix}-#{$size} { #{$prop}: $length !important; }\n .#{$abbrev}t#{$infix}-#{$size},\n .#{$abbrev}y#{$infix}-#{$size} {\n #{$prop}-top: $length !important;\n }\n .#{$abbrev}r#{$infix}-#{$size},\n .#{$abbrev}x#{$infix}-#{$size} {\n #{$prop}-right: $length !important;\n }\n .#{$abbrev}b#{$infix}-#{$size},\n .#{$abbrev}y#{$infix}-#{$size} {\n #{$prop}-bottom: $length !important;\n }\n .#{$abbrev}l#{$infix}-#{$size},\n .#{$abbrev}x#{$infix}-#{$size} {\n #{$prop}-left: $length !important;\n }\n }\n }\n\n // Negative margins (e.g., where `.mb-n1` is negative version of `.mb-1`)\n @each $size, $length in $spacers {\n @if $size != 0 {\n .m#{$infix}-n#{$size} { margin: -$length !important; }\n .mt#{$infix}-n#{$size},\n .my#{$infix}-n#{$size} {\n margin-top: -$length !important;\n }\n .mr#{$infix}-n#{$size},\n .mx#{$infix}-n#{$size} {\n margin-right: -$length !important;\n }\n .mb#{$infix}-n#{$size},\n .my#{$infix}-n#{$size} {\n margin-bottom: -$length !important;\n }\n .ml#{$infix}-n#{$size},\n .mx#{$infix}-n#{$size} {\n margin-left: -$length !important;\n }\n }\n }\n\n // Some special margin utils\n .m#{$infix}-auto { margin: auto !important; }\n .mt#{$infix}-auto,\n .my#{$infix}-auto {\n margin-top: auto !important;\n }\n .mr#{$infix}-auto,\n .mx#{$infix}-auto {\n margin-right: auto !important;\n }\n .mb#{$infix}-auto,\n .my#{$infix}-auto {\n margin-bottom: auto !important;\n }\n .ml#{$infix}-auto,\n .mx#{$infix}-auto {\n margin-left: auto !important;\n }\n }\n}\n","// stylelint-disable declaration-no-important\n\n//\n// Text\n//\n\n.text-monospace { font-family: $font-family-monospace; }\n\n// Alignment\n\n.text-justify { text-align: justify !important; }\n.text-wrap { white-space: normal !important; }\n.text-nowrap { white-space: nowrap !important; }\n.text-truncate { @include text-truncate; }\n\n// Responsive alignment\n\n@each $breakpoint in map-keys($grid-breakpoints) {\n @include media-breakpoint-up($breakpoint) {\n $infix: breakpoint-infix($breakpoint, $grid-breakpoints);\n\n .text#{$infix}-left { text-align: left !important; }\n .text#{$infix}-right { text-align: right !important; }\n .text#{$infix}-center { text-align: center !important; }\n }\n}\n\n// Transformation\n\n.text-lowercase { text-transform: lowercase !important; }\n.text-uppercase { text-transform: uppercase !important; }\n.text-capitalize { text-transform: capitalize !important; }\n\n// Weight and italics\n\n.font-weight-light { font-weight: $font-weight-light !important; }\n.font-weight-lighter { font-weight: $font-weight-lighter !important; }\n.font-weight-normal { font-weight: $font-weight-normal !important; }\n.font-weight-bold { font-weight: $font-weight-bold !important; }\n.font-weight-bolder { font-weight: $font-weight-bolder !important; }\n.font-italic { font-style: italic !important; }\n\n// Contextual colors\n\n.text-white { color: $white !important; }\n\n@each $color, $value in $theme-colors {\n @include text-emphasis-variant(\".text-#{$color}\", $value);\n}\n\n.text-body { color: $body-color !important; }\n.text-muted { color: $text-muted !important; }\n\n.text-black-50 { color: rgba($black, .5) !important; }\n.text-white-50 { color: rgba($white, .5) !important; }\n\n// Misc\n\n.text-hide {\n @include text-hide($ignore-warning: true);\n}\n\n.text-decoration-none { text-decoration: none !important; }\n\n// Reset\n\n.text-reset { color: inherit !important; }\n","// Text truncate\n// Requires inline-block or block for proper styling\n\n@mixin text-truncate() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","// stylelint-disable declaration-no-important\n\n// Typography\n\n@mixin text-emphasis-variant($parent, $color) {\n #{$parent} {\n color: $color !important;\n }\n a#{$parent} {\n @include hover-focus {\n color: darken($color, $emphasized-link-hover-darken-percentage) !important;\n }\n }\n}\n","// CSS image replacement\n@mixin text-hide($ignore-warning: false) {\n // stylelint-disable-next-line font-family-no-missing-generic-family-keyword\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n\n @if ($ignore-warning != true) {\n @warn \"The `text-hide()` mixin has been deprecated as of v4.1.0. It will be removed entirely in v5.\";\n }\n}\n","//\n// Visibility utilities\n//\n\n.visible {\n @include invisible(visible);\n}\n\n.invisible {\n @include invisible(hidden);\n}\n","// stylelint-disable declaration-no-important\n\n// Visibility\n\n@mixin invisible($visibility) {\n visibility: $visibility !important;\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type\n\n// Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request:\n// https://www.phpied.com/delay-loading-your-print-css/\n// ==========================================================================\n\n@if $enable-print-styles {\n @media print {\n *,\n *::before,\n *::after {\n // Bootstrap specific; comment out `color` and `background`\n //color: $black !important; // Black prints faster\n text-shadow: none !important;\n //background: transparent !important;\n box-shadow: none !important;\n }\n\n a {\n &:not(.btn) {\n text-decoration: underline;\n }\n }\n\n // Bootstrap specific; comment the following selector out\n //a[href]::after {\n // content: \" (\" attr(href) \")\";\n //}\n\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n\n // Bootstrap specific; comment the following selector out\n //\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n //\n\n //a[href^=\"#\"]::after,\n //a[href^=\"javascript:\"]::after {\n // content: \"\";\n //}\n\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: $border-width solid $gray-500; // Bootstrap custom code; using `$border-width` instead of 1px\n page-break-inside: avoid;\n }\n\n //\n // Printing Tables:\n // http://css-discuss.incutio.com/wiki/Printing_Tables\n //\n\n thead {\n display: table-header-group;\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n\n // Specify a size and min-width to make printing closer across browsers.\n // We don't set margin here because it breaks `size` in Chrome. We also\n // don't use `!important` on `size` as it breaks in Chrome.\n @page {\n size: $print-page-size;\n }\n body {\n min-width: $print-body-min-width !important;\n }\n .container {\n min-width: $print-body-min-width !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .badge {\n border: $border-width solid $black;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: $white !important;\n }\n }\n\n .table-bordered {\n th,\n td {\n border: 1px solid $gray-300 !important;\n }\n }\n\n .table-dark {\n color: inherit;\n\n th,\n td,\n thead th,\n tbody + tbody {\n border-color: $table-border-color;\n }\n }\n\n .table .thead-dark th {\n color: inherit;\n border-color: $table-border-color;\n }\n\n // Bootstrap specific changes end\n }\n}\n"]} \ No newline at end of file diff --git a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css b/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css deleted file mode 100644 index edd61c64cb..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css +++ /dev/null @@ -1,3 +0,0 @@ -body{background-color:#323238;color:#eeeeee}.pane{display:none}.dark>.list-group-item{background:#454747}#output{background:#212121;color:#aaaaaa;font-family:monospace;padding:2em;font-weight:bold;min-height:6em;margin-top:1em}.small-font{font-size:0.85em} - -/*# sourceMappingURL=renamer.min.css.map */ \ No newline at end of file diff --git a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css.map b/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css.map deleted file mode 100644 index f21fefc948..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.min.css.map +++ /dev/null @@ -1,9 +0,0 @@ -{ - "version": 3, - "file": "renamer.min.css", - "sources": [ - "renamer.scss" - ], - "names": [], - "mappings": "AAAA,AAAA,IAAI,AAAC,CACH,gBAAgB,CAAE,OAAO,CACzB,KAAK,CAAE,OAAO,CACf,AAED,AAAA,KAAK,AAAC,CACJ,OAAO,CAAE,IAAI,CACd,AAED,AAAA,KAAK,CAAG,gBAAgB,AAAC,CACvB,UAAU,CAAE,OAAO,CACpB,AAED,AAAA,OAAO,AAAC,CACN,UAAU,CAAE,OAAO,CACnB,KAAK,CAAE,OAAO,CACd,WAAW,CAAE,SAAS,CACtB,OAAO,CAAE,GAAG,CACZ,WAAW,CAAE,IAAI,CACjB,UAAU,CAAE,GAAG,CACf,UAAU,CAAE,GAAG,CAChB,AAED,AAAA,WAAW,AAAC,CACV,SAAS,CAAE,MAAM,CAClB" -} \ No newline at end of file diff --git a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.scss b/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.scss deleted file mode 100644 index fd19266a9b..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/css/renamer.scss +++ /dev/null @@ -1,26 +0,0 @@ -body { - background-color: #323238; - color: #eeeeee; -} - -.pane { - display: none; -} - -.dark > .list-group-item { - background: #454747; -} - -#output { - background: #212121; - color: #aaaaaa; - font-family: monospace; - padding: 2em; - font-weight: bold; - min-height: 6em; - margin-top: 1em; -} - -.small-font { - font-size: 0.85em; -} diff --git a/pype/hosts/premiere/extensions/com.pype.rename/index.html b/pype/hosts/premiere/extensions/com.pype.rename/index.html deleted file mode 100644 index 08756e2829..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/index.html +++ /dev/null @@ -1,205 +0,0 @@ -<!DOCTYPE html> -<!-- - - -. == [ part 0f PyPE CluB ] == .- -_______________.___._____________________ -\______ \__ | |\______ \_ _____/ - | ___// | | | ___/| __)_ - | | \____ | | | | \ - |____| / ______| |____| /_______ / - \/ \/ - .. __/ CliP R3N4M3R \__ .. - ---> -<html lang="en"> - <head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> - <title>Pype Renamer - - - - - - - - - - - - - -
-
Clip renamer
- - -
-
-
- -
Sequential Rename with Hierarchy
-
-
- Folder -
- -
-
-
- Episode -
- -
-
-
- Sequence -
- -
-
- - -
-

Use tokens of above fields to reference its value.

-
-
- Pattern -
- -
-
-
- Start # / Increment -
- - -
- -
-
- -
Sequential Rename
-
-
- Pattern -
- -
-
-
- Start # / Increment -
- - -
-
- - -
-
- -
- -
Simple Rename
-

Use {shot} token to reference current clip name. -

-
- New Name -
- -
-
- -
- -
Find and replace
-
-
- Find -
- - -
- Replace -
-
-
- -
- -
Match sequence
-

Not implemented yet.

- -
-
- -
Clip Rename
-

This will rename clip to its filename

- -
-
- -
Change Case
-
-
- - -
- -
-
-
- - -
-
- -
- - - - diff --git a/pype/hosts/premiere/extensions/com.pype.rename/jsx/PPRO/Premiere.jsx b/pype/hosts/premiere/extensions/com.pype.rename/jsx/PPRO/Premiere.jsx deleted file mode 100644 index 6a34893a82..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/jsx/PPRO/Premiere.jsx +++ /dev/null @@ -1,2379 +0,0 @@ -/************************************************************************* - * ADOBE CONFIDENTIAL - * ___________________ - * - * Copyright 2014 Adobe - * All Rights Reserved. - * - * NOTICE: Adobe permits you to use, modify, and distribute this file in - * accordance with the terms of the Adobe license agreement accompanying - * it. If you have received this file from a source other than Adobe, - * then your use, modification, or distribution of it requires the prior - * written permission of Adobe. - **************************************************************************/ -//include "PPro_API_Constants.jsx" - -$._PPP_ = { - - createDeepFolderStructure: function (foldersArray, maxDepth) { - if (typeof foldersArray !== 'object' || foldersArray.length <= 0) { - throw new Error('No valid folders array was provided!'); - } - - // if the first folder already exists, throw error - for (var i = 0; i < app.project.rootItem.children.numItems; i++) { - var curChild = app.project.rootItem.children[i]; - if (curChild.type === ProjectItemType.BIN && curChild.name === foldersArray[0]) { - throw new Error('Folder with name "' + curChild.name + '" already exists!'); - } - } - - // create the deep folder structure - var currentBin = app.project.rootItem.createBin(foldersArray[0]); - for (var m = 1; m < foldersArray.length && m < maxDepth; i++) { - currentBin = currentBin.createBin(foldersArray[i]); - } - }, - - getVersionInfo: function () { - return 'PPro ' + app.version + 'x' + app.build; - }, - - getUserName: function () { - var homeDir = new File('~/'); - var userName = homeDir.displayName; - homeDir.close(); - return userName; - }, - - updateGrowingFile: function () { - var numItems = app.project.rootItem.children.numItems; - for (var i = 0; i < numItems; i++) { - var currentItem = app.project.rootItem.children[i]; - if (currentItem) { - currentItem.refreshMedia(); - } - } - }, - - getSep: function () { - if (Folder.fs == 'Macintosh') { - return '/'; - } else { - return '\\'; - } - }, - - saveProject: function () { - app.project.save(); - }, - - exportCurrentFrameAsPNG: function () { - app.enableQE(); - var activeSequence = qe.project.getActiveSequence(); // note: make sure a sequence is active in PPro UI - if (activeSequence) { - // Create a file name based on timecode of frame. - var time = activeSequence.CTI.timecode; // CTI = Current Time Indicator. - var removeThese = /:|;/ig; // Why? Because Windows chokes on colons. - time = time.replace(removeThese, '_'); - var outputPath = new File("~/Desktop"); - var outputFileName = outputPath.fsName + $._PPP_.getSep() + time + '___' + activeSequence.name; - activeSequence.exportFramePNG(time, outputFileName); - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - renameFootage: function () { - var item = app.project.rootItem.children[0]; // assumes the zero-th item in the project is footage. - if (item) { - item.name = item.name + ", updated by PProPanel."; - } else { - $._PPP_.updateEventPanel("No project items found."); - } - }, - - getActiveSequenceName: function () { - if (app.project.activeSequence) { - return app.project.activeSequence.name; - } else { - return "No active sequence."; - } - }, - - projectPanelSelectionChanged: function (projectItems, viewID) { - - var remainingArgs = projectItems.length; - var message = ""; - - if (remainingArgs) { - var message = remainingArgs + " items selected: "; - var view = viewID; - - // Concatenate selected project item names, into message. - for (var i = 0; i < projectItems.length; i++) { - message += projectItems[i].name; - remainingArgs--; - if (remainingArgs > 1) { - message += ', '; - } - if (remainingArgs === 1) { - message += ", and "; - } - if (remainingArgs === 0) { - message += "."; - } - } - } else { - message = '0 items selected.'; - } - app.setSDKEventMessage(message, 'info'); - }, - - registerProjectPanelSelectionChangedFxn: function () { - var success = app.bind("onSourceClipSelectedInProjectPanel", $._PPP_.projectPanelSelectionChanged); - }, - - saveCurrentProjectLayout: function () { - var currentProjPanelDisplay = app.project.getProjectPanelMetadata(); - if (currentProjPanelDisplay) { - var outFileName = 'Previous_Project_Panel_Display_Settings.xml'; - var actualProjectPath = new File(app.project.path); - var projDir = actualProjectPath.parent; - if (actualProjectPath) { - var completeOutputPath = projDir + $._PPP_.getSep() + outFileName; - var outFile = new File(completeOutputPath); - if (outFile) { - outFile.encoding = "UTF8"; - outFile.open("w", "TEXT", "????"); - outFile.write(currentProjPanelDisplay); - $._PPP_.updateEventPanel("Saved layout to next to the project."); - outFile.close(); - } - actualProjectPath.close(); - } - } else { - $._PPP_.updateEventPanel("Could not retrieve current project layout."); - } - }, - - setProjectPanelMeta: function () { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "XML files:*.xml"; - } - var fileToOpen = File.openDialog("Choose Project panel layout to open.", - filterString, - false); - if (fileToOpen) { - if (fileToOpen.fsName.indexOf('.xml')) { // We should really be more careful, but hey, it says it's XML! - fileToOpen.encoding = "UTF8"; - fileToOpen.open("r", "TEXT", "????"); - var fileContents = fileToOpen.read(); - if (fileContents) { - var setResult = app.project.setProjectPanelMetadata(fileContents); - if (setResult) { - $._PPP_.updateEventPanel("Could not update layout using " + fileToOpen.filename + "."); - } else { - $._PPP_.updateEventPanel("Updated layout from .xml file."); - } - } - } - } else { - $._PPP_.updateEventPanel("No valid layout file chosen."); - } - }, - - exportSequenceAsPrProj: function () { - var activeSequence = app.project.activeSequence; - if (activeSequence) { - var startTimeOffset = activeSequence.zeroPoint; - var prProjExtension = '.prproj'; - var outputName = activeSequence.name; - var outFolder = Folder.selectDialog(); - - if (outFolder) { - var completeOutputPath = outFolder.fsName + - $._PPP_.getSep() + - outputName + - prProjExtension; - - app.project.activeSequence.exportAsProject(completeOutputPath); - - $._PPP_.updateEventPanel("Exported " + app.project.activeSequence.name + " to " + completeOutputPath + "."); - } else { - $._PPP_.updateEventPanel("Could not find or create output folder."); - } - - // Here's how to import N sequences from a project. - // - // var seqIDsToBeImported = new Array; - // seqIDsToBeImported[0] = ID1; - // ... - // seqIDsToBeImported[N] = IDN; - // - //app.project.importSequences(pathToPrProj, seqIDsToBeImported); - - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - createSequenceMarkers: function () { - var activeSequence = app.project.activeSequence; - if (activeSequence) { - var markers = activeSequence.markers; - if (markers) { - var numMarkers = markers.numMarkers; - if (numMarkers > 0) { - var marker_index = 1; - for (var current_marker = markers.getFirstMarker(); current_marker !== undefined; current_marker = markers.getNextMarker(current_marker)) { - if (current_marker.name !== "") { - $._PPP_.updateEventPanel('Marker ' + marker_index + ' name = ' + current_marker.name + '.'); - } else { - $._PPP_.updateEventPanel('Marker ' + marker_index + ' has no name.'); - } - - if (current_marker.end.seconds > 0) { - $._PPP_.updateEventPanel('Marker ' + marker_index + ' duration = ' + (current_marker.end.seconds - current_marker.start.seconds) + ' seconds.'); - } else { - $._PPP_.updateEventPanel('Marker ' + marker_index + ' has no duration.'); - } - $._PPP_.updateEventPanel('Marker ' + marker_index + ' starts at ' + current_marker.start.seconds + ' seconds.'); - marker_index = marker_index + 1; - } - } - } - - var newCommentMarker = markers.createMarker(12.345); - newCommentMarker.name = 'Marker created by PProPanel.'; - newCommentMarker.comments = 'Here are some comments, inserted by PProPanel.'; - newCommentMarker.end = 15.6789; - - var newWebMarker = markers.createMarker(14.345); - newWebMarker.name = 'Web marker created by PProPanel.'; - newWebMarker.comments = 'Here are some comments, inserted by PProPanel.'; - newWebMarker.end = 17.6789; - newWebMarker.setTypeAsWebLink("http://www.adobe.com", "frame target"); - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - exportFCPXML: function () { - if (app.project.activeSequence) { - var projPath = new File(app.project.path); - var parentDir = projPath.parent; - var outputName = app.project.activeSequence.name; - var xmlExtension = '.xml'; - var outputPath = Folder.selectDialog("Choose the output directory"); - - if (outputPath) { - var completeOutputPath = outputPath.fsName + $._PPP_.getSep() + outputName + xmlExtension; - app.project.activeSequence.exportAsFinalCutProXML(completeOutputPath, 1); // 1 == suppress UI - var info = "Exported FCP XML for " + - app.project.activeSequence.name + - " to " + - completeOutputPath + - "."; - $._PPP_.updateEventPanel(info); - } else { - $._PPP_.updateEventPanel("No output path chosen."); - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - openInSource: function () { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - var fileToOpen = File.openDialog("Choose file to open.", - filterString, - false); - if (fileToOpen) { - app.sourceMonitor.openFilePath(fileToOpen.fsName); - app.sourceMonitor.play(1.73); // playback speed as float, 1.0 = normal speed forward - var position = app.sourceMonitor.getPosition(); // new in 13.0 - $._PPP_.updateEventPanel("Current Source monitor position: " + position.seconds + " seconds."); - fileToOpen.close(); - } else { - $._PPP_.updateEventPanel("No file chosen."); - } - }, - - searchForBinWithName: function (nameToFind) { - // deep-search a folder by name in project - var deepSearchBin = function (inFolder) { - if (inFolder && inFolder.name === nameToFind && inFolder.type === 2) { - return inFolder; - } else { - for (var i = 0; i < inFolder.children.numItems; i++) { - if (inFolder.children[i] && inFolder.children[i].type === 2) { - var foundBin = deepSearchBin(inFolder.children[i]); - if (foundBin) return foundBin; - } - } - } - return undefined; - }; - return deepSearchBin(app.project.rootItem); - }, - - importFiles: function () { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - if (app.project) { - var fileOrFilesToImport = File.openDialog("Choose files to import", // title - filterString, // filter available files? - true); // allow multiple? - if (fileOrFilesToImport) { - // We have an array of File objects; importFiles() takes an array of paths. - var importThese = []; - if (importThese) { - for (var i = 0; i < fileOrFilesToImport.length; i++) { - importThese[i] = fileOrFilesToImport[i].fsName; - } - app.project.importFiles(importThese, - 1, // suppress warnings - app.project.getInsertionBin(), - 0); // import as numbered stills - } - } else { - $._PPP_.updateEventPanel("No files to import."); - } - } - }, - - muteFun: function () { - if (app.project.activeSequence) { - for (var i = 0; i < app.project.activeSequence.audioTracks.numTracks; i++) { - var currentTrack = app.project.activeSequence.audioTracks[i]; - if (Math.random() > 0.5) { - currentTrack.setMute(!(currentTrack.isMuted())); - } - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - disableImportWorkspaceWithProjects: function () { - var prefToModify = 'FE.Prefs.ImportWorkspace'; - var appProperties = app.properties; - - if (appProperties) { - var propertyExists = app.properties.doesPropertyExist(prefToModify); - var propertyIsReadOnly = app.properties.isPropertyReadOnly(prefToModify); - var propertyValue = app.properties.getProperty(prefToModify); - - appProperties.setProperty(prefToModify, false, 1); // optional 3rd param : 0 = non-persistent, 1 = persistent (default) - var safetyCheck = app.properties.getProperty(prefToModify); - if (safetyCheck != propertyValue) { - $._PPP_.updateEventPanel("Changed \'Import Workspaces with Projects\' from " + propertyValue + " to " + safetyCheck + "."); - } - } else { - $._PPP_.updateEventPanel("Properties not found."); - } - }, - - turnOffStartDialog: function () { - var prefToModify = 'MZ.Prefs.ShowQuickstartDialog'; - var appProperties = app.properties; - if (appProperties) { - var propertyExists = app.properties.doesPropertyExist(prefToModify); - var propertyIsReadOnly = app.properties.isPropertyReadOnly(prefToModify); - var originalValue = app.properties.getProperty(prefToModify); - - appProperties.setProperty(prefToModify, false, 1, 1); // optional 4th param : 0 = non-persistent, 1 = persistent (default) - var safetyCheck = app.properties.getProperty(prefToModify); - if (safetyCheck != originalValue) { - $._PPP_.updateEventPanel("Start dialog now OFF. Enjoy!"); - } else { - $._PPP_.updateEventPanel("Start dialog was already OFF."); - } - } else { - $._PPP_.updateEventPanel("Properties not found."); - } - }, - - replaceMedia: function () { - - // Note: This method of changing paths for projectItems is from the time - // before PPro supported full-res AND proxy paths for each projectItem. - // This can still be used, and will change the hi-res projectItem path, but - // if your panel supports proxy workflows, it should rely instead upon - // projectItem.setProxyPath() instead. - - var firstProjectItem = app.project.rootItem.children[0]; - if (firstProjectItem) { - if (firstProjectItem.canChangeMediaPath()) { - - // NEW in 9.0: setScaleToFrameSize() ensures that for all clips created from this footage, - // auto scale to frame size will be ON, regardless of the current user preference. - // This is important for proxy workflows, to avoid mis-scaling upon replacement. - - // Addendum: This setting will be in effect the NEXT time the projectItem is added to a - // sequence; it will not affect or reinterpret clips from this projectItem, already in - // sequences. - - firstProjectItem.setScaleToFrameSize(); - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - var replacementMedia = File.openDialog("Choose new media file, for " + - firstProjectItem.name, - filterString, // file filter - false); // allow multiple? - - if (replacementMedia) { - var suppressWarnings = true; - firstProjectItem.name = replacementMedia.name + ", formerly known as " + firstProjectItem.name; - firstProjectItem.changeMediaPath(replacementMedia.fsName, suppressWarnings); // new in 12.1 - replacementMedia.close(); - } - } else { - $._PPP_.updateEventPanel("Couldn't change path of " + firstProjectItem.name + "."); - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - }, - - openProject: function () { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "Premiere Pro project files:*.prproj"; - } - var projToOpen = File.openDialog("Choose project:", - filterString, - false); - if ((projToOpen) && projToOpen.exists) { - app.openDocument(projToOpen.fsName, - 1, // suppress 'Convert Project' dialogs? - 1, // suppress 'Locate Files' dialogs? - 1); // suppress warning dialogs? - projToOpen.close(); - } - }, - - exportFramesForMarkers: function () { - app.enableQE(); - var activeSequence = app.project.activeSequence; - if (activeSequence) { - var markers = activeSequence.markers; - var markerCount = markers.numMarkers; - if (markerCount) { - var firstMarker = markers.getFirstMarker(); - activeSequence.setPlayerPosition(firstMarker.start.ticks); - $._PPP_.exportCurrentFrameAsPNG(); - - var previousMarker = 0; - if (firstMarker) { - for (var i = 0; i < markerCount; i++) { - if (i === 0) { - currentMarker = markers.getNextMarker(firstMarker); - } else { - currentMarker = markers.getNextMarker(previousMarker); - } - if (currentMarker) { - activeSequence.setPlayerPosition(currentMarker.start.ticks); - previousMarker = currentMarker; - $._PPP_.exportCurrentFrameAsPNG(); - } - } - } - } else { - $._PPP_.updateEventPanel("No markers applied to " + activeSequence.name + "."); - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - createSequence: function (name) { - var someID = "xyz123"; - var seqName = prompt('Name of sequence?', '<<>>', 'Sequence Naming Prompt'); - app.project.createNewSequence(seqName, someID); - }, - - createSequenceFromPreset: function (presetPath) { - app.enableQE(); - var seqName = prompt('Name of sequence?', '<<>>', 'Sequence Naming Prompt'); - if (seqName) { - qe.project.newSequence(seqName, presetPath); - } - }, - - transcode: function (outputPresetPath) { - app.encoder.bind('onEncoderJobComplete', $._PPP_.onEncoderJobComplete); - app.encoder.bind('onEncoderJobError', $._PPP_.onEncoderJobError); - app.encoder.bind('onEncoderJobProgress', $._PPP_.onEncoderJobProgress); - app.encoder.bind('onEncoderJobQueued', $._PPP_.onEncoderJobQueued); - app.encoder.bind('onEncoderJobCanceled', $._PPP_.onEncoderJobCanceled); - - var projRoot = app.project.rootItem.children; - - if (projRoot.numItems) { - var firstProjectItem = app.project.rootItem.children[0]; - if (firstProjectItem) { - - app.encoder.launchEncoder(); // This can take a while; let's get the ball rolling. - - var fileOutputPath = Folder.selectDialog("Choose the output directory"); - if (fileOutputPath) { - var outputName = firstProjectItem.name.search('[.]'); - if (outputName == -1) { - outputName = firstProjectItem.name.length; - } - outFileName = firstProjectItem.name.substr(0, outputName); - outFileName = outFileName.replace('/', '-'); - var completeOutputPath = fileOutputPath.fsName + $._PPP_.getSep() + outFileName + '.mxf'; - var removeFromQueue = true; - var rangeToEncode = app.encoder.ENCODE_IN_TO_OUT; - app.encoder.encodeProjectItem(firstProjectItem, - completeOutputPath, - outputPresetPath, - rangeToEncode, - removeFromQueue); - app.encoder.startBatch(); - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - } else { - $._PPP_.updateEventPanel("Project is empty."); - } - }, - - transcodeExternal: function (outputPresetPath) { - app.encoder.launchEncoder(); - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - var fileToTranscode = File.openDialog("Choose file to open.", - filterString, - false); - if (fileToTranscode) { - var fileOutputPath = Folder.selectDialog("Choose the output directory"); - if (fileOutputPath) { - - var srcInPoint = 1.0; // encode start time at 1s (optional--if omitted, encode entire file) - var srcOutPoint = 3.0; // encode stop time at 3s (optional--if omitted, encode entire file) - var removeFromQueue = false; - - var result = app.encoder.encodeFile(fileToTranscode.fsName, - fileOutputPath.fsName, - outputPresetPath, - removeFromQueue, - srcInPoint, - srcOutPoint); - } - } - }, - - render: function (outputPresetPath) { - app.enableQE(); - var activeSequence = qe.project.getActiveSequence(); // we use a QE DOM function, to determine the output extension. - if (activeSequence) { - app.encoder.launchEncoder(); // This can take a while; let's get the ball rolling. - - var timeSecs = activeSequence.CTI.secs; // Just for reference, here's how to access the CTI - var timeFrames = activeSequence.CTI.frames; // (Current Time Indicator), for the active sequence. - var timeTicks = activeSequence.CTI.ticks; - var timeString = activeSequence.CTI.timecode; - - var seqInPoint = app.project.activeSequence.getInPoint(); // new in 9.0 - var seqOutPoint = app.project.activeSequence.getOutPoint(); // new in 9.0 - - var seqInPointAsTime = app.project.activeSequence.getInPointAsTime(); // new in 12.0 - var seqOutPointAsTime = app.project.activeSequence.getOutPointAsTime(); // new in 12.0 - - var projPath = new File(app.project.path); - var outputPath = Folder.selectDialog("Choose the output directory"); - - if ((outputPath) && projPath.exists) { - var outPreset = new File(outputPresetPath); - if (outPreset.exists === true) { - var outputFormatExtension = activeSequence.getExportFileExtension(outPreset.fsName); - if (outputFormatExtension) { - var outputFilename = activeSequence.name + '.' + outputFormatExtension; - - var fullPathToFile = outputPath.fsName + - $._PPP_.getSep() + - activeSequence.name + - "." + - outputFormatExtension; - - var outFileTest = new File(fullPathToFile); - - if (outFileTest.exists) { - var destroyExisting = confirm("A file with that name already exists; overwrite?", false, "Are you sure...?"); - if (destroyExisting) { - outFileTest.remove(); - outFileTest.close(); - } - } - - app.encoder.bind('onEncoderJobComplete', $._PPP_.onEncoderJobComplete); - app.encoder.bind('onEncoderJobError', $._PPP_.onEncoderJobError); - app.encoder.bind('onEncoderJobProgress', $._PPP_.onEncoderJobProgress); - app.encoder.bind('onEncoderJobQueued', $._PPP_.onEncoderJobQueued); - app.encoder.bind('onEncoderJobCanceled', $._PPP_.onEncoderJobCanceled); - - - // use these 0 or 1 settings to disable some/all metadata creation. - - app.encoder.setSidecarXMPEnabled(0); - app.encoder.setEmbeddedXMPEnabled(0); - - /* - - For reference, here's how to export from within PPro (blocking further user interaction). - - var seq = app.project.activeSequence; - - if (seq) { - seq.exportAsMediaDirect(fullPathToFile, - outPreset.fsName, - app.encoder.ENCODE_WORKAREA); - - Bonus: Here's how to compute a sequence's duration, in ticks. 254016000000 ticks/second. - var sequenceDuration = app.project.activeSequence.end - app.project.activeSequence.zeroPoint; - } - - */ - - var jobID = app.encoder.encodeSequence(app.project.activeSequence, - fullPathToFile, - outPreset.fsName, - app.encoder.ENCODE_WORKAREA, - 1); // Remove from queue upon successful completion? - $._PPP_.updateEventPanel('jobID = ' + jobID); - outPreset.close(); - } - } else { - $._PPP_.updateEventPanel("Could not find output preset."); - } - } else { - $._PPP_.updateEventPanel("Could not find/create output path."); - } - projPath.close(); - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - saveProjectCopy: function () { - var sessionCounter = 1; - var originalPath = app.project.path; - var outputPath = Folder.selectDialog("Choose the output directory"); - - if (outputPath) { - var absPath = outputPath.fsName; - var outputName = String(app.project.name); - var array = outputName.split('.', 2); - - outputName = array[0] + sessionCounter + '.' + array[1]; - sessionCounter++; - - var fullOutPath = absPath + $._PPP_.getSep() + outputName; - - app.project.saveAs(fullOutPath); - - for (var a = 0; a < app.projects.numProjects; a++) { - var currentProject = app.projects[a]; - if (currentProject.path === fullOutPath) { - app.openDocument(originalPath); // Why first? So we don't frighten the user by making PPro's window disappear. :) - currentProject.closeDocument(); - } - } - } else { - $._PPP_.updateEventPanel("No output path chosen."); - } - }, - - mungeXMP: function () { - var projectItem = app.project.rootItem.children[0]; // assumes first item is footage. - if (projectItem) { - if (ExternalObject.AdobeXMPScript === undefined) { - ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); - } - if (ExternalObject.AdobeXMPScript !== undefined) { // safety-conscious! - - var xmpBlob = projectItem.getXMPMetadata(); - var xmp = new XMPMeta(xmpBlob); - var oldSceneVal = ""; - var oldDMCreatorVal = ""; - - if (xmp.doesPropertyExist(XMPConst.NS_DM, "scene") === true) { - var myScene = xmp.getProperty(XMPConst.NS_DM, "scene"); - oldSceneVal = myScene.value; - } - - if (xmp.doesPropertyExist(XMPConst.NS_DM, "creator") === true) { - var myCreator = xmp.getProperty(XMPConst.NS_DM, "creator"); - oldCreatorVal = myCreator.value; - } - - // Regardless of whether there WAS scene or creator data, set scene and creator data. - - xmp.setProperty(XMPConst.NS_DM, "scene", oldSceneVal + " Added by PProPanel sample!"); - xmp.setProperty(XMPConst.NS_DM, "creator", oldDMCreatorVal + " Added by PProPanel sample!"); - - // That was the NS_DM creator; here's the NS_DC creator. - - var creatorProp = "creator"; - var containsDMCreatorValue = xmp.doesPropertyExist(XMPConst.NS_DC, creatorProp); - var numCreatorValuesPresent = xmp.countArrayItems(XMPConst.NS_DC, creatorProp); - var CreatorsSeparatedBy4PoundSigns = ""; - - if (numCreatorValuesPresent > 0) { - for (var z = 0; z < numCreatorValuesPresent; z++) { - CreatorsSeparatedBy4PoundSigns = CreatorsSeparatedBy4PoundSigns + xmp.getArrayItem(XMPConst.NS_DC, creatorProp, z + 1); - CreatorsSeparatedBy4PoundSigns = CreatorsSeparatedBy4PoundSigns + "####"; - } - $._PPP_.updateEventPanel(CreatorsSeparatedBy4PoundSigns); - - if (confirm("Replace previous?", false, "Replace existing Creator?")) { - xmp.deleteProperty(XMPConst.NS_DC, "creator"); - } - xmp.appendArrayItem(XMPConst.NS_DC, // If no values exist, appendArrayItem will create a value. - creatorProp, - numCreatorValuesPresent + " creator values were already present.", - null, - XMPConst.ARRAY_IS_ORDERED); - - } else { - - xmp.appendArrayItem(XMPConst.NS_DC, - creatorProp, - "PProPanel wrote the first value into NS_DC creator field.", - null, - XMPConst.ARRAY_IS_ORDERED); - } - var xmpAsString = xmp.serialize(); // either way, serialize and write XMP. - projectItem.setXMPMetadata(xmpAsString); - } - } else { - $._PPP_.updateEventPanel("Project item required."); - } - }, - - getProductionByName: function (nameToGet) { - var production; - for (var i = 0; i < productionList.numProductions; i++) { - var currentProduction = productionList[i]; - - if (currentProduction.name == nameToGet) { - production = currentProduction; - } - } - return production; - }, - - pokeAnywhere: function () { - var token = app.anywhere.getAuthenticationToken(); - var productionList = app.anywhere.listProductions(); - var isProductionOpen = app.anywhere.isProductionOpen(); - if (isProductionOpen === true) { - var sessionURL = app.anywhere.getCurrentEditingSessionURL(); - var selectionURL = app.anywhere.getCurrentEditingSessionSelectionURL(); - var activeSequenceURL = app.anywhere.getCurrentEditingSessionActiveSequenceURL(); - - var theOneIAskedFor = $._PPP_.getProductionByName("test"); - - if (theOneIAskedFor) { - var out = theOneIAskedFor.name + ", " + theOneIAskedFor.description; - $._PPP_.updateEventPanel("Found: " + out); // todo: put useful code here. - } - } else { - $._PPP_.updateEventPanel("No Production open."); - } - }, - - dumpOMF: function () { - var activeSequence = app.project.activeSequence; - if (activeSequence) { - var outputPath = Folder.selectDialog("Choose the output directory"); - if (outputPath) { - var absPath = outputPath.fsName; - var outputName = String(activeSequence.name) + '.omf'; - var fullOutPathWithName = absPath + $._PPP_.getSep() + outputName; - - app.project.exportOMF(app.project.activeSequence, // sequence - fullOutPathWithName, // output file path - 'OMFTitle', // OMF title - 48000, // sample rate (48000 or 96000) - 16, // bits per sample (16 or 24) - 1, // audio encapsulated flag (1 : yes or 0 : no) - 0, // audio file format (0 : AIFF or 1 : WAV) - 0, // trim audio files (0 : no or 1 : yes) - 0, // handle frames (if trim is 1, handle frames from 0 to 1000) - 0); // include pan flag (0 : no or 1 : yes) - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - addClipMarkers: function () { - if (app.project.rootItem.children.numItems > 0) { - var projectItem = app.project.rootItem.children[0]; // assumes first item is footage. - if (projectItem) { - if (projectItem.type == ProjectItemType.CLIP || projectItem.type == ProjectItemType.FILE) { - - markers = projectItem.getMarkers(); - - if (markers) { - var num_markers = markers.numMarkers; - var new_marker = markers.createMarker(12.345); - var guid = new_marker.guid; // new in 11.1 - - new_marker.name = 'Marker created by PProPanel.'; - new_marker.comments = 'Here are some comments, inserted by PProPanel.'; - new_marker.end = 15.6789; - - //default marker type == comment. To change marker type, call one of these: - - // new_marker.setTypeAsChapter(); - // new_marker.setTypeAsWebLink(); - // new_marker.setTypeAsSegmentation(); - // new_marker.setTypeAsComment(); - } - } else { - $._PPP_.updateEventPanel("Can only add markers to footage items."); - } - } else { - $._PPP_.updateEventPanel("Could not find first projectItem."); - } - } else { - $._PPP_.updateEventPanel("Project is empty."); - } - }, - - modifyProjectMetadata: function () { - var kPProPrivateProjectMetadataURI = "http://ns.adobe.com/premierePrivateProjectMetaData/1.0/"; - - var namefield = "Column.Intrinsic.Name"; - var tapename = "Column.Intrinsic.TapeName"; - var desc = "Column.PropertyText.Description"; - var logNote = "Column.Intrinsic.LogNote"; - var newField = "ExampleFieldName"; - - if (app.isDocumentOpen()) { - var projectItem = app.project.rootItem.children[0]; // just grabs first projectItem. - if (projectItem) { - if (ExternalObject.AdobeXMPScript === undefined) { - ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); - } - if (ExternalObject.AdobeXMPScript !== undefined) { // safety-conscious! - var projectMetadata = projectItem.getProjectMetadata(); - var successfullyAdded = app.project.addPropertyToProjectMetadataSchema(newField, "ExampleFieldLabel", 2); - - var xmp = new XMPMeta(projectMetadata); - var obj = xmp.dumpObject(); - - // var aliases = xmp.dumpAliases(); - - var namespaces = XMPMeta.dumpNamespaces(); - var found_name = xmp.doesPropertyExist(kPProPrivateProjectMetadataURI, namefield); - var found_tapename = xmp.doesPropertyExist(kPProPrivateProjectMetadataURI, tapename); - var found_desc = xmp.doesPropertyExist(kPProPrivateProjectMetadataURI, desc); - var found_custom = xmp.doesPropertyExist(kPProPrivateProjectMetadataURI, newField); - var foundLogNote = xmp.doesPropertyExist(kPProPrivateProjectMetadataURI, logNote); - var oldLogValue = ""; - var appendThis = "This log note inserted by PProPanel."; - var appendTextWasActuallyNew = false; - - if (foundLogNote) { - var oldLogNote = xmp.getProperty(kPProPrivateProjectMetadataURI, logNote); - if (oldLogNote) { - oldLogValue = oldLogNote.value; - } - } - - xmp.setProperty(kPProPrivateProjectMetadataURI, tapename, "***TAPENAME***"); - xmp.setProperty(kPProPrivateProjectMetadataURI, desc, "***DESCRIPTION***"); - xmp.setProperty(kPProPrivateProjectMetadataURI, namefield, "***NEWNAME***"); - xmp.setProperty(kPProPrivateProjectMetadataURI, newField, "PProPanel set this, using addPropertyToProjectMetadataSchema()."); - - - var array = []; - array[0] = tapename; - array[1] = desc; - array[2] = namefield; - array[3] = newField; - - var concatenatedLogNotes = ""; - - if (oldLogValue != appendThis) { // if that value is not exactly what we were going to add - if (oldLogValue.length > 0) { // if we have a valid value - concatenatedLogNotes += "Previous log notes: " + oldLogValue + " |||| "; - } - concatenatedLogNotes += appendThis; - xmp.setProperty(kPProPrivateProjectMetadataURI, logNote, concatenatedLogNotes); - array[4] = logNote; - } - - var str = xmp.serialize(); - projectItem.setProjectMetadata(str, array); - - // test: is it in there? - - var newblob = projectItem.getProjectMetadata(); - var newXMP = new XMPMeta(newblob); - var foundYet = newXMP.doesPropertyExist(kPProPrivateProjectMetadataURI, newField); - - if (foundYet) { - $._PPP_.updateEventPanel("PProPanel successfully added a field to the project metadata schema, and set a value for it."); - } - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - } - }, - - updatePAR: function () { - var item = app.project.rootItem.children[0]; - if (item) { - if ((item.type == ProjectItemType.FILE) || (item.type == ProjectItemType.CLIP)) { - // If there is an item, and it's either a clip or file... - item.setOverridePixelAspectRatio(185, 100); // anamorphic is BACK! ;) - } else { - $._PPP_.updateEventPanel('You cannot override the PAR of bins or sequences.'); - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - }, - - getnumAEProjectItems: function () { - var bt = new BridgeTalk(); - bt.target = 'aftereffects'; - bt.body = //'$._PPP_.updateEventPanel("Items in AE project: " + app.project.rootFolder.numItems);app.quit();'; - 'alert("Items in AE project: " + app.project.rootFolder.numItems);app.quit();'; - bt.send(); - }, - - updateEventPanel: function (message) { - app.setSDKEventMessage(message, 'info'); - //app.setSDKEventMessage('Here is some information.', 'info'); - //app.setSDKEventMessage('Here is a warning.', 'warning'); - //app.setSDKEventMessage('Here is an error.', 'error'); // Very annoying; use sparingly. - }, - - walkAllBinsForFootage: function (parentItem, outPath) { - for (var j = 0; j < parentItem.children.numItems; j++) { - var currentChild = parentItem.children[j]; - if (currentChild) { - if (currentChild.type == ProjectItemType.BIN) { - $._PPP_.walkAllBinsForFootage(currentChild, outPath); // warning; recursion! - } else { - $._PPP_.dumpProjectItemXMP(currentChild, outPath); - } - } - } - }, - - searchBinForProjItemByName: function (i, containingBin, nameToFind) { - for (var j = i; j < containingBin.children.numItems; j++) { - var currentChild = containingBin.children[j]; - if (currentChild) { - if (currentChild.type == ProjectItemType.BIN) { - return $._PPP_.searchBinForProjItemByName(j, currentChild, nameToFind); // warning; recursion! - } else { - if (currentChild.name == nameToFind) { - return currentChild; - } else { - currentChild = currentItem.children[j + 1]; - if (currentChild) { - return $._PPP_.searchBinForProjItemByName(0, currentChild, nameToFind); - } - } - } - } - } - }, - - dumpProjectItemXMP: function (projectItem, outPath) { - var xmpBlob = projectItem.getXMPMetadata(); - var outFileName = projectItem.name + '.xmp'; - var completeOutputPath = outPath + $._PPP_.getSep() + outFileName; - var outFile = new File(completeOutputPath); - - var isThisASequence = projectItem.isSequence(); - - if (outFile) { - outFile.encoding = "UTF8"; - outFile.open("w", "TEXT", "????"); - outFile.write(xmpBlob.toString()); - outFile.close(); - } - }, - - addSubClip: function () { - var startTime = new Time; - startTime.seconds = 0.0; - var endTime = new Time; - endTime.seconds = 3.21; - var hasHardBoundaries = 0; - var sessionCounter = 1; - var takeVideo = 1; // optional, defaults to 1 - var takeAudio = 1; // optional, defaults to 1 - var projectItem = app.project.rootItem.children[0]; // just grabs the first item - if (projectItem) { - if ((projectItem.type == ProjectItemType.CLIP) || (projectItem.type == ProjectItemType.FILE)) { - var newSubClipName = prompt('Name of subclip?', projectItem.name + '_' + sessionCounter, 'Name your subclip'); - - var newSubClip = projectItem.createSubClip(newSubClipName, - startTime, - endTime, - hasHardBoundaries, - takeVideo, - takeAudio); - - if (newSubClip) { - newSubClip.setStartTime(12.345); - } - } else { - $._PPP_.updateEventPanel("Could not sub-clip " + projectItem.name + "."); - } - } else { - $._PPP_.updateEventPanel("No project item found."); - } - }, - - dumpXMPFromAllProjectItems: function () { - var numItemsInRoot = app.project.rootItem.children.numItems; - if (numItemsInRoot > 0) { - var outPath = Folder.selectDialog("Choose the output directory"); - if (outPath) { - for (var i = 0; i < numItemsInRoot; i++) { - var currentItem = app.project.rootItem.children[i]; - if (currentItem) { - if (currentItem.type == ProjectItemType.BIN) { - $._PPP_.walkAllBinsForFootage(currentItem, outPath.fsName); - } else { - $._PPP_.dumpProjectItemXMP(currentItem, outPath.fsName); - } - } - } - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - }, - - exportAAF: function () { - var sessionCounter = 1; - if (app.project.activeSequence) { - var outputPath = Folder.selectDialog("Choose the output directory"); - if (outputPath) { - var absPath = outputPath.fsName; - var outputName = String(app.project.name); - var array = outputName.split('.', 2); - outputName = array[0] + sessionCounter + '.' + array[1]; - - sessionCounter++; - var fullOutPath = absPath + $._PPP_.getSep() + outputName + '.aaf'; - //var optionalPathToOutputPreset = null; New in 11.0.0, you can specify an output preset. - - app.project.exportAAF(app.project.activeSequence, // which sequence - fullOutPath, // output path - 1, // mix down video? - 0, // explode to mono? - 96000, // sample rate - 16, // bits per sample - 0, // embed audio? - 0, // audio file format? 0 = aiff, 1 = wav - 0, // trim sources? - 0 - /*, // number of 'handle' frames - optionalPathToOutputPreset*/ - ); // optional; .epr file to use - } else { - $._PPP_.updateEventPanel("Couldn't create AAF output."); - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - setScratchDisk: function () { - var scratchPath = Folder.selectDialog("Choose new scratch disk directory"); - if ((scratchPath) && scratchPath.exists) { - app.setScratchDiskPath(scratchPath.fsName, ScratchDiskType.FirstAutoSaveFolder); // see ScratchDiskType object, in ESTK. - } - }, - - getProjectProxySetting: function () { - var returnVal = ""; - if (app.project) { - var returnVal = "No sequence detected in " + app.project.name + "."; - if (app.getEnableProxies()) { - returnVal = 'true'; - } else { - returnVal = 'false'; - } - } else { - returnVal = "No project available."; - } - return returnVal; - }, - - toggleProxyState: function () { - var update = "Proxies for " + app.project.name + " turned "; - if (app.getEnableProxies()) { - app.setEnableProxies(0); - update = update + "OFF."; - app.setSDKEventMessage(update, 'info'); - } else { - app.setEnableProxies(1); - update = update + "ON."; - app.setSDKEventMessage(update, 'info'); - } - }, - - setProxiesON: function () { - var firstProjectItem = app.project.rootItem.children[0]; - if (firstProjectItem) { - if (firstProjectItem.canProxy()) { - var shouldAttachProxy = true; - if (firstProjectItem.hasProxy()) { - shouldAttachProxy = confirm(firstProjectItem.name + " already has an assigned proxy. Re-assign anyway?", false, "Are you sure...?"); - } - if (shouldAttachProxy) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - var proxyPath = File.openDialog("Choose proxy for " + firstProjectItem.name + ":", - filterString, - false); - if (proxyPath.exists) { - firstProjectItem.attachProxy(proxyPath.fsName, 0); - } else { - $._PPP_.updateEventPanel("Could not attach proxy from " + proxyPath + "."); - } - } - } else { - $._PPP_.updateEventPanel("Cannot attach a proxy to " + firstProjectItem.name + "."); - } - } else { - $._PPP_.updateEventPanel("No project item available."); - } - }, - - clearCache: function () { - app.enableQE(); - MediaType = {}; - - // Magical constants from Premiere Pro's internal automation. - - MediaType.VIDEO = "228CDA18-3625-4d2d-951E-348879E4ED93"; - MediaType.AUDIO = "80B8E3D5-6DCA-4195-AEFB-CB5F407AB009"; - MediaType.ANY = "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"; - qe.project.deletePreviewFiles(MediaType.ANY); - $._PPP_.updateEventPanel("All video and audio preview files deleted."); - }, - - randomizeSequenceSelection: function () { - var sequence = app.project.activeSequence; - - if (sequence) { - var trackGroups = [sequence.audioTracks, sequence.videoTracks]; - var trackGroupNames = ["audioTracks", "videoTracks"]; - var updateUI = true; - var before; - - for (var gi = 0; gi < 2; gi++) { - $._PPP_.updateEventPanel(trackGroupNames[gi]); - group = trackGroups[gi]; - for (var ti = 0; ti < group.numTracks; ti++) { - var track = group[ti]; - var clips = track.clips; - var transitions = track.transitions; - var beforeSelected; - var afterSelected; - - $._PPP_.updateEventPanel("track : " + ti + " clip count: " + clips.numTracks + " transition count: " + transitions.numTracks); - - for (var ci = 0; ci < clips.numTracks; ci++) { - var clip = clips[ci]; - name = (clip.projectItem === undefined ? "" : clip.projectItem.name); - before = clip.isSelected(); - - // randomly select clips - clip.setSelected((Math.random() > 0.5), updateUI); - - if (clip.isAdjustmentLayer()) { // new in 13.0 - $._PPP_.updateEventPanel("Clip named \"" + clip.name + "\" is an adjustment layer."); - } - - // Note; there's no good place to exercise this code yet, but - // I wanted to provide example usage. - - var allClipsInThisSequenceFromSameSource = clip.getLinkedItems(); - - if (allClipsInThisSequenceFromSameSource) { - $._PPP_.updateEventPanel("Found " + allClipsInThisSequenceFromSameSource.numItems + " clips from " + clip.projectItem.name + ", in this sequence."); - } - beforeSelected = before ? "Y" : "N"; - afterSelected = clip.selected ? "Y" : "N"; - $._PPP_.updateEventPanel("clip : " + ci + " " + name + " " + beforeSelected + " -> " + afterSelected); - } - - for (var tni = 0; tni < transitions.numTracks; ++tni) { - var transition = transitions[tni]; - before = transition.isSelected(); - - // randomly select transitions - transition.setSelected((Math.random() > 0.5), updateUI); - - beforeSelected = before ? "Y" : "N"; - afterSelected = transition.selected ? "Y" : "N"; - - $._PPP_.updateEventPanel('transition: ' + tni + " " + beforeSelected + " -> " + afterSelected); - } - } - } - } else { - $._PPP_.updateEventPanel("no active sequence."); - } - }, - - // Define a couple of callback functions, for AME to use during render. - - onEncoderJobComplete: function (jobID, outputFilePath) { - var eoName; - - if (Folder.fs == 'Macintosh') { - eoName = "PlugPlugExternalObject"; - } else { - eoName = "PlugPlugExternalObject.dll"; - } - - var suffixAddedByPPro = '_1'; // You should really test for any suffix. - var withoutExtension = outputFilePath.slice(0, -4); // trusting 3 char extension - var lastIndex = outputFilePath.lastIndexOf("."); - var extension = outputFilePath.substr(lastIndex + 1); - - if (outputFilePath.indexOf(suffixAddedByPPro)) { - $._PPP_.updateEventPanel(" Output filename was changed: the output preset name may have been added, or there may have been an existing file with that name. This would be a good place to deal with such occurrences."); - } - - var mylib = new ExternalObject('lib:' + eoName); - var eventObj = new CSXSEvent(); - - eventObj.type = "com.adobe.csxs.events.PProPanelRenderEvent"; - eventObj.data = "Rendered Job " + jobID + ", to " + outputFilePath + "."; - - eventObj.dispatch(); - }, - - onEncoderJobError: function (jobID, errorMessage) { - var eoName; - - if (Folder.fs === 'Macintosh') { - eoName = "PlugPlugExternalObject"; - } else { - eoName = "PlugPlugExternalObject.dll"; - } - - var mylib = new ExternalObject('lib:' + eoName); - var eventObj = new CSXSEvent(); - - eventObj.type = "com.adobe.csxs.events.PProPanelRenderEvent"; - eventObj.data = "Job " + jobID + " failed, due to " + errorMessage + "."; - eventObj.dispatch(); - }, - - onEncoderJobProgress: function (jobID, progress) { - $._PPP_.updateEventPanel('onEncoderJobProgress called. jobID = ' + jobID + '. progress = ' + progress + '.'); - }, - - onEncoderJobQueued: function (jobID) { - app.encoder.startBatch(); - }, - - onEncoderJobCanceled: function (jobID) { - $._PPP_.updateEventPanel('OnEncoderJobCanceled called. jobID = ' + jobID + '.'); - }, - - onPlayWithKeyframes: function () { - var seq = app.project.activeSequence; - if (seq) { - var firstVideoTrack = seq.videoTracks[0]; - if (firstVideoTrack) { - var firstClip = firstVideoTrack.clips[0]; - if (firstClip) { - var clipComponents = firstClip.components; - if (clipComponents) { - for (var i = 0; i < clipComponents.numItems; ++i) { - $._PPP_.updateEventPanel('component ' + i + ' = ' + clipComponents[i].matchName + ' : ' + clipComponents[i].displayName); - } - if (clipComponents.numItems > 2) { - - // 0 = clip - // 1 = Opacity - // N effects, then... - // Shape layer (new in 12.0) - - var blur = clipComponents[2]; // Assume Gaussian Blur is the first effect applied to the clip. - if (blur) { - var blurProps = blur.properties; - if (blurProps) { - for (var j = 0; j < blurProps.numItems; ++j) { - $._PPP_.updateEventPanel('param ' + j + ' = ' + blurProps[j].displayName); - } - var blurriness = blurProps[0]; - if (blurriness) { - if (!blurriness.isTimeVarying()) { - blurriness.setTimeVarying(true); - } - for (var k = 0; k < 20; ++k) { - updateUI = (k == 9); // Decide how often to update PPro's UI - blurriness.addKey(k); - var blurVal = Math.sin(3.14159 * i / 5) * 20 + 25; - blurriness.setValueAtKey(k, blurVal, updateUI); - } - } - var repeatEdgePixels = blurProps[2]; - if (repeatEdgePixels) { - if (!repeatEdgePixels.getValue()) { - updateUI = true; - repeatEdgePixels.setValue(true, updateUI); - } - } - // look for keyframe nearest to 4s with 1/10 second tolerance - var keyFrameTime = blurriness.findNearestKey(4.0, 0.1); - if (keyFrameTime !== undefined) { - $._PPP_.updateEventPanel('Found keyframe = ' + keyFrameTime.seconds); - } else { - $._PPP_.updateEventPanel('Keyframe not found.'); - } - - // scan keyframes, forward - - keyFrameTime = blurriness.findNearestKey(0.0, 0.1); - var lastKeyFrameTime = keyFrameTime; - while (keyFrameTime !== undefined) { - $._PPP_.updateEventPanel('keyframe @ ' + keyFrameTime.seconds); - lastKeyFrameTime = keyFrameTime; - keyFrameTime = blurriness.findNextKey(keyFrameTime); - } - - // scan keyframes, backward - keyFrameTime = lastKeyFrameTime; - while (keyFrameTime !== undefined) { - $._PPP_.updateEventPanel('keyframe @ ' + keyFrameTime.seconds); - lastKeyFrameTime = keyFrameTime; - keyFrameTime = blurriness.findPreviousKey(keyFrameTime); - } - - // get all keyframes - - var blurKeyframesArray = blurriness.getKeys(); - if (blurKeyframesArray) { - $._PPP_.updateEventPanel(blurKeyframesArray.length + ' keyframes found'); - } - - // remove keyframe at 19s - blurriness.removeKey(19); - - // remove keyframes in range from 0s to 5s - var shouldUpdateUI = true; - blurriness.removeKeyRange(0, 5, shouldUpdateUI); - } - - } else { - $._PPP_.updateEventPanel("Please apply the Gaussian Blur effect to the first clip in the first video track of the active sequence."); - } - } - } - } - } - } else { - $._PPP_.updateEventPanel("no active sequence."); - } - }, - - extractFileNameFromPath: function (fullPath) { - var lastDot = fullPath.lastIndexOf("."); - var lastSep = fullPath.lastIndexOf("/"); - - if (lastDot > -1) { - return fullPath.substr((lastSep + 1), (fullPath.length - (lastDot + 1))); - } else { - return fullPath; - } - }, - - onProxyTranscodeJobComplete: function (jobID, outputFilePath) { - var suffixAddedByPPro = '_1'; // You should really test for any suffix. - var withoutExtension = outputFilePath.slice(0, -4); // trusting 3 char extension - var lastIndex = outputFilePath.lastIndexOf("."); - var extension = outputFilePath.substr(lastIndex + 1); - - var wrapper = []; - wrapper[0] = outputFilePath; - - var nameToFind = 'Proxies generated by PProPanel'; - var targetBin = $._PPP_.getPPPInsertionBin(); - if (targetBin) { - app.project.importFiles(wrapper); - } - }, - - onProxyTranscodeJobError: function (jobID, errorMessage) { - $._PPP_.updateEventPanel(errorMessage); - }, - - onProxyTranscodeJobQueued: function (jobID) { - app.encoder.startBatch(); - }, - - ingestFiles: function (outputPresetPath) { - app.encoder.bind('onEncoderJobComplete', $._PPP_.onProxyTranscodeJobComplete); - app.encoder.bind('onEncoderJobError', $._PPP_.onProxyTranscodeJobError); - app.encoder.bind('onEncoderJobQueued', $._PPP_.onProxyTranscodeJobQueued); - app.encoder.bind('onEncoderJobCanceled', $._PPP_.onEncoderJobCanceled); - - if (app.project) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - var fileOrFilesToImport = File.openDialog("Choose full resolution files to import", // title - filterString, // filter available files? - true); // allow multiple? - if (fileOrFilesToImport) { - var nameToFind = 'Proxies generated by PProPanel'; - var targetBin = $._PPP_.searchForBinWithName(nameToFind); - if (targetBin === 0) { - // If panel can't find the target bin, it creates it. - app.project.rootItem.createBin(nameToFind); - targetBin = $._PPP_.searchForBinWithName(nameToFind); - } - if (targetBin) { - targetBin.select(); - var importThese = []; // We have an array of File objects; importFiles() takes an array of paths. - if (importThese) { - for (var i = 0; i < fileOrFilesToImport.length; i++) { - importThese[i] = fileOrFilesToImport[i].fsName; - var justFileName = extractFileNameFromPath(importThese[i]); - var suffix = '_PROXY.mp4'; - var containingPath = fileOrFilesToImport[i].parent.fsName; - var completeProxyPath = containingPath + $._PPP_.getSep() + justFileName + suffix; - - var jobID = app.encoder.encodeFile(fileOrFilesToImport[i].fsName, - completeProxyPath, - outputPresetPath, - 0); - } - - app.project.importFiles(importThese, - 1, // suppress warnings - targetBin, - 0); // import as numbered stills - } - } else { - $._PPP_.updateEventPanel("Could not find or create target bin."); - } - } else { - $._PPP_.updateEventPanel("No files to import."); - } - } else { - $._PPP_.updateEventPanel("No project found."); - } - }, - - insertOrAppend: function () { - var seq = app.project.activeSequence; - if (seq) { - var first = app.project.rootItem.children[0]; - if (first) { - var numVTracks = seq.videoTracks.numTracks; - var targetVTrack = seq.videoTracks[(numVTracks - 1)]; - if (targetVTrack) { - // If there are already clips in this track, - // append this one to the end. Otherwise, - // insert at start time. - - if (targetVTrack.clips.numItems > 0) { - var lastClip = targetVTrack.clips[(targetVTrack.clips.numItems - 1)]; - if (lastClip) { - targetVTrack.insertClip(first, lastClip.end.seconds); - } - } else { - targetVTrack.insertClip(first, '00;00;00;00'); - } - } else { - $._PPP_.updateEventPanel("Could not find first video track."); - } - } else { - $._PPP_.updateEventPanel("Couldn't locate first projectItem."); - } - } else { - $._PPP_.updateEventPanel("no active sequence."); - } - }, - - overWrite: function () { - var seq = app.project.activeSequence; - if (seq) { - var first = app.project.rootItem.children[0]; - if (first) { - var vTrack1 = seq.videoTracks[0]; - if (vTrack1) { - var now = seq.getPlayerPosition(); - vTrack1.overwriteClip(first, now.seconds); - } else { - $._PPP_.updateEventPanel("Could not find first video track."); - } - } else { - $._PPP_.updateEventPanel("Couldn't locate first projectItem."); - } - } else { - $._PPP_.updateEventPanel("no active sequence."); - } - }, - - closeFrontSourceClip: function () { - app.sourceMonitor.closeClip(); - }, - - closeAllClipsInSourceMonitor: function () { - app.sourceMonitor.closeAllClips(); - }, - - changeLabel: function () { - var first = app.project.rootItem.children[0]; - if (first) { - var currentLabel = first.getColorLabel(); - var newLabel = currentLabel + 1; // 4 = Cerulean. 0 = Violet, 15 = Yellow. - if (newLabel > 15) { - newLabel = newLabel - 16; - } - app.setSDKEventMessage("Previous Label color = " + currentLabel + ".", 'info'); - first.setColorLabel(newLabel); - app.setSDKEventMessage("New Label color = " + newLabel + ".", 'info'); - } else { - $._PPP_.updateEventPanel("Couldn't locate first projectItem."); - } - }, - - getPPPInsertionBin: function () { - var nameToFind = "Here's where PProPanel puts things."; - - var targetBin = $._PPP_.searchForBinWithName(nameToFind); - - if (targetBin === undefined) { - // If panel can't find the target bin, it creates it. - app.project.rootItem.createBin(nameToFind); - targetBin = $._PPP_.searchForBinWithName(nameToFind); - } - if (targetBin) { - targetBin.select(); - return targetBin; - } - }, - - importComps: function () { - var targetBin = $._PPP_.getPPPInsertionBin(); - if (targetBin) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "All files:*.*"; - } - compNamesToImport = []; - - var aepToImport = File.openDialog("Choose After Effects project", // title - filterString, // filter available files? - false); // allow multiple? - if (aepToImport) { - var importAll = confirm("Import all compositions in project?", false, "Import all?"); - if (importAll) { - var result = app.project.importAllAEComps(aepToImport.fsName, targetBin); - } else { - var compName = prompt('Name of composition to import?', - '', - 'Which Comp to import'); - if (compName) { - compNamesToImport[0] = compName; - var importAECompResult = app.project.importAEComps(aepToImport.fsName, compNamesToImport, targetBin); - } else { - $._PPP_.updateEventPanel("Could not find Composition."); - } - } - } else { - $._PPP_.updateEventPanel("Could not open project."); - } - } else { - $._PPP_.updateEventPanel("Could not find or create target bin."); - } - }, - - consolidateProject: function () { - var pmo = app.projectManager.options; - - if (app.project.sequences.length) { - if (pmo) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "Output Presets:*.epr"; - } - - var outFolder = Folder.selectDialog("Choose output directory."); - if (outFolder) { - - var presetPath = ""; - var useSpecificPreset = confirm("Would you like to select an output preset?", false, "Are you sure...?"); - if (useSpecificPreset) { - var useThisEPR = File.openDialog("Choose output preset (.epr file)", // title - filterString, // filter available files? - false); // allow multiple? - - if (useThisEPR) { - pmo.clipTranscoderOption = pmo.CLIP_TRANSCODE_MATCH_PRESET; - pmo.encoderPresetFilePath = useThisEPR.fsName; - } - } else { - pmo.clipTranscoderOption = pmo.CLIP_TRANSCODE_MATCH_SEQUENCE; - } - - var processAllSequences = confirm("Process all sequences? No = just the first sequence found.", true, "Process all?"); - - if (processAllSequences) { - pmo.includeAllSequences = true; - } else { - pmo.includeAllSequences = false; - pmo.affectedSequences = [app.project.sequences[0]]; - } - - pmo.clipTransferOption = pmo.CLIP_TRANSFER_TRANSCODE; - pmo.convertAECompsToClips = false; - pmo.convertSyntheticsToClips = false; - pmo.copyToPreventAlphaLoss = false; - pmo.destinationPath = outFolder.fsName; - pmo.excludeUnused = false; - pmo.handleFrameCount = 0; - pmo.includeConformedAudio = true; - pmo.includePreviews = true; - pmo.renameMedia = false; - - var result = app.projectManager.process(app.project); - var errorList = app.projectManager.errors; - - if (errorList.length) { - for (var k = 0; k < errorList.length; k++) { - $._PPP_.updateEventPanel(errorList[k][1]); - } - } else { - $._PPP_.updateEventPanel(app.project.name + " successfully processed to " + outFolder.fsName + "."); - } - return result; - } - } - - - } - if (pmo) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "Output Presets:*.epr"; - } - - var outFolder = Folder.selectDialog("Choose output directory."); - if (outFolder) { - - var presetPath = ""; - var useSpecificPreset = confirm("Would you like to select an output preset?", false, "Are you sure...?"); - if (useSpecificPreset) { - var useThisEPR = File.openDialog("Choose output preset (.epr file)", // title - filterString, // filter available files? - false); // allow multiple? - - if (useThisEPR) { - pmo.clipTranscoderOption = pmo.CLIP_TRANSCODE_MATCH_PRESET; - pmo.encoderPresetFilePath = useThisEPR.fsName; - } - } else { - pmo.clipTranscoderOption = pmo.CLIP_TRANSCODE_MATCH_SEQUENCE; - } - - var processAllSequences = confirm("Process all sequences? No = just the first sequence found.", true, "Process all?"); - - if (processAllSequences) { - pmo.includeAllSequences = true; - } else { - pmo.includeAllSequences = false; - pmo.affectedSequences = [app.project.sequences[0]]; - } - - pmo.clipTransferOption = pmo.CLIP_TRANSFER_TRANSCODE; - pmo.convertAECompsToClips = false; - pmo.convertSyntheticsToClips = false; - pmo.copyToPreventAlphaLoss = false; - pmo.destinationPath = outFolder.fsName; - pmo.excludeUnused = false; - pmo.handleFrameCount = 0; - pmo.includeConformedAudio = true; - pmo.includePreviews = true; - pmo.renameMedia = false; - - var result = app.projectManager.process(app.project); - var errorList = app.projectManager.errors; - - if (errorList.length) { - for (var k = 0; k < errorList.length; k++) { - $._PPP_.updateEventPanel(errorList[k][1]); - } - } else { - $._PPP_.updateEventPanel(app.project.name + " successfully processed to " + outFolder.fsName + "."); - } - return result; - } - } - }, - - importMoGRT: function () { - var activeSeq = app.project.activeSequence; - if (activeSeq) { - var filterString = ""; - if (Folder.fs === 'Windows') { - filterString = "Motion Graphics Templates:*.mogrt"; - } - var mogrtToImport = File.openDialog("Choose MoGRT", // title - filterString, // filter available files? - false); // allow multiple? - if (mogrtToImport) { - var targetTime = activeSeq.getPlayerPosition(); - var vidTrackOffset = 0; - var audTrackOffset = 0; - var newTrackItem = activeSeq.importMGT(mogrtToImport.fsName, - targetTime.ticks, - vidTrackOffset, - audTrackOffset); - if (newTrackItem) { - var moComp = newTrackItem.getMGTComponent(); - if (moComp) { - var params = moComp.properties; - for (var z = 0; z < params.numItems; z++) { - var thisParam = params[0]; - } - var srcTextParam = params.getParamForDisplayName("Main Title"); - if (srcTextParam) { - var val = srcTextParam.getValue(); - srcTextParam.setValue("New value set by PProPanel!"); - } - } - } - } else { - app.setSDKEventMessage('Unable to import ' + mogrtToImport.fsName + '.', 'error'); - } - } else { - app.setSDKEventMessage('No active sequence.'); - } - }, - - reportCurrentProjectSelection: function () { - var viewIDs = app.getProjectViewIDs(); // sample code optimized for a single open project - viewSelection = app.getProjectViewSelection(viewIDs[0]); - $._PPP_.projectPanelSelectionChanged(viewSelection, viewIDs[0]); - }, - - randomizeProjectSelection: function () { - var viewIDs = app.getProjectViewIDs(); - var firstProject = app.getProjectFromViewID(viewIDs[0]); - var arrayOfRandomProjectItems = []; - - for (var b = 0; b < app.project.rootItem.children.numItems; b++) { - var currentProjectItem = app.project.rootItem.children[b]; - if (Math.random() > 0.5) { - arrayOfRandomProjectItems.push(currentProjectItem); - } - } - if (arrayOfRandomProjectItems.length > 0) { - app.setProjectViewSelection(arrayOfRandomProjectItems, viewIDs[0]); - } - }, - - setAllProjectItemsOnline: function (startingBin) { - for (var k = 0; k < startingBin.children.numItems; k++) { - var currentChild = startingBin.children[k]; - if (currentChild) { - if (currentChild.type === ProjectItemType.BIN) { - $._PPP_.setAllProjectItemsOnline(currentChild); // warning; recursion! - } else if (currentChild.isOffline()) { - currentChild.changeMediaPath(currentChild.getMediaPath(), true); - if (currentChild.isOffline()) { - $._PPP_.updateEventPanel("Failed to bring \'" + currentChild.name + "\' online."); - } else { - $._PPP_.updateEventPanel("\'" + currentChild.name + "\' is once again online."); - } - } - } - } - }, - - setAllOnline: function () { - var startingBin = app.project.rootItem; - $._PPP_.setAllProjectItemsOnline(startingBin); - }, - - setOffline: function () { - var viewIDs = app.getProjectViewIDs(); - for (var a = 0; a < app.projects.numProjects; a++) { - var currentProject = app.getProjectFromViewID(viewIDs[a]); - if (currentProject) { - if (currentProject.documentID === app.project.documentID) { // We're in the right project! - var selectedItems = app.getProjectViewSelection(viewIDs[a]); - for (var b = 0; b < selectedItems.length; b++) { - var currentItem = selectedItems[b]; - if (currentItem) { - if ((!currentItem.isSequence()) && (currentItem.type !== ProjectItemType.BIN)) { // For every selected item which isn't a bin or sequence... - if (currentItem.isOffline()) { - $._PPP_.updateEventPanel("\'" + currentItem.name + "\'was already offline."); - } else { - var result = currentItem.setOffline(); - $._PPP_.updateEventPanel("\'" + currentItem.name + "\' is now offline."); - } - } - } - } - } - } - } - }, - - updateFrameRate: function () { - var item = app.project.rootItem.children[0]; - if (item) { - if ((item.type == ProjectItemType.FILE) || (item.type == ProjectItemType.CLIP)) { - // If there is an item, and it's either a clip or file... - item.setOverrideFrameRate(23.976); - } else { - $._PPP_.updateEventPanel('You cannot override the frame rate of bins or sequences.'); - } - } else { - $._PPP_.updateEventPanel("No project items found."); - } - }, - - onItemAddedToProject: function (whichProject, addedProjectItem) { - var msg = addedProjectItem.name + " was added to " + whichProject + "." - $._PPP_.updateEventPanel(msg); - }, - - registerItemAddedFxn: function () { - app.onItemAddedToProjectSuccess = $._PPP_.onItemAddedToProject; - }, - - myOnProjectChanged: function (documentID) { - var msg = 'Project with ID ' + documentID + ' Changed.'; - // Commented out, as this happens a LOT. - // $._PPP_.updateEventPanel(msg); - }, - - registerProjectChangedFxn: function () { - app.bind('onProjectChanged', $._PPP_.myOnProjectChanged); - }, - - confirmPProHostVersion: function () { - var version = parseFloat(app.version); - if (version < 12.1) { - $._PPP_.updateEventPanel("Note: PProPanel relies on features added in 12.1, but is currently running in " + version + "."); - } - }, - - changeMarkerColors: function () { - if (app.project.rootItem.children.numItems > 0) { - var projectItem = app.project.rootItem.children[0]; // assumes first item is footage. - if (projectItem) { - if (projectItem.type == ProjectItemType.CLIP || - projectItem.type == ProjectItemType.FILE) { - - markers = projectItem.getMarkers(); - - if (markers) { - var markerCount = markers.numMarkers; - - if (markerCount) { - for (var thisMarker = markers.getFirstMarker(); thisMarker !== undefined; thisMarker = markers.getNextMarker(thisMarker)) { - var oldColor = thisMarker.getColorByIndex(); - var newColor = oldColor + 1; - if (newColor > 7) { - newColor = 0; - } - thisMarker.setColorByIndex(newColor); - $._PPP_.updateEventPanel("Changed color of marker named \'" + thisMarker.name + "\' from " + oldColor + " to " + newColor + "."); - } - } - } - } else { - $._PPP_.updateEventPanel("Can only add markers to footage items."); - } - } else { - $._PPP_.updateEventPanel("Could not find first projectItem."); - } - } else { - $._PPP_.updateEventPanel("Project is empty."); - } - }, - - changeSeqTimeCodeDisplay: function () { - if (app.project.activeSequence) { - var currentSeqSettings = app.project.activeSequence.getSettings(); - if (currentSeqSettings) { - var oldVidSetting = currentSeqSettings.videoDisplayFormat; - currentSeqSettings.videoDisplayFormat = oldVidSetting + 1; - if (currentSeqSettings.videoDisplayFormat > TIMEDISPLAY_48Timecode) { - currentSeqSettings.videoDisplayFormat = TIMEDISPLAY_24Timecode; - } - app.project.activeSequence.setSettings(currentSeqSettings); - $._PPP_.updateEventPanel("Changed timecode display format for \'" + app.project.activeSequence.name + "\'."); - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - myActiveSequenceChangedFxn: function () { - $._PPP_.updateEventPanel("Active sequence is now " + app.project.activeSequence.name + "."); - }, - - myActiveSequenceSelectionChangedFxn: function () { - var sel = app.project.activeSequence.getSelection(); - $._PPP_.updateEventPanel('Current active sequence = ' + app.project.activeSequence.name + '.'); - $._PPP_.updateEventPanel(sel.length + ' track items selected.'); - for (var i = 0; i < sel.length; i++) { - if (sel[i].name !== 'anonymous') { - $._PPP_.updateEventPanel('Selected item ' + (i + 1) + ' == ' + sel[i].name + '.'); - } - } - }, - - registerActiveSequenceChangedFxn: function () { - var success = app.bind("onActiveSequenceChanged", $._PPP_.myActiveSequenceChangedFxn); - }, - - registerSequenceSelectionChangedFxn: function () { - var success = app.bind('onActiveSequenceSelectionChanged', $._PPP_.myActiveSequenceSelectionChangedFxn); - }, - - enableNewWorldScripting: function () { - app.enableQE(); - - var previousNWValue = qe.getDebugDatabaseEntry("ScriptLayerPPro.EnableNewWorld"); - var previousInternalDOMValue = qe.getDebugDatabaseEntry("dvascripting.EnabledInternalDOM"); - if ((previousNWValue === 'true') && (previousInternalDOMValue === 'true')) { - qe.setDebugDatabaseEntry("ScriptLayerPPro.EnableNewWorld", "false"); - qe.setDebugDatabaseEntry("dvascripting.EnabledInternalDOM", "false"); - $._PPP_.updateEventPanel("ScriptLayerPPro.EnableNewWorld and dvascripting.EnabledInternalDOM are now OFF."); - } else { - qe.setDebugDatabaseEntry("ScriptLayerPPro.EnableNewWorld", "true"); - qe.setDebugDatabaseEntry("dvascripting.EnabledInternalDOM", "true"); - $._PPP_.updateEventPanel("ScriptLayerPPro.EnableNewWorld and dvascripting.EnabledInternalDOM are now ON."); - } - }, - - insertOrAppendToTopTracks: function () { - var seq = app.project.activeSequence; - if (seq) { - var first = app.project.rootItem.children[0]; - if (first) { - var time = seq.getPlayerPosition(); - var newClip = seq.insertClip(first, time, (seq.videoTracks.numTracks - 1), (seq.audioTracks.numTracks - 1)); - if (newClip) { - $._PPP_.updateEventPanel("Inserted " + newClip.name + ", into " + seq.name + "."); - } - } else { - $._PPP_.updateEventPanel("Couldn't locate first projectItem."); - } - } else { - $._PPP_.updateEventPanel("no active sequence."); - } - }, - - closeAllProjectsOtherThanActiveProject: function () { - var viewIDs = app.getProjectViewIDs(); - var closeTheseProjects = []; - for (var a = 0; a < viewIDs.length; a++) { - var thisProj = app.getProjectFromViewID(viewIDs[a]); - if (thisProj.documentID !== app.project.documentID) { - closeTheseProjects[a] = thisProj; - } - } - // Why do this afterward? Because if we close projects in that loop, we change the active project. :) - for (var b = 0; b < closeTheseProjects.length; b++) { - $._PPP_.updateEventPanel("Closed " + closeTheseProjects[b].name); - closeTheseProjects[b].closeDocument(); - } - }, - - countAdjustmentLayersInBin: function (parentItem, arrayOfAdjustmentLayerNames, foundSoFar) { - for (var j = 0; j < parentItem.children.numItems; j++) { - var currentChild = parentItem.children[j]; - if (currentChild) { - if (currentChild.type == ProjectItemType.BIN) { - $._PPP_.countAdjustmentLayersInBin(currentChild, arrayOfAdjustmentLayerNames, foundSoFar); // warning; recursion! - } else { - if (currentChild.isAdjustmentLayer()) { - arrayOfAdjustmentLayerNames[foundSoFar] = currentChild.name; - foundSoFar++; - } - } - } - } - }, - - findAllAdjustmentLayersInProject: function () { - var arrayOfAdjustmentLayerNames = []; - var foundSoFar = 0; - var startingBin = app.project.rootItem; - - $._PPP_.countAdjustmentLayersInBin(startingBin, arrayOfAdjustmentLayerNames, foundSoFar); - if (arrayOfAdjustmentLayerNames.length) { - var remainingArgs = arrayOfAdjustmentLayerNames.length; - var message = remainingArgs + " adjustment layers found: "; - - for (var i = 0; i < arrayOfAdjustmentLayerNames.length; i++) { - message += arrayOfAdjustmentLayerNames[i]; - remainingArgs--; - if (remainingArgs > 1) { - message += ', '; - } - if (remainingArgs === 1) { - message += ", and "; - } - if (remainingArgs === 0) { - message += "."; - } - } - $._PPP_.updateEventPanel(message); - } else { - $._PPP_.updateEventPanel("No adjustment layers found in " + app.project.name + "."); - } - }, - - consolidateDuplicates: function () { - result = app.project.consolidateDuplicates(); - $._PPP_.updateEventPanel("Duplicates consolidated in " + app.project.name + "."); - }, - - closeAllSequences: function () { - var seqList = app.project.sequences; - for (var a = 0; a < seqList.numSequences; a++) { - var currentSeq = seqList[a]; - if (currentSeq) { - currentSeq.close(); - } else { - $._PPP_.updateEventPanel("No sequences from " + app.project.name + " were open."); - } - } - }, - - dumpAllPresets: function () { - var desktopPath = new File("~/Desktop"); - var outputFileName = desktopPath.fsName + $._PPP_.getSep() + 'available_presets.txt'; - var selectedPreset = undefined; - var selectedExporter = undefined; - var exporters = app.encoder.getExporters(); - - var outFile = new File(outputFileName); - - outFile.encoding = "UTF8"; - outFile.open("w", "TEXT", "????"); - - for (var i = 0; i < exporters.length; i++) { - var exporter = exporters[i]; - if (exporter) { - outFile.writeln('-----------------------------------------------'); - outFile.writeln(i + ':' + exporter.name + ' : ' + exporter.classID + ' : ' + exporter.fileType); - var presets = exporter.getPresets(); - if (presets) { - outFile.writeln(presets.length + ' presets found'); - for (var j = 0; j < presets.length; j++) { - var preset = presets[j]; - if (preset) { - outFile.writeln('matchName: ' + preset.matchName + '(' + preset.name + ')'); - if (preset.name.indexOf('TQM') > -1) { - selectedPreset = preset; - selectedExporter = exporter; - outFile.writeln('selected preset = ' + selectedExporter.name + ' : ' + selectedPreset.name); - selectedPreset.writeToFile(desktopPath.fsName + $._PPP_.getSep() + preset.name + ".epr"); - $._PPP_.updateEventPanel("List of available presets saved to desktop as \'available_presets.txt\'"); - } - } - } - } - } - } - desktopPath.close(); - outFile.close(); - }, - - reportSequenceVRSettings: function () { - var seq = app.project.activeSequence; - if (seq) { - var settings = seq.getSettings(); - if (settings) { - $._PPP_.updateEventPanel("===================================================="); - $._PPP_.updateEventPanel("VR Settings for \'" + seq.name + "\':"); - $._PPP_.updateEventPanel(""); - $._PPP_.updateEventPanel(" Horizontal captured view: " + settings.vrHorzCapturedView); - $._PPP_.updateEventPanel(" Vertical captured view: " + settings.vrVertCapturedView); - $._PPP_.updateEventPanel(" Layout: " + settings.Layout); - $._PPP_.updateEventPanel(" Projection: " + settings.vrProjection); - $._PPP_.updateEventPanel(""); - $._PPP_.updateEventPanel("===================================================="); - } - } - }, - - openProjectItemInSource: function () { - var viewIDs = app.getProjectViewIDs(); - if (viewIDs) { - for (var a = 0; a < app.projects.numProjects; a++) { - var currentProject = app.getProjectFromViewID(viewIDs[a]); - if (currentProject) { - if (currentProject.documentID === app.project.documentID) { // We're in the right project! - var selectedItems = app.getProjectViewSelection(viewIDs[a]); - for (var b = 0; b < selectedItems.length; b++) { - var currentItem = selectedItems[b]; - if (currentItem) { - if (currentItem.type !== ProjectItemType.BIN) { // For every selected item which isn't a bin or sequence... - app.sourceMonitor.openProjectItem(currentItem); - } - } else { - $._PPP_.updateEventPanel("No item available."); - } - } - } - } else { - $._PPP_.updateEventPanel("No project available."); - } - } - } else { - $._PPP_.updateEventPanel("No view IDs available."); - } - }, - - reinterpretFootage: function () { - var viewIDs = app.getProjectViewIDs(); - if (viewIDs) { - for (var a = 0; a < app.projects.numProjects; a++) { - var currentProject = app.getProjectFromViewID(viewIDs[a]); - if (currentProject) { - if (currentProject.documentID === app.project.documentID) { // We're in the right project! - var selectedItems = app.getProjectViewSelection(viewIDs[a]); - if (selectedItems) { - for (var b = 0; b < selectedItems.length; b++) { - var currentItem = selectedItems[b]; - if (currentItem) { - if ((currentItem.type !== ProjectItemType.BIN) && - (currentItem.isSequence() === false)) { - var interp = currentItem.getFootageInterpretation(); - if (interp) { - // Note: I made this something terrible, so the change is apparent. - interp.frameRate = 17.868; - interp.pixelAspectRatio = 1.2121; - currentItem.setFootageInterpretation(interp); - } else { - $._PPP_.updateEventPanel("Unable to get interpretation for " + currentItem.name + "."); - } - var mapping = currentItem.getAudioChannelMapping; - if (mapping) { - mapping.audioChannelsType = AUDIOCHANNELTYPE_Stereo; - mapping.audioClipsNumber = 1; - mapping.setMappingForChannel(0, 4); // 1st param = channel index, 2nd param = source index - mapping.setMappingForChannel(1, 5); - currentItem.setAudioChannelMapping(mapping); // submit changed mapping object - } - } - } else { - $._PPP_.updateEventPanel("No project item available."); - } - } - } else { - $._PPP_.updateEventPanel("No items selected."); - } - } - } else { - $._PPP_.updateEventPanel("No project available."); - } - } - } else { - $._PPP_.updateEventPanel("No view IDs available."); - } - }, - - createSubSequence: function () { - - /* Behavioral Note - - createSubSequence() uses track targeting to select clips when there is - no current clip selection, in the sequence. To create a subsequence with - clips on tracks that are currently NOT targeted, either select some clips - (on any track), or temporarily target all desired tracks. - - */ - - var activeSequence = app.project.activeSequence; - if (activeSequence) { - var foundTarget = false; - for (var a = 0; - (a < activeSequence.videoTracks.numTracks) && (foundTarget === false); a++) { - var vTrack = activeSequence.videoTracks[a]; - if (vTrack) { - if (vTrack.isTargeted()) { - foundTarget = true; - } - } - } - // If no targeted track was found, just target the zero-th track, for demo purposes - if (foundTarget === false) { - activeSequence.videotracks[0].setTargeted(true, true); - } - - var cloneAnyway = true; - if ((activeSequence.getInPoint() == NOT_SET) && (activeSequence.getOutPoint() == NOT_SET)) { - cloneAnyway = confirm("No in or out points set; clone entire sequence?", false, "Clone the whole thing?"); - } - if (cloneAnyway) { - var ignoreMapping = confirm("Ignore track mapping?", false, "Ignore track mapping?"); - var newSeq = activeSequence.createSubsequence(ignoreMapping); - // rename newSeq here, as desired. - } - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - selectAllRetimedClips: function () { - var activeSeq = app.project.activeSequence; - var numRetimedClips = 0; - if (activeSeq) { - var trackGroups = [activeSeq.audioTracks, activeSeq.videoTracks]; - var trackGroupNames = ["audioTracks", "videoTracks"]; - var updateUI = true; - - for (var gi = 0; gi < 2; gi++) { - group = trackGroups[gi]; - for (var ti = 0; ti < group.numTracks; ti++) { - var track = group[ti]; - var clips = track.clips; - for (var ci = 0; ci < clips.numTracks; ci++) { - var clip = clips[ci]; - if (clip.getSpeed() !== 1) { - clip.setSelected(true, updateUI); - numRetimedClips++; - } - } - } - } - $._PPP_.updateEventPanel(numRetimedClips + " retimed clips found."); - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - selectReversedClips: function () { - var sequence = app.project.activeSequence; - var numReversedClips = 0; - if (sequence) { - var trackGroups = [sequence.audioTracks, sequence.videoTracks]; - var trackGroupNames = ["audioTracks", "videoTracks"]; - var updateUI = true; - - for (var gi = 0; gi < 2; gi++) { - for (var ti = 0; ti < group.numTracks; ti++) { - for (var ci = 0; ci < group[ti].clips.numTracks; ci++) { - var clip = group[ti].clips[ci]; - var isReversed = clip.isSpeedReversed(); - if (isReversed) { - clip.setSelected(isReversed, updateUI); - numReversedClips++; - } - } - } - } - $._PPP_.updateEventPanel(numReversedClips + " reversed clips found."); - } else { - $._PPP_.updateEventPanel("No active sequence."); - } - }, - - logConsoleOutput: function () { - app.enableQE(); - var logFileName = "PPro_Console_output.txt" - var outFolder = Folder.selectDialog("Where do you want to save the log file?"); - if (outFolder) { - var entireOutputPath = outFolder.fsName + $._PPP_.getSep() + logFileName; - var result = qe.executeConsoleCommand("con.openlog " + entireOutputPath); - $._PPP_.updateEventPanel("Log opened at " + entireOutputPath + "."); - } - }, - - closeLog: function () { - app.enableQE(); - qe.executeConsoleCommand("con.closelog"); - }, - - stitch: function (presetPath) { - var viewIDs = app.getProjectViewIDs(); - var allPathsToStitch = ""; - - for (var a = 0; a < app.projects.numProjects; a++) { - var currentProject = app.getProjectFromViewID(viewIDs[a]); - if (currentProject) { - if (currentProject.documentID === app.project.documentID) { // We're in the right project! - var selectedItems = app.getProjectViewSelection(viewIDs[a]); - if (selectedItems.length) { - for (var b = 0; b < selectedItems.length; b++) { - var currentItem = selectedItems[b]; - if (currentItem) { - if ((!currentItem.isSequence()) && (currentItem.type !== ProjectItemType.BIN)) { // For every selected item which isn't a bin or sequence... - allPathsToStitch += currentItem.getMediaPath(); - allPathsToStitch += ";"; - } - } - } - - var AMEString = "var fe = app.getFrontend(); fe.stitchFiles(\"" + allPathsToStitch + "\""; - var addendum = ", \"H.264\", \"" + presetPath + "\", " + "\"(This path parameter is never used)\");"; - - AMEString += addendum; - - // 3. Send Command to AME for Export // - var bt = new BridgeTalk(); - bt.target = 'ame'; - bt.body = AMEString; - bt.send(); - - - - } - } - } - } - }, - - clearESTKConsole: function () { - var bt = new BridgeTalk(); - bt.target = 'estoolkit-4.0'; - bt.body = function () { - app.clc(); - }.toSource() + "()"; - bt.send(); - } -}; diff --git a/pype/hosts/premiere/extensions/com.pype.rename/jsx/PypeRename.jsx b/pype/hosts/premiere/extensions/com.pype.rename/jsx/PypeRename.jsx deleted file mode 100644 index ac405761b6..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/jsx/PypeRename.jsx +++ /dev/null @@ -1,359 +0,0 @@ -/* global app, XMPMeta, ExternalObject, CSXSEvent, Folder */ -/* -------------------------------------- - -. == [ part 0f PyPE CluB ] == .- -_______________.___._____________________ -\______ \__ | |\______ \_ _____/ - | ___// | | | ___/| __)_ - | | \____ | | | | \ - |____| / ______| |____| /_______ / - \/ \/ - .. __/ CliP R3N4M3R \__ .. -*/ -var renamer = {}; - -/** - * Sequence-rename selected clips and establish their hierarchy based upon provided - * data. Using data.folder, data.episode, data.sequence to name a clip and write - * resulting hierarchical clip data into sequence metadata via XMP. - * - * @param {Object} data - data {'folder', 'episode', 'sequence', 'pattern', 'increment', 'start'} - * @return {String} state - */ -renamer.renameSeqHierarchy = function (data) { // eslint-disable-line no-unused-vars - var sequence = app.project.activeSequence; - var selected = sequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - app.setSDKEventMessage('pattern ' + data.pattern + '\n' + 'increment ' + data.increment, 'info'); - // get padding - var padPtr = RegExp('(?:.*?)(#+)(.*)'); - var res = data.pattern.match(padPtr); - // res is now null if there is no padding string (###) in Pattern - // res[1] is padding string - if (!res) { - app.setSDKEventMessage('no padding string detected in pattern ' + data.pattern, 'error'); - return false; - } - - // convert to int - var index = parseInt(data.start); - // change padding string to zero: '####' -> '0000' - var rx = RegExp('#', 'g'); - var fgexp = RegExp('{folder}', 'i'); - var egexp = RegExp('{episode}', 'i'); - var sgexp = RegExp('{sequence}', 'i'); - var shotrg = RegExp('{shot}', 'i'); - var zero = res[1].replace(rx, '0'); - // iterate over selection - var metadata = renamer.getSequencePypeMetadata(sequence); - for (var c = 0; c < selected.length; c++) { - var mediaType = selected[c].mediaType; - if (mediaType === 'Audio') { - continue - } - delete metadata.clips[selected[c].name]; - // convert index to string - var indexStr = '' + index; - // left-zero pad number - var padding = zero.substring(0, zero.length - indexStr.length) + indexStr; - // put name together - // replace {shot} token - selected[c].name = data.pattern.replace(shotrg, selected[c].name); - selected[c].name = selected[c].name.replace(res[1], padding); - selected[c].name = selected[c].name.replace(fgexp, data.folder); - selected[c].name = selected[c].name.replace(egexp, data.episode); - selected[c].name = selected[c].name.replace(sgexp, data.sequence); - - // fill in hierarchy if set - var parents = []; - var hierarchy = []; - - if (data.folder) { - parents.push({ - 'entityType': 'folder', - 'entityName': data.folder - }); - hierarchy.push(data.folder); - } - - if (data.episode) { - parents.push({ - 'entityType': 'episode', - 'entityName': data.episode - }); - hierarchy.push(data.episode); - } - - if (data.sequence) { - parents.push({ - 'entityType': 'sequence', - 'entityName': data.sequence - }); - hierarchy.push(data.sequence); - } - - // push it to metadata - metadata.clips[selected[c].name] = { - 'parents': parents, - 'hierarchy': hierarchy.join('/'), - }; - - // add increment - index = index + parseInt(data.increment); - } - - renamer.setSequencePypeMetadata(sequence, metadata); - - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); - -}; - -/** - * Sequence rename seleced clips - * @param {Object} data - {pattern, start, increment} - */ -renamer.renameSeq = function (data) { // eslint-disable-line no-unused-vars - var selected = app.project.activeSequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - app.setSDKEventMessage('pattern ' + data.pattern + '\n' + 'increment ' + data.increment, 'info'); - // get padding - var padPtr = RegExp('(?:.*?)(#+)(?:.*)'); - var res = data.pattern.match(padPtr); - // res is now null if there is no padding string (###) in Pattern - // res[1] is padding string - - if (!res) { - app.setSDKEventMessage('no padding string detected in pattern ' + data.pattern, 'error'); - return false; - } - - // convert to int - var index = parseInt(data.start); - // change padding string to zero: '####' -> '0000' - var rx = RegExp('#', 'g'); - var zero = res[2].replace(rx, '0'); - // iterate over selection - for (var c = 0; c < selected.length; c++) { - // convert index to string - var indexStr = '' + index; - // left-zero pad number - var padding = zero.substring(0, zero.length - indexStr.length) + indexStr; - // put name together - selected[c].name = data.pattern.replace(res[1], padding); - // add increment - index = index + parseInt(data.increment); - } - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); -}; - -/** - * Simple rename clips - * @param {string} newName - new clip name. `{shot}` designates current clip name - * @return {string} result - return stringified JSON status - */ -renamer.renameSimple = function (newName) { // eslint-disable-line no-unused-vars - app.setSDKEventMessage('Replacing with pattern ' + newName, 'info'); - var selected = app.project.activeSequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - var rx = RegExp('{shot}', 'i'); - for (var c = 0; c < selected.length; c++) { - // find {shot} token and replace it with existing clip name - selected[c].name = newName.replace(rx, selected[c].name); - } - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); -}; - -/** - * Find string in clip name and replace it with another - * @param {Object} data - {find, replaceWith} object - * @return {string} result - return stringified JSON status - */ -renamer.renameFindReplace = function (data) { // eslint-disable-line no-unused-vars - var selected = app.project.activeSequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - - var rx = RegExp('{shot}', 'i'); - for (var c = 0; c < selected.length; c++) { - // replace {shot} token with actual clip name - var find = data.find.replace(rx, selected[c].name); - var repl = data.replaceWith.replace(rx, selected[c].name); - // replace find with replaceWith - selected[c].name = selected[c].name.replace(find, repl); - } - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); -}; - -/** - * Replace current clip name with filename (without extension) - * @return {string} result - return stringified JSON status - */ -renamer.renameClipRename = function () { // eslint-disable-line no-unused-vars - var selected = app.project.activeSequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - - var regexp = new RegExp('.[^/.]+$'); - for (var c = 0; c < selected.length; c++) { - // suddenly causes syntax error on regexp? So using explicit contructor - // regexp above. - // selected[c].name = selected[c].projectItem.name.replace(/\.[^/.]+$/, ''); - selected[c].name = selected[c].projectItem.name.replace(regexp, ''); - } - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); -}; - -/** - * Change clip name to lower or upper case - * @param {int} case - 0 lower, 1 upper - * @return {string} result - return stringified JSON status - */ -renamer.renameChangeCase = function (caseMode) { // eslint-disable-line no-unused-vars - var selected = app.project.activeSequence.getSelection(); - if (selected.length < 1) { - app.setSDKEventMessage('nothing selected', 'error'); - return false; - } - - for (var c = 0; c < selected.length; c++) { - if (caseMode === 0) { - selected[c].name = selected[c].name.toLowerCase(); - } else { - selected[c].name = selected[c].name.toUpperCase(); - } - } - return JSON.stringify({ - 'status': 'renamed ' + selected.length + ' clips' - }); -}; - -/** - * Set Pype metadata into sequence metadata using XMP. - * This is `hackish` way to get over premiere lack of addressing unique clip on timeline, - * so we cannot store data directly per clip. - * - * @param {Object} sequence - sequence object - * @param {Object} data - to be serialized and saved - */ -renamer.setSequencePypeMetadata = function (sequence, data) { // eslint-disable-line no-unused-vars - var kPProPrivateProjectMetadataURI = 'http://ns.adobe.com/premierePrivateProjectMetaData/1.0/'; - var metadata = sequence.projectItem.getProjectMetadata(); - var pypeData = 'pypeData'; - var xmp = new XMPMeta(metadata); - var dataJSON = JSON.stringify(data); - app.project.addPropertyToProjectMetadataSchema(pypeData, 'Pype Data', 2); - - xmp.setProperty(kPProPrivateProjectMetadataURI, pypeData, dataJSON); - - var str = xmp.serialize(); - sequence.projectItem.setProjectMetadata(str, [pypeData]); - - // test - var newMetadata = sequence.projectItem.getProjectMetadata(); - var newXMP = new XMPMeta(newMetadata); - var found = newXMP.doesPropertyExist(kPProPrivateProjectMetadataURI, pypeData); - if (!found) { - app.setSDKEventMessage('metadata not set', 'error'); - } -}; - -/** - * Get Pype metadata from sequence using XMP. - * @param {Object} sequence - * @return {Object} - */ -renamer.getSequencePypeMetadata = function (sequence) { // eslint-disable-line no-unused-vars - var kPProPrivateProjectMetadataURI = 'http://ns.adobe.com/premierePrivateProjectMetaData/1.0/'; - var metadata = sequence.projectItem.getProjectMetadata(); - var pypeData = 'pypeData'; - var pypeDataN = 'Pype Data'; - var xmp = new XMPMeta(metadata); - app.project.addPropertyToProjectMetadataSchema(pypeData, pypeDataN, 2); - var pypeDataValue = xmp.getProperty(kPProPrivateProjectMetadataURI, pypeData); - if (pypeDataValue === undefined) { - var metadata = { - clips: {}, - tags: {} - }; - renamer.setSequencePypeMetadata(sequence, metadata); - pypeDataValue = xmp.getProperty(kPProPrivateProjectMetadataURI, pypeData); - return renamer.getSequencePypeMetadata(sequence); - } else { - return JSON.parse(pypeDataValue); - } -}; - -function keepExtension() { - return app.setExtensionPersistent('com.pype.rename', 0); -} - -/** - * Dispatch event with new selection - */ -renamer.activeSequenceSelectionChanged = function () { - var sel = app.project.activeSequence.getSelection(); - var selection = []; - for (var i = 0; i < sel.length; i++) { - if (sel[i].name !== 'anonymous') { - selection.push({ - 'name': sel[i].name, - 'path': sel[i].projectItem.getMediaPath() - }); - } - } - - var eoName; - if (Folder.fs === 'Macintosh') { - eoName = 'PlugPlugExternalObject'; - } else { - eoName = 'PlugPlugExternalObject.dll'; - } - - var mylib = new ExternalObject('lib:' + eoName); - - var eventObj = new CSXSEvent(); - eventObj.type = 'activeSequenceSelectionChanged'; - eventObj.data = JSON.stringify(selection); - eventObj.dispatch(); - // app.setSDKEventMessage('selection changed', 'info'); -}; - -/** - * Register active selection event dispatching - */ -renamer.registerActiveSelectionChanged = function () { - var success = app.bind('onActiveSequenceSelectionChanged', renamer.activeSequenceSelectionChanged); - return success; -}; - -keepExtension(); - -// load the XMPScript library -if (ExternalObject.AdobeXMPScript === undefined) { - ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); -} - -// var seq = app.project.activeSequence; -// renamer.getSequencePypeMetadata(seq); diff --git a/pype/hosts/premiere/extensions/com.pype.rename/lib/CEPEngine_extensions.js b/pype/hosts/premiere/extensions/com.pype.rename/lib/CEPEngine_extensions.js deleted file mode 100644 index 04f5516a2d..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/lib/CEPEngine_extensions.js +++ /dev/null @@ -1,699 +0,0 @@ -/************************************************************************************************** -* -* ADOBE SYSTEMS INCORPORATED -* Copyright 2013 Adobe Systems Incorporated -* All Rights Reserved. -* -* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the -* terms of the Adobe license agreement accompanying it. If you have received this file from a -* source other than Adobe, then your use, modification, or distribution of it requires the prior -* written permission of Adobe. -* -**************************************************************************************************/ - -// This is the JavaScript code for bridging to native functionality -// See CEPEngine_extensions.cpp for implementation of native methods. -// -// Note: So far all native file i/o functions are synchronous, and aynchronous file i/o is TBD. - -/** Version v8.0.0 */ - -/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */ -/*global define, native */ - -var cep; -if (!cep) { - cep = {}; -} -if (!cep.fs) { - cep.fs = {}; -} -if (!cep.process) { - cep.process = {}; -} -if (!cep.encoding) { - cep.encoding = {}; -} -if (!cep.util) { - cep.util = {}; -} -(function () { - // Internal function to get the last error code. - native function GetLastError(); - function getLastError() { - return GetLastError(); - } - - function getErrorResult(){ - var result = {err: getLastError()}; - return result; - } - - // Error values. These MUST be in sync with the error values - // at the top of CEPEngine_extensions.cpp - - /** - * @constant No error. - */ - cep.fs.NO_ERROR = 0; - - /** - * @constant Unknown error occurred. - */ - cep.fs.ERR_UNKNOWN = 1; - - /** - * @constant Invalid parameters passed to function. - */ - cep.fs.ERR_INVALID_PARAMS = 2; - - /** - * @constant File or directory was not found. - */ - cep.fs.ERR_NOT_FOUND = 3; - - /** - * @constant File or directory could not be read. - */ - cep.fs.ERR_CANT_READ = 4; - - /** - * @constant An unsupported encoding value was specified. - */ - cep.fs.ERR_UNSUPPORTED_ENCODING = 5; - - /** - * @constant File could not be written. - */ - cep.fs.ERR_CANT_WRITE = 6; - - /** - * @constant Target directory is out of space. File could not be written. - */ - cep.fs.ERR_OUT_OF_SPACE = 7; - - /** - * @constant Specified path does not point to a file. - */ - cep.fs.ERR_NOT_FILE = 8; - - /** - * @constant Specified path does not point to a directory. - */ - cep.fs.ERR_NOT_DIRECTORY = 9; - - /** - * @constant Specified file already exists. - */ - cep.fs.ERR_FILE_EXISTS = 10; - - /** - * @constant The maximum number of processes has been exceeded. - */ - cep.process.ERR_EXCEED_MAX_NUM_PROCESS = 101; - - /** - * @constant Invalid URL. - */ - cep.util.ERR_INVALID_URL = 201; - - /** - * @constant deprecated API. - */ - cep.util.DEPRECATED_API = 202; - - /** - * @constant UTF8 encoding type. - */ - cep.encoding.UTF8 = "UTF-8"; - - /** - * @constant Base64 encoding type. - */ - cep.encoding.Base64 = "Base64"; - - /** - * Displays the OS File Open dialog, allowing the user to select files or directories. - * - * @param allowMultipleSelection {boolean} When true, multiple files/folders can be selected. - * @param chooseDirectory {boolean} When true, only folders can be selected. When false, only - * files can be selected. - * @param title {string} Title of the open dialog. - * @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to - * display the last path chosen. - * @param fileTypes {Array.} The file extensions (without the dot) for the types - * of files that can be selected. Ignored when chooseDirectory=true. - * - * @return An object with these properties: - *
  • "data": An array of the full names of the selected files.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_INVALID_PARAMS
  • - *
- **/ - native function ShowOpenDialog(); - cep.fs.showOpenDialog = function (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes) { - var resultString = ShowOpenDialog(allowMultipleSelection, chooseDirectory, - title || 'Open', initialPath || '', - fileTypes ? fileTypes.join(' ') : ''); - - var result = {data: JSON.parse(resultString || '[]'), err: getLastError() }; - return result; - }; - - /** - * Displays the OS File Open dialog, allowing the user to select files or directories. - * - * @param allowMultipleSelection {boolean} When true, multiple files/folders can be selected. - * @param chooseDirectory {boolean} When true, only folders can be selected. When false, only - * files can be selected. - * @param title {string} Title of the open dialog. - * @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to - * display the last path chosen. - * @param fileTypes {Array.} The file extensions (without the dot) for the types - * of files that can be selected. Ignored when chooseDirectory=true. - * @param friendlyFilePrefix {string} String to put in front of the extensions - * of files that can be selected. Ignored when chooseDirectory=true. (win only) - * For example: - * fileTypes = ["gif", "jpg", "jpeg", "png", "bmp", "webp", "svg"]; - * friendlyFilePrefix = "Images (*.gif;*.jpg;*.jpeg;*.png;*.bmp;*.webp;*.svg)"; - * @param prompt {string} String for OK button (mac only, default is "Open" on mac, "Open" or "Select Folder" on win). - * - * @return An object with these properties: - *
  • "data": An array of the full names of the selected files.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_INVALID_PARAMS
  • - *
- **/ - native function ShowOpenDialogEx(); - cep.fs.showOpenDialogEx = function (allowMultipleSelection, chooseDirectory, title, initialPath, fileTypes, - friendlyFilePrefix, prompt) { - var resultString = ShowOpenDialogEx(allowMultipleSelection, chooseDirectory, - title || 'Open', initialPath || '', - fileTypes ? fileTypes.join(' ') : '', friendlyFilePrefix || '', - prompt || ''); - - var result = {data: JSON.parse(resultString || '[]'), err: getLastError() }; - return result; - }; - - /** - * Displays the OS File Save dialog, allowing the user to type in a file name. - * - * @param title {string} Title of the save dialog. - * @param initialPath {string} Initial path to display in the dialog. Pass NULL or "" to - * display the last path chosen. - * @param fileTypes {Array.} The file extensions (without the dot) for the types - * of files that can be selected. - * @param defaultName {string} String to start with for the file name. - * @param friendlyFilePrefix {string} String to put in front of the extensions of files that can be selected. (win only) - * For example: - * fileTypes = ["gif", "jpg", "jpeg", "png", "bmp", "webp", "svg"]; - * friendlyFilePrefix = "Images (*.gif;*.jpg;*.jpeg;*.png;*.bmp;*.webp;*.svg)"; - * @param prompt {string} String for Save button (mac only, default is "Save" on mac and win). - * @param nameFieldLabel {string} String displayed in front of the file name text field (mac only, "File name:" on win). - * - * @return An object with these properties: - *
  • "data": The file path selected to save at or "" if canceled
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_INVALID_PARAMS
  • - *
- **/ - native function ShowSaveDialogEx(); - cep.fs.showSaveDialogEx = function (title, initialPath, fileTypes, defaultName, friendlyFilePrefix, prompt, nameFieldLabel) { - var resultString = ShowSaveDialogEx(title || '', initialPath || '', - fileTypes ? fileTypes.join(' ') : '', defaultName || '', - friendlyFilePrefix || '', prompt || '', nameFieldLabel || ''); - - var result = {data: resultString || '', err: getLastError() }; - return result; - }; - - /** - * Reads the contents of a folder. - * - * @param path {string} The path of the folder to read. - * - * @return An object with these properties: - *
  • "data": An array of the names of the contained files (excluding '.' and '..'.
  • - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_NOT_FOUND - *
    ERR_CANT_READ
- **/ - native function ReadDir(); - cep.fs.readdir = function (path) { - var resultString = ReadDir(path); - var result = {data: JSON.parse(resultString || '[]'), err: getLastError() }; - return result; - }; - - /** - * Creates a new folder. - * - * @param path {string} The path of the folder to create. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS
- **/ - native function MakeDir(); - cep.fs.makedir = function (path) { - MakeDir(path); - return getErrorResult(); - }; - - /** - * Renames a file or folder. - * - * @param oldPath {string} The old name of the file or folder. - * @param newPath {string} The new name of the file or folder. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_NOT_FOUND - *
    ERR_FILE_EXISTS
- **/ - native function Rename(); - cep.fs.rename = function(oldPath, newPath) { - Rename(oldPath, newPath); - return getErrorResult(); - }; - - /** - * Reports whether an item is a file or folder. - * - * @param path {string} The path of the file or folder. - * - * @return An object with these properties: - *
  • "data": An object with properties - *
    isFile (boolean) - *
    isDirectory (boolean) - *
    mtime (modification DateTime)
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_NOT_FOUND
  • - *
- **/ - native function IsDirectory(); - native function GetFileModificationTime(); - cep.fs.stat = function (path) { - var isDir = IsDirectory(path); - var modtime = GetFileModificationTime(path); - var result = { - data: { - isFile: function () { - return !isDir; - }, - isDirectory: function () { - return isDir; - }, - mtime: modtime - }, - err: getLastError() - }; - - return result; - }; - - /** - * Reads the entire contents of a file. - * - * @param path {string} The path of the file to read. - * @param encoding {string} The encoding of the contents of file, one of - * UTF8 (the default) or Base64. - * - * @return An object with these properties: - *
  • "data": The file contents.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_NOT_FOUND - *
    ERR_CANT_READ - *
    ERR_UNSUPPORTED_ENCODING
  • - *
- **/ - native function ReadFile(); - cep.fs.readFile = function (path, encoding) { - encoding = encoding ? encoding : cep.encoding.UTF8; - var contents = ReadFile(path, encoding); - var result = {data: contents, err: getLastError() }; - return result; - }; - - /** - * Writes data to a file, replacing the file if it already exists. - * - * @param path {string} The path of the file to write. - * @param data {string} The data to write to the file. - * @param encoding {string} The encoding of the contents of file, one of - * UTF8 (the default) or Base64. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_UNSUPPORTED_ENCODING - *
    ERR_CANT_WRITE - *
    ERR_OUT_OF_SPACE
- **/ - native function WriteFile(); - cep.fs.writeFile = function (path, data, encoding) { - encoding = encoding ? encoding : cep.encoding.UTF8; - WriteFile(path, data, encoding); - return getErrorResult(); - }; - - /** - * Sets permissions for a file or folder. - * - * @param path {string} The path of the file or folder. - * @param mode {number} The permissions in numeric format (for example, 0777). - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_CANT_WRITE
- **/ - native function SetPosixPermissions(); - cep.fs.chmod = function (path, mode) { - SetPosixPermissions(path, mode); - return getErrorResult(); - }; - - /** - * Deletes a file. - * - * @param path {string} The path of the file to delete. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_NOT_FOUND - *
    ERR_NOT_FILE
- **/ - native function DeleteFileOrDirectory(); - native function IsDirectory(); - cep.fs.deleteFile = function (path) { - if (IsDirectory(path)) { - var result = {err: cep.fs.ERR_NOT_FILE}; - return result; - } - DeleteFileOrDirectory(path); - return getErrorResult(); - }; - - /** - * Creates a process. - * - * @param arguments {list} The arguments to create process. The first one is the full path of the executable, - * followed by the arguments of the executable. - * - * @return An object with these properties: - *
  • "data": The pid of the process, or -1 on error.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_EXCEED_MAX_NUM_PROCESS - *
    ERR_NOT_FOUND - *
    ERR_NOT_FILE
  • - *
- **/ - native function CreateProcess(); - cep.process.createProcess = function () { - var args = Array.prototype.slice.call(arguments); - var pid = CreateProcess(args); - var result = {data: pid, err: getLastError()}; - return result; - }; - - /** - * Registers a standard-output handler for a process. - * - * @param pid {int} The pid of the process. - * @param callback {function} The handler function for the standard output callback. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function SetupStdOutHandler(); - cep.process.stdout = function (pid, callback) { - SetupStdOutHandler(pid, callback); - return getErrorResult(); - }; - - /** - * Registers up a standard-error handler for a process. - * - * @param pid {int} The pid of the process. - * @param callback {function} The handler function for the standard error callback. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function SetupStdErrHandler(); - cep.process.stderr = function (pid, callback) { - SetupStdErrHandler(pid, callback); - return getErrorResult(); - }; - - /** - * Writes data to the standard input of a process. - * - * @param pid {int} The pid of the process - * @param data {string} The data to write. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function WriteStdIn(); - cep.process.stdin = function (pid, data) { - WriteStdIn(pid, data); - return getErrorResult(); - }; - - /** - * Retrieves the working directory of a process. - * - * @param pid {int} The pid of the process. - * - * @return An object with these properties: - *
  • "data": The path of the working directory.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function GetWorkingDirectory(); - cep.process.getWorkingDirectory = function (pid) { - var wd = GetWorkingDirectory(pid); - var result = {data: wd, err: getLastError()}; - return result; - }; - - /** - * Waits for a process to quit. - * - * @param pid {int} The pid of the process. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function WaitFor(); - cep.process.waitfor = function (pid) { - WaitFor(pid); - return getErrorResult(); - }; - - /** - * Registers a handler for the onquit callback of a process. - * - * @param pid {int} The pid of the process. - * @param callback {function} The handler function. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function OnQuit(); - cep.process.onquit = function (pid, callback) { - OnQuit(pid, callback); - return getErrorResult(); - }; - - /** - * Reports whether a process is currently running. - * - * @param pid {int} The pid of the process. - * - * @return An object with these properties: - *
  • "data": True if the process is running, false otherwise.
  • - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function IsRunning(); - cep.process.isRunning = function (pid) { - var isRunning = IsRunning(pid); - var result = {data: isRunning, err: getLastError()}; - return result; - }; - - /** - * Terminates a process. - * - * @param pid {int} The pid of the process - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS - *
    ERR_INVALID_PROCESS_ID
- **/ - native function Terminate(); - cep.process.terminate = function (pid) { - Terminate(pid); - return getErrorResult(); - }; - - /** - * Encoding conversions. - * - */ - cep.encoding.convertion = - { - utf8_to_b64: function(str) { - return window.btoa(unescape(encodeURIComponent(str))); - }, - - b64_to_utf8: function(base64str) { - // If a base64 string contains any whitespace character, DOM Exception 5 occurs during window.atob, please see - // http://stackoverflow.com/questions/14695988/dom-exception-5-invalid-character-error-on-valid-base64-image-string-in-javascri - base64str = base64str.replace(/\s/g, ''); - return decodeURIComponent(escape(window.atob(base64str))); - }, - - binary_to_b64: function(binary) { - return window.btoa(binary); - }, - - b64_to_binary: function(base64str) { - return window.atob(base64str); - }, - - ascii_to_b64: function(ascii) { - return window.btoa(binary); - }, - - b64_to_ascii: function(base64str) { - return window.atob(base64str); - } - }; - - /** - * Opens a page in the default system browser. - * - * @param url {string} The URL of the page/file to open, or the email address. - * Must use HTTP/HTTPS/file/mailto. For example: - * "http://www.adobe.com" - * "https://github.com" - * "file:///C:/log.txt" - * "mailto:test@adobe.com" - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_UNKNOWN - *
    ERR_INVALID_PARAMS
- **/ - native function OpenURLInDefaultBrowser(); - cep.util.openURLInDefaultBrowser = function (url) { - if (url && (url.indexOf("http://") === 0 || - url.indexOf("https://") === 0 || - url.indexOf("file://") === 0 || - url.indexOf("mailto:") === 0)) { - OpenURLInDefaultBrowser(url); - return getErrorResult(); - } else { - return { err : cep.util.ERR_INVALID_URL }; - } - }; - - /** - * Registers a callback function for extension unload. If called more than once, - * the last callback that is successfully registered is used. - * - * @deprecated since version 6.0.0 - * - * @param callback {function} The handler function. - * - * @return An object with this property: - *
  • "err": The status of the operation, one of: - *
    NO_ERROR - *
    ERR_INVALID_PARAMS
- **/ - native function RegisterExtensionUnloadCallback(); - cep.util.registerExtensionUnloadCallback = function (callback) { - return { err : cep.util.DEPRECATED_API }; - }; - - /** - * Stores the user's proxy credentials - * - * @param username {string} proxy username - * @param password {string} proxy password - * - * @return An object with this property: - *
  • "err": The status of the operation, one of - *
    NO_ERROR - *
    ERR_INVALID_PARAMS
  • - *
- **/ - native function StoreProxyCredentials(); - cep.util.storeProxyCredentials = function (username, password) { - StoreProxyCredentials(username, password); - return getErrorResult(); - }; - -})(); diff --git a/pype/hosts/premiere/extensions/com.pype.rename/lib/CSInterface.js b/pype/hosts/premiere/extensions/com.pype.rename/lib/CSInterface.js deleted file mode 100644 index e2a6e02eb2..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/lib/CSInterface.js +++ /dev/null @@ -1,1291 +0,0 @@ -/************************************************************************************************** -* -* ADOBE SYSTEMS INCORPORATED -* Copyright 2013 Adobe Systems Incorporated -* All Rights Reserved. -* -* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the -* terms of the Adobe license agreement accompanying it. If you have received this file from a -* source other than Adobe, then your use, modification, or distribution of it requires the prior -* written permission of Adobe. -* -**************************************************************************************************/ - -/** CSInterface - v9.2.0 */ - -/** - * Stores constants for the window types supported by the CSXS infrastructure. - */ -function CSXSWindowType() -{ -} - -/** Constant for the CSXS window type Panel. */ -CSXSWindowType._PANEL = "Panel"; - -/** Constant for the CSXS window type Modeless. */ -CSXSWindowType._MODELESS = "Modeless"; - -/** Constant for the CSXS window type ModalDialog. */ -CSXSWindowType._MODAL_DIALOG = "ModalDialog"; - -/** EvalScript error message */ -EvalScript_ErrMessage = "EvalScript error."; - -/** - * @class Version - * Defines a version number with major, minor, micro, and special - * components. The major, minor and micro values are numeric; the special - * value can be any string. - * - * @param major The major version component, a positive integer up to nine digits long. - * @param minor The minor version component, a positive integer up to nine digits long. - * @param micro The micro version component, a positive integer up to nine digits long. - * @param special The special version component, an arbitrary string. - * - * @return A new \c Version object. - */ -function Version(major, minor, micro, special) -{ - this.major = major; - this.minor = minor; - this.micro = micro; - this.special = special; -} - -/** - * The maximum value allowed for a numeric version component. - * This reflects the maximum value allowed in PlugPlug and the manifest schema. - */ -Version.MAX_NUM = 999999999; - -/** - * @class VersionBound - * Defines a boundary for a version range, which associates a \c Version object - * with a flag for whether it is an inclusive or exclusive boundary. - * - * @param version The \c #Version object. - * @param inclusive True if this boundary is inclusive, false if it is exclusive. - * - * @return A new \c VersionBound object. - */ -function VersionBound(version, inclusive) -{ - this.version = version; - this.inclusive = inclusive; -} - -/** - * @class VersionRange - * Defines a range of versions using a lower boundary and optional upper boundary. - * - * @param lowerBound The \c #VersionBound object. - * @param upperBound The \c #VersionBound object, or null for a range with no upper boundary. - * - * @return A new \c VersionRange object. - */ -function VersionRange(lowerBound, upperBound) -{ - this.lowerBound = lowerBound; - this.upperBound = upperBound; -} - -/** - * @class Runtime - * Represents a runtime related to the CEP infrastructure. - * Extensions can declare dependencies on particular - * CEP runtime versions in the extension manifest. - * - * @param name The runtime name. - * @param version A \c #VersionRange object that defines a range of valid versions. - * - * @return A new \c Runtime object. - */ -function Runtime(name, versionRange) -{ - this.name = name; - this.versionRange = versionRange; -} - -/** -* @class Extension -* Encapsulates a CEP-based extension to an Adobe application. -* -* @param id The unique identifier of this extension. -* @param name The localizable display name of this extension. -* @param mainPath The path of the "index.html" file. -* @param basePath The base path of this extension. -* @param windowType The window type of the main window of this extension. - Valid values are defined by \c #CSXSWindowType. -* @param width The default width in pixels of the main window of this extension. -* @param height The default height in pixels of the main window of this extension. -* @param minWidth The minimum width in pixels of the main window of this extension. -* @param minHeight The minimum height in pixels of the main window of this extension. -* @param maxWidth The maximum width in pixels of the main window of this extension. -* @param maxHeight The maximum height in pixels of the main window of this extension. -* @param defaultExtensionDataXml The extension data contained in the default \c ExtensionDispatchInfo section of the extension manifest. -* @param specialExtensionDataXml The extension data contained in the application-specific \c ExtensionDispatchInfo section of the extension manifest. -* @param requiredRuntimeList An array of \c Runtime objects for runtimes required by this extension. -* @param isAutoVisible True if this extension is visible on loading. -* @param isPluginExtension True if this extension has been deployed in the Plugins folder of the host application. -* -* @return A new \c Extension object. -*/ -function Extension(id, name, mainPath, basePath, windowType, width, height, minWidth, minHeight, maxWidth, maxHeight, - defaultExtensionDataXml, specialExtensionDataXml, requiredRuntimeList, isAutoVisible, isPluginExtension) -{ - this.id = id; - this.name = name; - this.mainPath = mainPath; - this.basePath = basePath; - this.windowType = windowType; - this.width = width; - this.height = height; - this.minWidth = minWidth; - this.minHeight = minHeight; - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.defaultExtensionDataXml = defaultExtensionDataXml; - this.specialExtensionDataXml = specialExtensionDataXml; - this.requiredRuntimeList = requiredRuntimeList; - this.isAutoVisible = isAutoVisible; - this.isPluginExtension = isPluginExtension; -} - -/** - * @class CSEvent - * A standard JavaScript event, the base class for CEP events. - * - * @param type The name of the event type. - * @param scope The scope of event, can be "GLOBAL" or "APPLICATION". - * @param appId The unique identifier of the application that generated the event. - * @param extensionId The unique identifier of the extension that generated the event. - * - * @return A new \c CSEvent object - */ -function CSEvent(type, scope, appId, extensionId) -{ - this.type = type; - this.scope = scope; - this.appId = appId; - this.extensionId = extensionId; -} - -/** Event-specific data. */ -CSEvent.prototype.data = ""; - -/** - * @class SystemPath - * Stores operating-system-specific location constants for use in the - * \c #CSInterface.getSystemPath() method. - * @return A new \c SystemPath object. - */ -function SystemPath() -{ -} - -/** The path to user data. */ -SystemPath.USER_DATA = "userData"; - -/** The path to common files for Adobe applications. */ -SystemPath.COMMON_FILES = "commonFiles"; - -/** The path to the user's default document folder. */ -SystemPath.MY_DOCUMENTS = "myDocuments"; - -/** @deprecated. Use \c #SystemPath.Extension. */ -SystemPath.APPLICATION = "application"; - -/** The path to current extension. */ -SystemPath.EXTENSION = "extension"; - -/** The path to hosting application's executable. */ -SystemPath.HOST_APPLICATION = "hostApplication"; - -/** - * @class ColorType - * Stores color-type constants. - */ -function ColorType() -{ -} - -/** RGB color type. */ -ColorType.RGB = "rgb"; - -/** Gradient color type. */ -ColorType.GRADIENT = "gradient"; - -/** Null color type. */ -ColorType.NONE = "none"; - -/** - * @class RGBColor - * Stores an RGB color with red, green, blue, and alpha values. - * All values are in the range [0.0 to 255.0]. Invalid numeric values are - * converted to numbers within this range. - * - * @param red The red value, in the range [0.0 to 255.0]. - * @param green The green value, in the range [0.0 to 255.0]. - * @param blue The blue value, in the range [0.0 to 255.0]. - * @param alpha The alpha (transparency) value, in the range [0.0 to 255.0]. - * The default, 255.0, means that the color is fully opaque. - * - * @return A new RGBColor object. - */ -function RGBColor(red, green, blue, alpha) -{ - this.red = red; - this.green = green; - this.blue = blue; - this.alpha = alpha; -} - -/** - * @class Direction - * A point value in which the y component is 0 and the x component - * is positive or negative for a right or left direction, - * or the x component is 0 and the y component is positive or negative for - * an up or down direction. - * - * @param x The horizontal component of the point. - * @param y The vertical component of the point. - * - * @return A new \c Direction object. - */ -function Direction(x, y) -{ - this.x = x; - this.y = y; -} - -/** - * @class GradientStop - * Stores gradient stop information. - * - * @param offset The offset of the gradient stop, in the range [0.0 to 1.0]. - * @param rgbColor The color of the gradient at this point, an \c #RGBColor object. - * - * @return GradientStop object. - */ -function GradientStop(offset, rgbColor) -{ - this.offset = offset; - this.rgbColor = rgbColor; -} - -/** - * @class GradientColor - * Stores gradient color information. - * - * @param type The gradient type, must be "linear". - * @param direction A \c #Direction object for the direction of the gradient - (up, down, right, or left). - * @param numStops The number of stops in the gradient. - * @param gradientStopList An array of \c #GradientStop objects. - * - * @return A new \c GradientColor object. - */ -function GradientColor(type, direction, numStops, arrGradientStop) -{ - this.type = type; - this.direction = direction; - this.numStops = numStops; - this.arrGradientStop = arrGradientStop; -} - -/** - * @class UIColor - * Stores color information, including the type, anti-alias level, and specific color - * values in a color object of an appropriate type. - * - * @param type The color type, 1 for "rgb" and 2 for "gradient". - The supplied color object must correspond to this type. - * @param antialiasLevel The anti-alias level constant. - * @param color A \c #RGBColor or \c #GradientColor object containing specific color information. - * - * @return A new \c UIColor object. - */ -function UIColor(type, antialiasLevel, color) -{ - this.type = type; - this.antialiasLevel = antialiasLevel; - this.color = color; -} - -/** - * @class AppSkinInfo - * Stores window-skin properties, such as color and font. All color parameter values are \c #UIColor objects except that systemHighlightColor is \c #RGBColor object. - * - * @param baseFontFamily The base font family of the application. - * @param baseFontSize The base font size of the application. - * @param appBarBackgroundColor The application bar background color. - * @param panelBackgroundColor The background color of the extension panel. - * @param appBarBackgroundColorSRGB The application bar background color, as sRGB. - * @param panelBackgroundColorSRGB The background color of the extension panel, as sRGB. - * @param systemHighlightColor The highlight color of the extension panel, if provided by the host application. Otherwise, the operating-system highlight color. - * - * @return AppSkinInfo object. - */ -function AppSkinInfo(baseFontFamily, baseFontSize, appBarBackgroundColor, panelBackgroundColor, appBarBackgroundColorSRGB, panelBackgroundColorSRGB, systemHighlightColor) -{ - this.baseFontFamily = baseFontFamily; - this.baseFontSize = baseFontSize; - this.appBarBackgroundColor = appBarBackgroundColor; - this.panelBackgroundColor = panelBackgroundColor; - this.appBarBackgroundColorSRGB = appBarBackgroundColorSRGB; - this.panelBackgroundColorSRGB = panelBackgroundColorSRGB; - this.systemHighlightColor = systemHighlightColor; -} - -/** - * @class HostEnvironment - * Stores information about the environment in which the extension is loaded. - * - * @param appName The application's name. - * @param appVersion The application's version. - * @param appLocale The application's current license locale. - * @param appUILocale The application's current UI locale. - * @param appId The application's unique identifier. - * @param isAppOnline True if the application is currently online. - * @param appSkinInfo An \c #AppSkinInfo object containing the application's default color and font styles. - * - * @return A new \c HostEnvironment object. - */ -function HostEnvironment(appName, appVersion, appLocale, appUILocale, appId, isAppOnline, appSkinInfo) -{ - this.appName = appName; - this.appVersion = appVersion; - this.appLocale = appLocale; - this.appUILocale = appUILocale; - this.appId = appId; - this.isAppOnline = isAppOnline; - this.appSkinInfo = appSkinInfo; -} - -/** - * @class HostCapabilities - * Stores information about the host capabilities. - * - * @param EXTENDED_PANEL_MENU True if the application supports panel menu. - * @param EXTENDED_PANEL_ICONS True if the application supports panel icon. - * @param DELEGATE_APE_ENGINE True if the application supports delegated APE engine. - * @param SUPPORT_HTML_EXTENSIONS True if the application supports HTML extensions. - * @param DISABLE_FLASH_EXTENSIONS True if the application disables FLASH extensions. - * - * @return A new \c HostCapabilities object. - */ -function HostCapabilities(EXTENDED_PANEL_MENU, EXTENDED_PANEL_ICONS, DELEGATE_APE_ENGINE, SUPPORT_HTML_EXTENSIONS, DISABLE_FLASH_EXTENSIONS) -{ - this.EXTENDED_PANEL_MENU = EXTENDED_PANEL_MENU; - this.EXTENDED_PANEL_ICONS = EXTENDED_PANEL_ICONS; - this.DELEGATE_APE_ENGINE = DELEGATE_APE_ENGINE; - this.SUPPORT_HTML_EXTENSIONS = SUPPORT_HTML_EXTENSIONS; - this.DISABLE_FLASH_EXTENSIONS = DISABLE_FLASH_EXTENSIONS; // Since 5.0.0 -} - -/** - * @class ApiVersion - * Stores current api version. - * - * Since 4.2.0 - * - * @param major The major version - * @param minor The minor version. - * @param micro The micro version. - * - * @return ApiVersion object. - */ -function ApiVersion(major, minor, micro) -{ - this.major = major; - this.minor = minor; - this.micro = micro; -} - -/** - * @class MenuItemStatus - * Stores flyout menu item status - * - * Since 5.2.0 - * - * @param menuItemLabel The menu item label. - * @param enabled True if user wants to enable the menu item. - * @param checked True if user wants to check the menu item. - * - * @return MenuItemStatus object. - */ -function MenuItemStatus(menuItemLabel, enabled, checked) -{ - this.menuItemLabel = menuItemLabel; - this.enabled = enabled; - this.checked = checked; -} - -/** - * @class ContextMenuItemStatus - * Stores the status of the context menu item. - * - * Since 5.2.0 - * - * @param menuItemID The menu item id. - * @param enabled True if user wants to enable the menu item. - * @param checked True if user wants to check the menu item. - * - * @return MenuItemStatus object. - */ -function ContextMenuItemStatus(menuItemID, enabled, checked) -{ - this.menuItemID = menuItemID; - this.enabled = enabled; - this.checked = checked; -} -//------------------------------ CSInterface ---------------------------------- - -/** - * @class CSInterface - * This is the entry point to the CEP extensibility infrastructure. - * Instantiate this object and use it to: - *
    - *
  • Access information about the host application in which an extension is running
  • - *
  • Launch an extension
  • - *
  • Register interest in event notifications, and dispatch events
  • - *
- * - * @return A new \c CSInterface object - */ -function CSInterface() -{ -} - -/** - * User can add this event listener to handle native application theme color changes. - * Callback function gives extensions ability to fine-tune their theme color after the - * global theme color has been changed. - * The callback function should be like below: - * - * @example - * // event is a CSEvent object, but user can ignore it. - * function OnAppThemeColorChanged(event) - * { - * // Should get a latest HostEnvironment object from application. - * var skinInfo = JSON.parse(window.__adobe_cep__.getHostEnvironment()).appSkinInfo; - * // Gets the style information such as color info from the skinInfo, - * // and redraw all UI controls of your extension according to the style info. - * } - */ -CSInterface.THEME_COLOR_CHANGED_EVENT = "com.adobe.csxs.events.ThemeColorChanged"; - -/** The host environment data object. */ -CSInterface.prototype.hostEnvironment = window.__adobe_cep__ ? JSON.parse(window.__adobe_cep__.getHostEnvironment()) : null; - -/** Retrieves information about the host environment in which the - * extension is currently running. - * - * @return A \c #HostEnvironment object. - */ -CSInterface.prototype.getHostEnvironment = function() -{ - this.hostEnvironment = JSON.parse(window.__adobe_cep__.getHostEnvironment()); - return this.hostEnvironment; -}; - -/** Loads binary file created which is located at url asynchronously -* -*@param urlName url at which binary file is located. Local files should start with 'file://' -*@param callback Optional. A callback function that returns after binary is loaded - -*@example -* To create JS binary use command ./cep_compiler test.js test.bin -* To load JS binary asyncronously -* var CSLib = new CSInterface(); -* CSLib.loadBinAsync(url, function () { }); -*/ -CSInterface.prototype.loadBinAsync = function(urlName,callback) -{ - try - { - var xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; // make response as ArrayBuffer - xhr.open('GET', urlName, true); - xhr.onerror = function () - { - console.log("Unable to load snapshot from given URL"); - return false; - }; - xhr.send(); - xhr.onload = () => { - window.__adobe_cep__.loadSnapshot(xhr.response); - if (typeof callback === "function") - { - callback(); - } - else if(typeof callback !== "undefined") - { - console.log("Provided callback is not a function"); - } - } - } - catch(err) - { - console.log(err); - return false; - } - - return true; -}; - -/** Loads binary file created synchronously -* -*@param pathName the local path at which binary file is located - -*@example -* To create JS binary use command ./cep_compiler test.js test.bin -* To load JS binary syncronously -* var CSLib = new CSInterface(); -* CSLib.loadBinSync(path); -*/ -CSInterface.prototype.loadBinSync = function(pathName) -{ - try - { - var OSVersion = this.getOSInformation(); - if(pathName.startsWith("file://")) - { - if (OSVersion.indexOf("Windows") >= 0) - { - pathName = pathName.replace("file:///", ""); - } - else if (OSVersion.indexOf("Mac") >= 0) - { - pathName = pathName.replace("file://", ""); - } - window.__adobe_cep__.loadSnapshot(pathName); - return true; - } - } - catch(err) - { - console.log(err); - return false; - } - //control should not come here - return false; -}; - -/** Closes this extension. */ -CSInterface.prototype.closeExtension = function() -{ - window.__adobe_cep__.closeExtension(); -}; - -/** - * Retrieves a path for which a constant is defined in the system. - * - * @param pathType The path-type constant defined in \c #SystemPath , - * - * @return The platform-specific system path string. - */ -CSInterface.prototype.getSystemPath = function(pathType) -{ - var path = decodeURI(window.__adobe_cep__.getSystemPath(pathType)); - var OSVersion = this.getOSInformation(); - if (OSVersion.indexOf("Windows") >= 0) - { - path = path.replace("file:///", ""); - } - else if (OSVersion.indexOf("Mac") >= 0) - { - path = path.replace("file://", ""); - } - return path; -}; - -/** - * Evaluates a JavaScript script, which can use the JavaScript DOM - * of the host application. - * - * @param script The JavaScript script. - * @param callback Optional. A callback function that receives the result of execution. - * If execution fails, the callback function receives the error message \c EvalScript_ErrMessage. - */ -CSInterface.prototype.evalScript = function(script, callback) -{ - if(callback === null || callback === undefined) - { - callback = function(result){}; - } - window.__adobe_cep__.evalScript(script, callback); -}; - -/** - * Retrieves the unique identifier of the application. - * in which the extension is currently running. - * - * @return The unique ID string. - */ -CSInterface.prototype.getApplicationID = function() -{ - var appId = this.hostEnvironment.appId; - return appId; -}; - -/** - * Retrieves host capability information for the application - * in which the extension is currently running. - * - * @return A \c #HostCapabilities object. - */ -CSInterface.prototype.getHostCapabilities = function() -{ - var hostCapabilities = JSON.parse(window.__adobe_cep__.getHostCapabilities() ); - return hostCapabilities; -}; - -/** - * Triggers a CEP event programmatically. Yoy can use it to dispatch - * an event of a predefined type, or of a type you have defined. - * - * @param event A \c CSEvent object. - */ -CSInterface.prototype.dispatchEvent = function(event) -{ - if (typeof event.data == "object") - { - event.data = JSON.stringify(event.data); - } - - window.__adobe_cep__.dispatchEvent(event); -}; - -/** - * Registers an interest in a CEP event of a particular type, and - * assigns an event handler. - * The event infrastructure notifies your extension when events of this type occur, - * passing the event object to the registered handler function. - * - * @param type The name of the event type of interest. - * @param listener The JavaScript handler function or method. - * @param obj Optional, the object containing the handler method, if any. - * Default is null. - */ -CSInterface.prototype.addEventListener = function(type, listener, obj) -{ - window.__adobe_cep__.addEventListener(type, listener, obj); -}; - -/** - * Removes a registered event listener. - * - * @param type The name of the event type of interest. - * @param listener The JavaScript handler function or method that was registered. - * @param obj Optional, the object containing the handler method, if any. - * Default is null. - */ -CSInterface.prototype.removeEventListener = function(type, listener, obj) -{ - window.__adobe_cep__.removeEventListener(type, listener, obj); -}; - -/** - * Loads and launches another extension, or activates the extension if it is already loaded. - * - * @param extensionId The extension's unique identifier. - * @param startupParams Not currently used, pass "". - * - * @example - * To launch the extension "help" with ID "HLP" from this extension, call: - * requestOpenExtension("HLP", ""); - * - */ -CSInterface.prototype.requestOpenExtension = function(extensionId, params) -{ - window.__adobe_cep__.requestOpenExtension(extensionId, params); -}; - -/** - * Retrieves the list of extensions currently loaded in the current host application. - * The extension list is initialized once, and remains the same during the lifetime - * of the CEP session. - * - * @param extensionIds Optional, an array of unique identifiers for extensions of interest. - * If omitted, retrieves data for all extensions. - * - * @return Zero or more \c #Extension objects. - */ -CSInterface.prototype.getExtensions = function(extensionIds) -{ - var extensionIdsStr = JSON.stringify(extensionIds); - var extensionsStr = window.__adobe_cep__.getExtensions(extensionIdsStr); - - var extensions = JSON.parse(extensionsStr); - return extensions; -}; - -/** - * Retrieves network-related preferences. - * - * @return A JavaScript object containing network preferences. - */ -CSInterface.prototype.getNetworkPreferences = function() -{ - var result = window.__adobe_cep__.getNetworkPreferences(); - var networkPre = JSON.parse(result); - - return networkPre; -}; - -/** - * Initializes the resource bundle for this extension with property values - * for the current application and locale. - * To support multiple locales, you must define a property file for each locale, - * containing keyed display-string values for that locale. - * See localization documentation for Extension Builder and related products. - * - * Keys can be in the - * form key.value="localized string", for use in HTML text elements. - * For example, in this input element, the localized \c key.value string is displayed - * instead of the empty \c value string: - * - * - * - * @return An object containing the resource bundle information. - */ -CSInterface.prototype.initResourceBundle = function() -{ - var resourceBundle = JSON.parse(window.__adobe_cep__.initResourceBundle()); - var resElms = document.querySelectorAll('[data-locale]'); - for (var n = 0; n < resElms.length; n++) - { - var resEl = resElms[n]; - // Get the resource key from the element. - var resKey = resEl.getAttribute('data-locale'); - if (resKey) - { - // Get all the resources that start with the key. - for (var key in resourceBundle) - { - if (key.indexOf(resKey) === 0) - { - var resValue = resourceBundle[key]; - if (key.length == resKey.length) - { - resEl.innerHTML = resValue; - } - else if ('.' == key.charAt(resKey.length)) - { - var attrKey = key.substring(resKey.length + 1); - resEl[attrKey] = resValue; - } - } - } - } - } - return resourceBundle; -}; - -/** - * Writes installation information to a file. - * - * @return The file path. - */ -CSInterface.prototype.dumpInstallationInfo = function() -{ - return window.__adobe_cep__.dumpInstallationInfo(); -}; - -/** - * Retrieves version information for the current Operating System, - * See http://www.useragentstring.com/pages/Chrome/ for Chrome \c navigator.userAgent values. - * - * @return A string containing the OS version, or "unknown Operation System". - * If user customizes the User Agent by setting CEF command parameter "--user-agent", only - * "Mac OS X" or "Windows" will be returned. - */ -CSInterface.prototype.getOSInformation = function() -{ - var userAgent = navigator.userAgent; - - if ((navigator.platform == "Win32") || (navigator.platform == "Windows")) - { - var winVersion = "Windows"; - var winBit = ""; - if (userAgent.indexOf("Windows") > -1) - { - if (userAgent.indexOf("Windows NT 5.0") > -1) - { - winVersion = "Windows 2000"; - } - else if (userAgent.indexOf("Windows NT 5.1") > -1) - { - winVersion = "Windows XP"; - } - else if (userAgent.indexOf("Windows NT 5.2") > -1) - { - winVersion = "Windows Server 2003"; - } - else if (userAgent.indexOf("Windows NT 6.0") > -1) - { - winVersion = "Windows Vista"; - } - else if (userAgent.indexOf("Windows NT 6.1") > -1) - { - winVersion = "Windows 7"; - } - else if (userAgent.indexOf("Windows NT 6.2") > -1) - { - winVersion = "Windows 8"; - } - else if (userAgent.indexOf("Windows NT 6.3") > -1) - { - winVersion = "Windows 8.1"; - } - else if (userAgent.indexOf("Windows NT 10") > -1) - { - winVersion = "Windows 10"; - } - - if (userAgent.indexOf("WOW64") > -1 || userAgent.indexOf("Win64") > -1) - { - winBit = " 64-bit"; - } - else - { - winBit = " 32-bit"; - } - } - - return winVersion + winBit; - } - else if ((navigator.platform == "MacIntel") || (navigator.platform == "Macintosh")) - { - var result = "Mac OS X"; - - if (userAgent.indexOf("Mac OS X") > -1) - { - result = userAgent.substring(userAgent.indexOf("Mac OS X"), userAgent.indexOf(")")); - result = result.replace(/_/g, "."); - } - - return result; - } - - return "Unknown Operation System"; -}; - -/** - * Opens a page in the default system browser. - * - * Since 4.2.0 - * - * @param url The URL of the page/file to open, or the email address. - * Must use HTTP/HTTPS/file/mailto protocol. For example: - * "http://www.adobe.com" - * "https://github.com" - * "file:///C:/log.txt" - * "mailto:test@adobe.com" - * - * @return One of these error codes:\n - *
    \n - *
  • NO_ERROR - 0
  • \n - *
  • ERR_UNKNOWN - 1
  • \n - *
  • ERR_INVALID_PARAMS - 2
  • \n - *
  • ERR_INVALID_URL - 201
  • \n - *
\n - */ -CSInterface.prototype.openURLInDefaultBrowser = function(url) -{ - return cep.util.openURLInDefaultBrowser(url); -}; - -/** - * Retrieves extension ID. - * - * Since 4.2.0 - * - * @return extension ID. - */ -CSInterface.prototype.getExtensionID = function() -{ - return window.__adobe_cep__.getExtensionId(); -}; - -/** - * Retrieves the scale factor of screen. - * On Windows platform, the value of scale factor might be different from operating system's scale factor, - * since host application may use its self-defined scale factor. - * - * Since 4.2.0 - * - * @return One of the following float number. - *
    \n - *
  • -1.0 when error occurs
  • \n - *
  • 1.0 means normal screen
  • \n - *
  • >1.0 means HiDPI screen
  • \n - *
\n - */ -CSInterface.prototype.getScaleFactor = function() -{ - return window.__adobe_cep__.getScaleFactor(); -}; - -/** - * Retrieves the scale factor of Monitor. - * - * Since 8.5.0 - * - * @return value >= 1.0f - * only available for windows machine - */ - if(navigator.appVersion.toLowerCase().indexOf("windows") >= 0) { - CSInterface.prototype.getMonitorScaleFactor = function() - { - return window.__adobe_cep__.getMonitorScaleFactor(); - }; -} - -/** - * Set a handler to detect any changes of scale factor. This only works on Mac. - * - * Since 4.2.0 - * - * @param handler The function to be called when scale factor is changed. - * - */ -CSInterface.prototype.setScaleFactorChangedHandler = function(handler) -{ - window.__adobe_cep__.setScaleFactorChangedHandler(handler); -}; - -/** - * Retrieves current API version. - * - * Since 4.2.0 - * - * @return ApiVersion object. - * - */ -CSInterface.prototype.getCurrentApiVersion = function() -{ - var apiVersion = JSON.parse(window.__adobe_cep__.getCurrentApiVersion()); - return apiVersion; -}; - -/** - * Set panel flyout menu by an XML. - * - * Since 5.2.0 - * - * Register a callback function for "com.adobe.csxs.events.flyoutMenuClicked" to get notified when a - * menu item is clicked. - * The "data" attribute of event is an object which contains "menuId" and "menuName" attributes. - * - * Register callback functions for "com.adobe.csxs.events.flyoutMenuOpened" and "com.adobe.csxs.events.flyoutMenuClosed" - * respectively to get notified when flyout menu is opened or closed. - * - * @param menu A XML string which describes menu structure. - * An example menu XML: - * - * - * - * - * - * - * - * - * - * - * - * - */ -CSInterface.prototype.setPanelFlyoutMenu = function(menu) -{ - if ("string" != typeof menu) - { - return; - } - - window.__adobe_cep__.invokeSync("setPanelFlyoutMenu", menu); -}; - -/** - * Updates a menu item in the extension window's flyout menu, by setting the enabled - * and selection status. - * - * Since 5.2.0 - * - * @param menuItemLabel The menu item label. - * @param enabled True to enable the item, false to disable it (gray it out). - * @param checked True to select the item, false to deselect it. - * - * @return false when the host application does not support this functionality (HostCapabilities.EXTENDED_PANEL_MENU is false). - * Fails silently if menu label is invalid. - * - * @see HostCapabilities.EXTENDED_PANEL_MENU - */ -CSInterface.prototype.updatePanelMenuItem = function(menuItemLabel, enabled, checked) -{ - var ret = false; - if (this.getHostCapabilities().EXTENDED_PANEL_MENU) - { - var itemStatus = new MenuItemStatus(menuItemLabel, enabled, checked); - ret = window.__adobe_cep__.invokeSync("updatePanelMenuItem", JSON.stringify(itemStatus)); - } - return ret; -}; - - -/** - * Set context menu by XML string. - * - * Since 5.2.0 - * - * There are a number of conventions used to communicate what type of menu item to create and how it should be handled. - * - an item without menu ID or menu name is disabled and is not shown. - * - if the item name is "---" (three hyphens) then it is treated as a separator. The menu ID in this case will always be NULL. - * - Checkable attribute takes precedence over Checked attribute. - * - a PNG icon. For optimal display results please supply a 16 x 16px icon as larger dimensions will increase the size of the menu item. - The Chrome extension contextMenus API was taken as a reference. - https://developer.chrome.com/extensions/contextMenus - * - the items with icons and checkable items cannot coexist on the same menu level. The former take precedences over the latter. - * - * @param menu A XML string which describes menu structure. - * @param callback The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item. - * - * @description An example menu XML: - * - * - * - * - * - * - * - * - * - * - * - */ -CSInterface.prototype.setContextMenu = function(menu, callback) -{ - if ("string" != typeof menu) - { - return; - } - - window.__adobe_cep__.invokeAsync("setContextMenu", menu, callback); -}; - -/** - * Set context menu by JSON string. - * - * Since 6.0.0 - * - * There are a number of conventions used to communicate what type of menu item to create and how it should be handled. - * - an item without menu ID or menu name is disabled and is not shown. - * - if the item label is "---" (three hyphens) then it is treated as a separator. The menu ID in this case will always be NULL. - * - Checkable attribute takes precedence over Checked attribute. - * - a PNG icon. For optimal display results please supply a 16 x 16px icon as larger dimensions will increase the size of the menu item. - The Chrome extension contextMenus API was taken as a reference. - * - the items with icons and checkable items cannot coexist on the same menu level. The former take precedences over the latter. - https://developer.chrome.com/extensions/contextMenus - * - * @param menu A JSON string which describes menu structure. - * @param callback The callback function which is called when a menu item is clicked. The only parameter is the returned ID of clicked menu item. - * - * @description An example menu JSON: - * - * { - * "menu": [ - * { - * "id": "menuItemId1", - * "label": "testExample1", - * "enabled": true, - * "checkable": true, - * "checked": false, - * "icon": "./image/small_16X16.png" - * }, - * { - * "id": "menuItemId2", - * "label": "testExample2", - * "menu": [ - * { - * "id": "menuItemId2-1", - * "label": "testExample2-1", - * "menu": [ - * { - * "id": "menuItemId2-1-1", - * "label": "testExample2-1-1", - * "enabled": false, - * "checkable": true, - * "checked": true - * } - * ] - * }, - * { - * "id": "menuItemId2-2", - * "label": "testExample2-2", - * "enabled": true, - * "checkable": true, - * "checked": true - * } - * ] - * }, - * { - * "label": "---" - * }, - * { - * "id": "menuItemId3", - * "label": "testExample3", - * "enabled": false, - * "checkable": true, - * "checked": false - * } - * ] - * } - * - */ -CSInterface.prototype.setContextMenuByJSON = function(menu, callback) -{ - if ("string" != typeof menu) - { - return; - } - - window.__adobe_cep__.invokeAsync("setContextMenuByJSON", menu, callback); -}; - -/** - * Updates a context menu item by setting the enabled and selection status. - * - * Since 5.2.0 - * - * @param menuItemID The menu item ID. - * @param enabled True to enable the item, false to disable it (gray it out). - * @param checked True to select the item, false to deselect it. - */ -CSInterface.prototype.updateContextMenuItem = function(menuItemID, enabled, checked) -{ - var itemStatus = new ContextMenuItemStatus(menuItemID, enabled, checked); - ret = window.__adobe_cep__.invokeSync("updateContextMenuItem", JSON.stringify(itemStatus)); -}; - -/** - * Get the visibility status of an extension window. - * - * Since 6.0.0 - * - * @return true if the extension window is visible; false if the extension window is hidden. - */ -CSInterface.prototype.isWindowVisible = function() -{ - return window.__adobe_cep__.invokeSync("isWindowVisible", ""); -}; - -/** - * Resize extension's content to the specified dimensions. - * 1. Works with modal and modeless extensions in all Adobe products. - * 2. Extension's manifest min/max size constraints apply and take precedence. - * 3. For panel extensions - * 3.1 This works in all Adobe products except: - * * Premiere Pro - * * Prelude - * * After Effects - * 3.2 When the panel is in certain states (especially when being docked), - * it will not change to the desired dimensions even when the - * specified size satisfies min/max constraints. - * - * Since 6.0.0 - * - * @param width The new width - * @param height The new height - */ -CSInterface.prototype.resizeContent = function(width, height) -{ - window.__adobe_cep__.resizeContent(width, height); -}; - -/** - * Register the invalid certificate callback for an extension. - * This callback will be triggered when the extension tries to access the web site that contains the invalid certificate on the main frame. - * But if the extension does not call this function and tries to access the web site containing the invalid certificate, a default error page will be shown. - * - * Since 6.1.0 - * - * @param callback the callback function - */ -CSInterface.prototype.registerInvalidCertificateCallback = function(callback) -{ - return window.__adobe_cep__.registerInvalidCertificateCallback(callback); -}; - -/** - * Register an interest in some key events to prevent them from being sent to the host application. - * - * This function works with modeless extensions and panel extensions. - * Generally all the key events will be sent to the host application for these two extensions if the current focused element - * is not text input or dropdown, - * If you want to intercept some key events and want them to be handled in the extension, please call this function - * in advance to prevent them being sent to the host application. - * - * Since 6.1.0 - * - * @param keyEventsInterest A JSON string describing those key events you are interested in. A null object or - an empty string will lead to removing the interest - * - * This JSON string should be an array, each object has following keys: - * - * keyCode: [Required] represents an OS system dependent virtual key code identifying - * the unmodified value of the pressed key. - * ctrlKey: [optional] a Boolean that indicates if the control key was pressed (true) or not (false) when the event occurred. - * altKey: [optional] a Boolean that indicates if the alt key was pressed (true) or not (false) when the event occurred. - * shiftKey: [optional] a Boolean that indicates if the shift key was pressed (true) or not (false) when the event occurred. - * metaKey: [optional] (Mac Only) a Boolean that indicates if the Meta key was pressed (true) or not (false) when the event occurred. - * On Macintosh keyboards, this is the command key. To detect Windows key on Windows, please use keyCode instead. - * An example JSON string: - * - * [ - * { - * "keyCode": 48 - * }, - * { - * "keyCode": 123, - * "ctrlKey": true - * }, - * { - * "keyCode": 123, - * "ctrlKey": true, - * "metaKey": true - * } - * ] - * - */ -CSInterface.prototype.registerKeyEventsInterest = function(keyEventsInterest) -{ - return window.__adobe_cep__.registerKeyEventsInterest(keyEventsInterest); -}; - -/** - * Set the title of the extension window. - * This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver. - * - * Since 6.1.0 - * - * @param title The window title. - */ -CSInterface.prototype.setWindowTitle = function(title) -{ - window.__adobe_cep__.invokeSync("setWindowTitle", title); -}; - -/** - * Get the title of the extension window. - * This function works with modal and modeless extensions in all Adobe products, and panel extensions in Photoshop, InDesign, InCopy, Illustrator, Flash Pro and Dreamweaver. - * - * Since 6.1.0 - * - * @return The window title. - */ -CSInterface.prototype.getWindowTitle = function() -{ - return window.__adobe_cep__.invokeSync("getWindowTitle", ""); -}; diff --git a/pype/hosts/premiere/extensions/com.pype.rename/lib/Vulcan.js b/pype/hosts/premiere/extensions/com.pype.rename/lib/Vulcan.js deleted file mode 100644 index 10db662fd3..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/lib/Vulcan.js +++ /dev/null @@ -1,459 +0,0 @@ -/************************************************************************************************** -* -* ADOBE SYSTEMS INCORPORATED -* Copyright 2013 Adobe Systems Incorporated -* All Rights Reserved. -* -* NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the -* terms of the Adobe license agreement accompanying it. If you have received this file from a -* source other than Adobe, then your use, modification, or distribution of it requires the prior -* written permission of Adobe. -* -**************************************************************************************************/ - -/** Vulcan - v9.2.0 */ - -/** - * @class Vulcan - * - * The singleton instance, VulcanInterface, provides an interface - * to the Vulcan. Allows you to launch CC applications - * and discover information about them. - */ -function Vulcan() -{ -} - -/** - * Gets all available application specifiers on the local machine. - * - * @return The array of all available application specifiers. - */ -Vulcan.prototype.getTargetSpecifiers = function() -{ - var params = {}; - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetTargetSpecifiers", JSON.stringify(params))); -}; - -/** - * Launches a CC application on the local machine, if it is not already running. - * - * @param targetSpecifier The application specifier; for example "indesign". - * - * Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version - * and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you - * installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may - * receive wrong result. - * The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator". - * - * In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier. - * @param focus True to launch in foreground, or false to launch in the background. - * @param cmdLine Optional, command-line parameters to supply to the launch command. - * @return True if the app can be launched, false otherwise. - */ -Vulcan.prototype.launchApp = function(targetSpecifier, focus, cmdLine) -{ - if(!requiredParamsValid(targetSpecifier)) - { - return false; - } - - var params = {}; - params.targetSpecifier = targetSpecifier; - params.focus = focus ? "true" : "false"; - params.cmdLine = requiredParamsValid(cmdLine) ? cmdLine : ""; - - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanLaunchApp", JSON.stringify(params))).result; -}; - -/** - * Checks whether a CC application is running on the local machine. - * - * @param targetSpecifier The application specifier; for example "indesign". - * - * Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version - * and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you - * installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may - * receive wrong result. - * The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator". - * - * In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier. - * @return True if the app is running, false otherwise. - */ -Vulcan.prototype.isAppRunning = function(targetSpecifier) -{ - if(!requiredParamsValid(targetSpecifier)) - { - return false; - } - - var params = {}; - params.targetSpecifier = targetSpecifier; - - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanIsAppRunning", JSON.stringify(params))).result; -}; - -/** - * Checks whether a CC application is installed on the local machine. - * - * @param targetSpecifier The application specifier; for example "indesign". - * - * Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version - * and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you - * installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may - * receive wrong result. - * The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator". - * - * In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier. - * @return True if the app is installed, false otherwise. - */ -Vulcan.prototype.isAppInstalled = function(targetSpecifier) -{ - if(!requiredParamsValid(targetSpecifier)) - { - return false; - } - - var params = {}; - params.targetSpecifier = targetSpecifier; - - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanIsAppInstalled", JSON.stringify(params))).result; -}; - -/** - * Retrieves the local install path of a CC application. - * - * @param targetSpecifier The application specifier; for example "indesign". - * - * Note: In Windows 7 64-bit or Windows 8 64-bit system, some target applications (like Photoshop and Illustrator) have both 32-bit version - * and 64-bit version. Therefore, we need to specify the version by this parameter with "photoshop-70.032" or "photoshop-70.064". If you - * installed Photoshop 32-bit and 64-bit on one Windows 64-bit system and invoke this interface with parameter "photoshop-70.032", you may - * receive wrong result. - * The specifiers for Illustrator is "illustrator-17.032", "illustrator-17.064", "illustrator-17" and "illustrator". - * - * In other platforms there is no such issue, so we can use "photoshop" or "photoshop-70" as specifier. - * @return The path string if the application is found, "" otherwise. - */ -Vulcan.prototype.getAppPath = function(targetSpecifier) -{ - if(!requiredParamsValid(targetSpecifier)) - { - return ""; - } - - var params = {}; - params.targetSpecifier = targetSpecifier; - - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetAppPath", JSON.stringify(params))).result; -}; - -/** - * Registers a message listener callback function for a Vulcan message. - * - * @param type The message type. - * @param callback The callback function that handles the message. - * Takes one argument, the message object. - * @param obj Optional, the object containing the callback method, if any. - * Default is null. - */ -Vulcan.prototype.addMessageListener = function(type, callback, obj) -{ - if(!requiredParamsValid(type, callback) || !strStartsWith(type, VulcanMessage.TYPE_PREFIX)) - { - return; - } - - var params = {}; - params.type = type; - - window.__adobe_cep__.invokeAsync("vulcanAddMessageListener", JSON.stringify(params), callback, obj); -}; - -/** - * Removes a registered message listener callback function for a Vulcan message. - * - * @param type The message type. - * @param callback The callback function that was registered. - * Takes one argument, the message object. - * @param obj Optional, the object containing the callback method, if any. - * Default is null. - */ -Vulcan.prototype.removeMessageListener = function(type, callback, obj) -{ - if(!requiredParamsValid(type, callback) || !strStartsWith(type, VulcanMessage.TYPE_PREFIX)) - { - return; - } - - var params = {}; - params.type = type; - - window.__adobe_cep__.invokeAsync("vulcanRemoveMessageListener", JSON.stringify(params), callback, obj); -}; - -/** - * Dispatches a Vulcan message. - * - * @param vulcanMessage The message object. - */ -Vulcan.prototype.dispatchMessage = function(vulcanMessage) -{ - if(!requiredParamsValid(vulcanMessage) || !strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX)) - { - return; - } - - var params = {}; - var message = new VulcanMessage(vulcanMessage.type); - message.initialize(vulcanMessage); - params.vulcanMessage = message; - - window.__adobe_cep__.invokeSync("vulcanDispatchMessage", JSON.stringify(params)); -}; - -/** - * Retrieves the message payload of a Vulcan message for the registered message listener callback function. - * - * @param vulcanMessage The message object. - * @return A string containing the message payload. - */ -Vulcan.prototype.getPayload = function(vulcanMessage) -{ - if(!requiredParamsValid(vulcanMessage) || !strStartsWith(vulcanMessage.type, VulcanMessage.TYPE_PREFIX)) - { - return null; - } - - var message = new VulcanMessage(vulcanMessage.type); - message.initialize(vulcanMessage); - return message.getPayload(); -}; - -/** - * Gets all available endpoints of the running Vulcan-enabled applications. - * - * Since 7.0.0 - * - * @return The array of all available endpoints. - * An example endpoint string: - * - * PHXS - * 16.1.0 - * - */ -Vulcan.prototype.getEndPoints = function() -{ - var params = {}; - return JSON.parse(window.__adobe_cep__.invokeSync("vulcanGetEndPoints", JSON.stringify(params))); -}; - -/** - * Gets the endpoint for itself. - * - * Since 7.0.0 - * - * @return The endpoint string for itself. - */ -Vulcan.prototype.getSelfEndPoint = function() -{ - var params = {}; - return window.__adobe_cep__.invokeSync("vulcanGetSelfEndPoint", JSON.stringify(params)); -}; - -/** Singleton instance of Vulcan **/ -var VulcanInterface = new Vulcan(); - -//--------------------------------- Vulcan Message ------------------------------ - -/** - * @class VulcanMessage - * Message type for sending messages between host applications. - * A message of this type can be sent to the designated destination - * when appId and appVersion are provided and valid. Otherwise, - * the message is broadcast to all running Vulcan-enabled applications. - * - * To send a message between extensions running within one - * application, use the CSEvent type in CSInterface.js. - * - * @param type The message type. - * @param appId The peer appId. - * @param appVersion The peer appVersion. - * - */ -function VulcanMessage(type, appId, appVersion) -{ - this.type = type; - this.scope = VulcanMessage.SCOPE_SUITE; - this.appId = requiredParamsValid(appId) ? appId : VulcanMessage.DEFAULT_APP_ID; - this.appVersion = requiredParamsValid(appVersion) ? appVersion : VulcanMessage.DEFAULT_APP_VERSION; - this.data = VulcanMessage.DEFAULT_DATA; -} - -VulcanMessage.TYPE_PREFIX = "vulcan.SuiteMessage."; -VulcanMessage.SCOPE_SUITE = "GLOBAL"; -VulcanMessage.DEFAULT_APP_ID = "UNKNOWN"; -VulcanMessage.DEFAULT_APP_VERSION = "UNKNOWN"; -VulcanMessage.DEFAULT_DATA = ""; -VulcanMessage.dataTemplate = "{0}"; -VulcanMessage.payloadTemplate = "{0}"; - -/** - * Initializes this message instance. - * - * @param message A \c message instance to use for initialization. - */ -VulcanMessage.prototype.initialize = function(message) -{ - this.type = message.type; - this.scope = message.scope; - this.appId = message.appId; - this.appVersion = message.appVersion; - this.data = message.data; -}; - -/** - * Retrieves the message data. - * - * @return A data string in XML format. - */ -VulcanMessage.prototype.xmlData = function() -{ - if(this.data === undefined) - { - var str = ""; - str = String.format(VulcanMessage.payloadTemplate, str); - this.data = String.format(VulcanMessage.dataTemplate, str); - } - return this.data; -}; - -/** - * Sets the message payload of this message. - * - * @param payload A string containing the message payload. - */ -VulcanMessage.prototype.setPayload = function(payload) -{ - var str = cep.encoding.convertion.utf8_to_b64(payload); - str = String.format(VulcanMessage.payloadTemplate, str); - this.data = String.format(VulcanMessage.dataTemplate, str); -}; - -/** - * Retrieves the message payload of this message. - * - * @return A string containing the message payload. - */ -VulcanMessage.prototype.getPayload = function() -{ - var str = GetValueByKey(this.data, "payload"); - if(str !== null) - { - return cep.encoding.convertion.b64_to_utf8(str); - } - return null; -}; - -/** - * Converts the properties of this instance to a string. - * - * @return The string version of this instance. - */ -VulcanMessage.prototype.toString = function() -{ - var str = "type=" + this.type; - str += ", scope=" + this.scope; - str += ", appId=" + this.appId; - str += ", appVersion=" + this.appVersion; - str += ", data=" + this.xmlData(); - return str; -}; - -//--------------------------------------- Util -------------------------------- - -/** - * Formats a string based on a template. - * - * @param src The format template. - * - * @return The formatted string - */ -String.format = function(src) -{ - if (arguments.length === 0) - { - return null; - } - - var args = Array.prototype.slice.call(arguments, 1); - return src.replace(/\{(\d+)\}/g, function(m, i){ - return args[i]; - }); -}; - -/** - * Retrieves the content of an XML element. - * - * @param xmlStr The XML string. - * @param key The name of the tag. - * - * @return The content of the tag, or the empty string - * if such tag is not found or the tag has no content. - */ -function GetValueByKey(xmlStr, key) -{ - if(window.DOMParser) - { - var parser = new window.DOMParser(); - try - { - var xmlDoc = parser.parseFromString(xmlStr, "text/xml"); - var node = xmlDoc.getElementsByTagName(key)[0]; - if(node && node.childNodes[0]) - { - return node.childNodes[0].nodeValue; - } - } - catch(e) - { - //log the error - } - } - return ""; -} - -/** - * Reports whether required parameters are valid. - * - * @return True if all required parameters are valid, - * false if any of the required parameters are invalid. - */ -function requiredParamsValid() -{ - for(var i = 0; i < arguments.length; i++) - { - var argument = arguments[i]; - if(argument === undefined || argument === null) - { - return false; - } - } - return true; -} - -/** - * Reports whether a string has a given prefix. - * - * @param str The target string. - * @param prefix The specific prefix string. - * - * @return True if the string has the prefix, false if not. - */ -function strStartsWith(str, prefix) -{ - if(typeof str != "string") - { - return false; - } - return str.indexOf(prefix) === 0; -} diff --git a/pype/hosts/premiere/extensions/com.pype.rename/lib/bootstrap.min.js b/pype/hosts/premiere/extensions/com.pype.rename/lib/bootstrap.min.js deleted file mode 100644 index 9df6b6c2ce..0000000000 --- a/pype/hosts/premiere/extensions/com.pype.rename/lib/bootstrap.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.2.1 (https://getbootstrap.com/) - * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("popper.js"),require("jquery")):"function"==typeof define&&define.amd?define(["exports","popper.js","jquery"],e):e(t.bootstrap={},t.Popper,t.jQuery)}(this,function(t,u,g){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Q.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},De="show",we="out",Ae={HIDE:"hide"+Ee,HIDDEN:"hidden"+Ee,SHOW:"show"+Ee,SHOWN:"shown"+Ee,INSERTED:"inserted"+Ee,CLICK:"click"+Ee,FOCUSIN:"focusin"+Ee,FOCUSOUT:"focusout"+Ee,MOUSEENTER:"mouseenter"+Ee,MOUSELEAVE:"mouseleave"+Ee},Ne="fade",Oe="show",ke=".tooltip-inner",Pe=".arrow",Le="hover",je="focus",He="click",Re="manual",Ue=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Oe))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(Ne);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,{placement:a,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:Pe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),g(o).addClass(Oe),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===we&&e._leave(null,e)};if(g(this.tip).hasClass(Ne)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=g.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==De&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),g(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(g(this.element).trigger(i),!i.isDefaultPrevented()){if(g(n).removeClass(Oe),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[He]=!1,this._activeTrigger[je]=!1,this._activeTrigger[Le]=!1,g(this.tip).hasClass(Ne)){var r=_.getTransitionDurationFromElement(n);g(n).one(_.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Ce+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(ke)),this.getTitle()),g(t).removeClass(Ne+" "+Oe)},t.setElementContent=function(t,e){var n=this.config.html;"object"==typeof e&&(e.nodeType||e.jquery)?n?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text()):t[n?"html":"text"](e)},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return be[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Re){var e=t===Le?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Le?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),g(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?je:Le]=!0),g(e.getTipElement()).hasClass(Oe)||e._hoverState===De?e._hoverState=De:(clearTimeout(e._timeout),e._hoverState=De,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===De&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?je:Le]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=we,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===we&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){return"number"==typeof(t=l({},this.constructor.Default,g(this.element).data(),"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(pe,t,this.constructor.DefaultType),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Te);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(Ne),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(ve),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(ve,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.2.1"}},{key:"Default",get:function(){return Ie}},{key:"NAME",get:function(){return pe}},{key:"DATA_KEY",get:function(){return ve}},{key:"Event",get:function(){return Ae}},{key:"EVENT_KEY",get:function(){return Ee}},{key:"DefaultType",get:function(){return Se}}]),i}();g.fn[pe]=Ue._jQueryInterface,g.fn[pe].Constructor=Ue,g.fn[pe].noConflict=function(){return g.fn[pe]=ye,Ue._jQueryInterface};var We="popover",xe="bs.popover",Fe="."+xe,qe=g.fn[We],Me="bs-popover",Ke=new RegExp("(^|\\s)"+Me+"\\S+","g"),Qe=l({},Ue.Default,{placement:"right",trigger:"click",content:"",template:''}),Be=l({},Ue.DefaultType,{content:"(string|element|function)"}),Ve="fade",Ye="show",Xe=".popover-header",ze=".popover-body",Ge={HIDE:"hide"+Fe,HIDDEN:"hidden"+Fe,SHOW:"show"+Fe,SHOWN:"shown"+Fe,INSERTED:"inserted"+Fe,CLICK:"click"+Fe,FOCUSIN:"focusin"+Fe,FOCUSOUT:"focusout"+Fe,MOUSEENTER:"mouseenter"+Fe,MOUSELEAVE:"mouseleave"+Fe},Je=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Me+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},o.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(Xe),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ze),e),t.removeClass(Ve+" "+Ye)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ke);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t {\n called = true\n })\n\n setTimeout(() => {\n if (!called) {\n Util.triggerTransitionEnd(this)\n }\n }, duration)\n\n return this\n}\n\nfunction setTransitionEndSupport() {\n $.fn.emulateTransitionEnd = transitionEndEmulator\n $.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent()\n}\n\n/**\n * --------------------------------------------------------------------------\n * Public Util Api\n * --------------------------------------------------------------------------\n */\n\nconst Util = {\n\n TRANSITION_END: 'bsTransitionEnd',\n\n getUID(prefix) {\n do {\n // eslint-disable-next-line no-bitwise\n prefix += ~~(Math.random() * MAX_UID) // \"~~\" acts like a faster Math.floor() here\n } while (document.getElementById(prefix))\n return prefix\n },\n\n getSelectorFromElement(element) {\n let selector = element.getAttribute('data-target')\n\n if (!selector || selector === '#') {\n const hrefAttr = element.getAttribute('href')\n selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : ''\n }\n\n return selector && document.querySelector(selector) ? selector : null\n },\n\n getTransitionDurationFromElement(element) {\n if (!element) {\n return 0\n }\n\n // Get transition-duration of the element\n let transitionDuration = $(element).css('transition-duration')\n let transitionDelay = $(element).css('transition-delay')\n\n const floatTransitionDuration = parseFloat(transitionDuration)\n const floatTransitionDelay = parseFloat(transitionDelay)\n\n // Return 0 if element or transition duration is not found\n if (!floatTransitionDuration && !floatTransitionDelay) {\n return 0\n }\n\n // If multiple durations are defined, take the first\n transitionDuration = transitionDuration.split(',')[0]\n transitionDelay = transitionDelay.split(',')[0]\n\n return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER\n },\n\n reflow(element) {\n return element.offsetHeight\n },\n\n triggerTransitionEnd(element) {\n $(element).trigger(TRANSITION_END)\n },\n\n // TODO: Remove in v5\n supportsTransitionEnd() {\n return Boolean(TRANSITION_END)\n },\n\n isElement(obj) {\n return (obj[0] || obj).nodeType\n },\n\n typeCheckConfig(componentName, config, configTypes) {\n for (const property in configTypes) {\n if (Object.prototype.hasOwnProperty.call(configTypes, property)) {\n const expectedTypes = configTypes[property]\n const value = config[property]\n const valueType = value && Util.isElement(value)\n ? 'element' : toType(value)\n\n if (!new RegExp(expectedTypes).test(valueType)) {\n throw new Error(\n `${componentName.toUpperCase()}: ` +\n `Option \"${property}\" provided type \"${valueType}\" ` +\n `but expected type \"${expectedTypes}\".`)\n }\n }\n }\n },\n\n findShadowRoot(element) {\n if (!document.documentElement.attachShadow) {\n return null\n }\n\n // Can find the shadow root otherwise it'll return the document\n if (typeof element.getRootNode === 'function') {\n const root = element.getRootNode()\n return root instanceof ShadowRoot ? root : null\n }\n\n if (element instanceof ShadowRoot) {\n return element\n }\n\n // when we don't find a shadow root\n if (!element.parentNode) {\n return null\n }\n\n return Util.findShadowRoot(element.parentNode)\n }\n}\n\nsetTransitionEndSupport()\n\nexport default Util\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): alert.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'alert'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.alert'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst Selector = {\n DISMISS : '[data-dismiss=\"alert\"]'\n}\n\nconst Event = {\n CLOSE : `close${EVENT_KEY}`,\n CLOSED : `closed${EVENT_KEY}`,\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n ALERT : 'alert',\n FADE : 'fade',\n SHOW : 'show'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Alert {\n constructor(element) {\n this._element = element\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n // Public\n\n close(element) {\n let rootElement = this._element\n if (element) {\n rootElement = this._getRootElement(element)\n }\n\n const customEvent = this._triggerCloseEvent(rootElement)\n\n if (customEvent.isDefaultPrevented()) {\n return\n }\n\n this._removeElement(rootElement)\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n this._element = null\n }\n\n // Private\n\n _getRootElement(element) {\n const selector = Util.getSelectorFromElement(element)\n let parent = false\n\n if (selector) {\n parent = document.querySelector(selector)\n }\n\n if (!parent) {\n parent = $(element).closest(`.${ClassName.ALERT}`)[0]\n }\n\n return parent\n }\n\n _triggerCloseEvent(element) {\n const closeEvent = $.Event(Event.CLOSE)\n\n $(element).trigger(closeEvent)\n return closeEvent\n }\n\n _removeElement(element) {\n $(element).removeClass(ClassName.SHOW)\n\n if (!$(element).hasClass(ClassName.FADE)) {\n this._destroyElement(element)\n return\n }\n\n const transitionDuration = Util.getTransitionDurationFromElement(element)\n\n $(element)\n .one(Util.TRANSITION_END, (event) => this._destroyElement(element, event))\n .emulateTransitionEnd(transitionDuration)\n }\n\n _destroyElement(element) {\n $(element)\n .detach()\n .trigger(Event.CLOSED)\n .remove()\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n const $element = $(this)\n let data = $element.data(DATA_KEY)\n\n if (!data) {\n data = new Alert(this)\n $element.data(DATA_KEY, data)\n }\n\n if (config === 'close') {\n data[config](this)\n }\n })\n }\n\n static _handleDismiss(alertInstance) {\n return function (event) {\n if (event) {\n event.preventDefault()\n }\n\n alertInstance.close(this)\n }\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(\n Event.CLICK_DATA_API,\n Selector.DISMISS,\n Alert._handleDismiss(new Alert())\n)\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Alert._jQueryInterface\n$.fn[NAME].Constructor = Alert\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Alert._jQueryInterface\n}\n\nexport default Alert\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): button.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'button'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.button'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst ClassName = {\n ACTIVE : 'active',\n BUTTON : 'btn',\n FOCUS : 'focus'\n}\n\nconst Selector = {\n DATA_TOGGLE_CARROT : '[data-toggle^=\"button\"]',\n DATA_TOGGLE : '[data-toggle=\"buttons\"]',\n INPUT : 'input:not([type=\"hidden\"])',\n ACTIVE : '.active',\n BUTTON : '.btn'\n}\n\nconst Event = {\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,\n FOCUS_BLUR_DATA_API : `focus${EVENT_KEY}${DATA_API_KEY} ` +\n `blur${EVENT_KEY}${DATA_API_KEY}`\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Button {\n constructor(element) {\n this._element = element\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n // Public\n\n toggle() {\n let triggerChangeEvent = true\n let addAriaPressed = true\n const rootElement = $(this._element).closest(\n Selector.DATA_TOGGLE\n )[0]\n\n if (rootElement) {\n const input = this._element.querySelector(Selector.INPUT)\n\n if (input) {\n if (input.type === 'radio') {\n if (input.checked &&\n this._element.classList.contains(ClassName.ACTIVE)) {\n triggerChangeEvent = false\n } else {\n const activeElement = rootElement.querySelector(Selector.ACTIVE)\n\n if (activeElement) {\n $(activeElement).removeClass(ClassName.ACTIVE)\n }\n }\n }\n\n if (triggerChangeEvent) {\n if (input.hasAttribute('disabled') ||\n rootElement.hasAttribute('disabled') ||\n input.classList.contains('disabled') ||\n rootElement.classList.contains('disabled')) {\n return\n }\n input.checked = !this._element.classList.contains(ClassName.ACTIVE)\n $(input).trigger('change')\n }\n\n input.focus()\n addAriaPressed = false\n }\n }\n\n if (addAriaPressed) {\n this._element.setAttribute('aria-pressed',\n !this._element.classList.contains(ClassName.ACTIVE))\n }\n\n if (triggerChangeEvent) {\n $(this._element).toggleClass(ClassName.ACTIVE)\n }\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n this._element = null\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n\n if (!data) {\n data = new Button(this)\n $(this).data(DATA_KEY, data)\n }\n\n if (config === 'toggle') {\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document)\n .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => {\n event.preventDefault()\n\n let button = event.target\n\n if (!$(button).hasClass(ClassName.BUTTON)) {\n button = $(button).closest(Selector.BUTTON)\n }\n\n Button._jQueryInterface.call($(button), 'toggle')\n })\n .on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, (event) => {\n const button = $(event.target).closest(Selector.BUTTON)[0]\n $(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type))\n })\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Button._jQueryInterface\n$.fn[NAME].Constructor = Button\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Button._jQueryInterface\n}\n\nexport default Button\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): carousel.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'carousel'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.carousel'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key\nconst ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key\nconst TOUCHEVENT_COMPAT_WAIT = 500 // Time for mouse compat events to fire after touch\nconst SWIPE_THRESHOLD = 40\n\nconst Default = {\n interval : 5000,\n keyboard : true,\n slide : false,\n pause : 'hover',\n wrap : true,\n touch : true\n}\n\nconst DefaultType = {\n interval : '(number|boolean)',\n keyboard : 'boolean',\n slide : '(boolean|string)',\n pause : '(string|boolean)',\n wrap : 'boolean',\n touch : 'boolean'\n}\n\nconst Direction = {\n NEXT : 'next',\n PREV : 'prev',\n LEFT : 'left',\n RIGHT : 'right'\n}\n\nconst Event = {\n SLIDE : `slide${EVENT_KEY}`,\n SLID : `slid${EVENT_KEY}`,\n KEYDOWN : `keydown${EVENT_KEY}`,\n MOUSEENTER : `mouseenter${EVENT_KEY}`,\n MOUSELEAVE : `mouseleave${EVENT_KEY}`,\n TOUCHSTART : `touchstart${EVENT_KEY}`,\n TOUCHMOVE : `touchmove${EVENT_KEY}`,\n TOUCHEND : `touchend${EVENT_KEY}`,\n POINTERDOWN : `pointerdown${EVENT_KEY}`,\n POINTERUP : `pointerup${EVENT_KEY}`,\n DRAG_START : `dragstart${EVENT_KEY}`,\n LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`,\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n CAROUSEL : 'carousel',\n ACTIVE : 'active',\n SLIDE : 'slide',\n RIGHT : 'carousel-item-right',\n LEFT : 'carousel-item-left',\n NEXT : 'carousel-item-next',\n PREV : 'carousel-item-prev',\n ITEM : 'carousel-item',\n POINTER_EVENT : 'pointer-event'\n}\n\nconst Selector = {\n ACTIVE : '.active',\n ACTIVE_ITEM : '.active.carousel-item',\n ITEM : '.carousel-item',\n ITEM_IMG : '.carousel-item img',\n NEXT_PREV : '.carousel-item-next, .carousel-item-prev',\n INDICATORS : '.carousel-indicators',\n DATA_SLIDE : '[data-slide], [data-slide-to]',\n DATA_RIDE : '[data-ride=\"carousel\"]'\n}\n\nconst PointerType = {\n TOUCH : 'touch',\n PEN : 'pen'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\nclass Carousel {\n constructor(element, config) {\n this._items = null\n this._interval = null\n this._activeElement = null\n this._isPaused = false\n this._isSliding = false\n this.touchTimeout = null\n this.touchStartX = 0\n this.touchDeltaX = 0\n\n this._config = this._getConfig(config)\n this._element = element\n this._indicatorsElement = this._element.querySelector(Selector.INDICATORS)\n this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0\n this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent)\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n next() {\n if (!this._isSliding) {\n this._slide(Direction.NEXT)\n }\n }\n\n nextWhenVisible() {\n // Don't call next when the page isn't visible\n // or the carousel or its parent isn't visible\n if (!document.hidden &&\n ($(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden')) {\n this.next()\n }\n }\n\n prev() {\n if (!this._isSliding) {\n this._slide(Direction.PREV)\n }\n }\n\n pause(event) {\n if (!event) {\n this._isPaused = true\n }\n\n if (this._element.querySelector(Selector.NEXT_PREV)) {\n Util.triggerTransitionEnd(this._element)\n this.cycle(true)\n }\n\n clearInterval(this._interval)\n this._interval = null\n }\n\n cycle(event) {\n if (!event) {\n this._isPaused = false\n }\n\n if (this._interval) {\n clearInterval(this._interval)\n this._interval = null\n }\n\n if (this._config.interval && !this._isPaused) {\n this._interval = setInterval(\n (document.visibilityState ? this.nextWhenVisible : this.next).bind(this),\n this._config.interval\n )\n }\n }\n\n to(index) {\n this._activeElement = this._element.querySelector(Selector.ACTIVE_ITEM)\n\n const activeIndex = this._getItemIndex(this._activeElement)\n\n if (index > this._items.length - 1 || index < 0) {\n return\n }\n\n if (this._isSliding) {\n $(this._element).one(Event.SLID, () => this.to(index))\n return\n }\n\n if (activeIndex === index) {\n this.pause()\n this.cycle()\n return\n }\n\n const direction = index > activeIndex\n ? Direction.NEXT\n : Direction.PREV\n\n this._slide(direction, this._items[index])\n }\n\n dispose() {\n $(this._element).off(EVENT_KEY)\n $.removeData(this._element, DATA_KEY)\n\n this._items = null\n this._config = null\n this._element = null\n this._interval = null\n this._isPaused = null\n this._isSliding = null\n this._activeElement = null\n this._indicatorsElement = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _handleSwipe() {\n const absDeltax = Math.abs(this.touchDeltaX)\n\n if (absDeltax <= SWIPE_THRESHOLD) {\n return\n }\n\n const direction = absDeltax / this.touchDeltaX\n\n // swipe left\n if (direction > 0) {\n this.prev()\n }\n\n // swipe right\n if (direction < 0) {\n this.next()\n }\n }\n\n _addEventListeners() {\n if (this._config.keyboard) {\n $(this._element)\n .on(Event.KEYDOWN, (event) => this._keydown(event))\n }\n\n if (this._config.pause === 'hover') {\n $(this._element)\n .on(Event.MOUSEENTER, (event) => this.pause(event))\n .on(Event.MOUSELEAVE, (event) => this.cycle(event))\n }\n\n this._addTouchEventListeners()\n }\n\n _addTouchEventListeners() {\n if (!this._touchSupported) {\n return\n }\n\n const start = (event) => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchStartX = event.originalEvent.clientX\n } else if (!this._pointerEvent) {\n this.touchStartX = event.originalEvent.touches[0].clientX\n }\n }\n\n const move = (event) => {\n // ensure swiping with one touch and not pinching\n if (event.originalEvent.touches && event.originalEvent.touches.length > 1) {\n this.touchDeltaX = 0\n } else {\n this.touchDeltaX = event.originalEvent.touches[0].clientX - this.touchStartX\n }\n }\n\n const end = (event) => {\n if (this._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {\n this.touchDeltaX = event.originalEvent.clientX - this.touchStartX\n }\n\n this._handleSwipe()\n if (this._config.pause === 'hover') {\n // If it's a touch-enabled device, mouseenter/leave are fired as\n // part of the mouse compatibility events on first tap - the carousel\n // would stop cycling until user tapped out of it;\n // here, we listen for touchend, explicitly pause the carousel\n // (as if it's the second time we tap on it, mouseenter compat event\n // is NOT fired) and after a timeout (to allow for mouse compatibility\n // events to fire) we explicitly restart cycling\n\n this.pause()\n if (this.touchTimeout) {\n clearTimeout(this.touchTimeout)\n }\n this.touchTimeout = setTimeout((event) => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval)\n }\n }\n\n $(this._element.querySelectorAll(Selector.ITEM_IMG)).on(Event.DRAG_START, (e) => e.preventDefault())\n if (this._pointerEvent) {\n $(this._element).on(Event.POINTERDOWN, (event) => start(event))\n $(this._element).on(Event.POINTERUP, (event) => end(event))\n\n this._element.classList.add(ClassName.POINTER_EVENT)\n } else {\n $(this._element).on(Event.TOUCHSTART, (event) => start(event))\n $(this._element).on(Event.TOUCHMOVE, (event) => move(event))\n $(this._element).on(Event.TOUCHEND, (event) => end(event))\n }\n }\n\n _keydown(event) {\n if (/input|textarea/i.test(event.target.tagName)) {\n return\n }\n\n switch (event.which) {\n case ARROW_LEFT_KEYCODE:\n event.preventDefault()\n this.prev()\n break\n case ARROW_RIGHT_KEYCODE:\n event.preventDefault()\n this.next()\n break\n default:\n }\n }\n\n _getItemIndex(element) {\n this._items = element && element.parentNode\n ? [].slice.call(element.parentNode.querySelectorAll(Selector.ITEM))\n : []\n return this._items.indexOf(element)\n }\n\n _getItemByDirection(direction, activeElement) {\n const isNextDirection = direction === Direction.NEXT\n const isPrevDirection = direction === Direction.PREV\n const activeIndex = this._getItemIndex(activeElement)\n const lastItemIndex = this._items.length - 1\n const isGoingToWrap = isPrevDirection && activeIndex === 0 ||\n isNextDirection && activeIndex === lastItemIndex\n\n if (isGoingToWrap && !this._config.wrap) {\n return activeElement\n }\n\n const delta = direction === Direction.PREV ? -1 : 1\n const itemIndex = (activeIndex + delta) % this._items.length\n\n return itemIndex === -1\n ? this._items[this._items.length - 1] : this._items[itemIndex]\n }\n\n _triggerSlideEvent(relatedTarget, eventDirectionName) {\n const targetIndex = this._getItemIndex(relatedTarget)\n const fromIndex = this._getItemIndex(this._element.querySelector(Selector.ACTIVE_ITEM))\n const slideEvent = $.Event(Event.SLIDE, {\n relatedTarget,\n direction: eventDirectionName,\n from: fromIndex,\n to: targetIndex\n })\n\n $(this._element).trigger(slideEvent)\n\n return slideEvent\n }\n\n _setActiveIndicatorElement(element) {\n if (this._indicatorsElement) {\n const indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector.ACTIVE))\n $(indicators)\n .removeClass(ClassName.ACTIVE)\n\n const nextIndicator = this._indicatorsElement.children[\n this._getItemIndex(element)\n ]\n\n if (nextIndicator) {\n $(nextIndicator).addClass(ClassName.ACTIVE)\n }\n }\n }\n\n _slide(direction, element) {\n const activeElement = this._element.querySelector(Selector.ACTIVE_ITEM)\n const activeElementIndex = this._getItemIndex(activeElement)\n const nextElement = element || activeElement &&\n this._getItemByDirection(direction, activeElement)\n const nextElementIndex = this._getItemIndex(nextElement)\n const isCycling = Boolean(this._interval)\n\n let directionalClassName\n let orderClassName\n let eventDirectionName\n\n if (direction === Direction.NEXT) {\n directionalClassName = ClassName.LEFT\n orderClassName = ClassName.NEXT\n eventDirectionName = Direction.LEFT\n } else {\n directionalClassName = ClassName.RIGHT\n orderClassName = ClassName.PREV\n eventDirectionName = Direction.RIGHT\n }\n\n if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) {\n this._isSliding = false\n return\n }\n\n const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName)\n if (slideEvent.isDefaultPrevented()) {\n return\n }\n\n if (!activeElement || !nextElement) {\n // Some weirdness is happening, so we bail\n return\n }\n\n this._isSliding = true\n\n if (isCycling) {\n this.pause()\n }\n\n this._setActiveIndicatorElement(nextElement)\n\n const slidEvent = $.Event(Event.SLID, {\n relatedTarget: nextElement,\n direction: eventDirectionName,\n from: activeElementIndex,\n to: nextElementIndex\n })\n\n if ($(this._element).hasClass(ClassName.SLIDE)) {\n $(nextElement).addClass(orderClassName)\n\n Util.reflow(nextElement)\n\n $(activeElement).addClass(directionalClassName)\n $(nextElement).addClass(directionalClassName)\n\n const nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10)\n if (nextElementInterval) {\n this._config.defaultInterval = this._config.defaultInterval || this._config.interval\n this._config.interval = nextElementInterval\n } else {\n this._config.interval = this._config.defaultInterval || this._config.interval\n }\n\n const transitionDuration = Util.getTransitionDurationFromElement(activeElement)\n\n $(activeElement)\n .one(Util.TRANSITION_END, () => {\n $(nextElement)\n .removeClass(`${directionalClassName} ${orderClassName}`)\n .addClass(ClassName.ACTIVE)\n\n $(activeElement).removeClass(`${ClassName.ACTIVE} ${orderClassName} ${directionalClassName}`)\n\n this._isSliding = false\n\n setTimeout(() => $(this._element).trigger(slidEvent), 0)\n })\n .emulateTransitionEnd(transitionDuration)\n } else {\n $(activeElement).removeClass(ClassName.ACTIVE)\n $(nextElement).addClass(ClassName.ACTIVE)\n\n this._isSliding = false\n $(this._element).trigger(slidEvent)\n }\n\n if (isCycling) {\n this.cycle()\n }\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n let _config = {\n ...Default,\n ...$(this).data()\n }\n\n if (typeof config === 'object') {\n _config = {\n ..._config,\n ...config\n }\n }\n\n const action = typeof config === 'string' ? config : _config.slide\n\n if (!data) {\n data = new Carousel(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'number') {\n data.to(config)\n } else if (typeof action === 'string') {\n if (typeof data[action] === 'undefined') {\n throw new TypeError(`No method named \"${action}\"`)\n }\n data[action]()\n } else if (_config.interval) {\n data.pause()\n data.cycle()\n }\n })\n }\n\n static _dataApiClickHandler(event) {\n const selector = Util.getSelectorFromElement(this)\n\n if (!selector) {\n return\n }\n\n const target = $(selector)[0]\n\n if (!target || !$(target).hasClass(ClassName.CAROUSEL)) {\n return\n }\n\n const config = {\n ...$(target).data(),\n ...$(this).data()\n }\n const slideIndex = this.getAttribute('data-slide-to')\n\n if (slideIndex) {\n config.interval = false\n }\n\n Carousel._jQueryInterface.call($(target), config)\n\n if (slideIndex) {\n $(target).data(DATA_KEY).to(slideIndex)\n }\n\n event.preventDefault()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document)\n .on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler)\n\n$(window).on(Event.LOAD_DATA_API, () => {\n const carousels = [].slice.call(document.querySelectorAll(Selector.DATA_RIDE))\n for (let i = 0, len = carousels.length; i < len; i++) {\n const $carousel = $(carousels[i])\n Carousel._jQueryInterface.call($carousel, $carousel.data())\n }\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Carousel._jQueryInterface\n$.fn[NAME].Constructor = Carousel\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Carousel._jQueryInterface\n}\n\nexport default Carousel\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): collapse.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'collapse'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.collapse'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst Default = {\n toggle : true,\n parent : ''\n}\n\nconst DefaultType = {\n toggle : 'boolean',\n parent : '(string|element)'\n}\n\nconst Event = {\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n SHOW : 'show',\n COLLAPSE : 'collapse',\n COLLAPSING : 'collapsing',\n COLLAPSED : 'collapsed'\n}\n\nconst Dimension = {\n WIDTH : 'width',\n HEIGHT : 'height'\n}\n\nconst Selector = {\n ACTIVES : '.show, .collapsing',\n DATA_TOGGLE : '[data-toggle=\"collapse\"]'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Collapse {\n constructor(element, config) {\n this._isTransitioning = false\n this._element = element\n this._config = this._getConfig(config)\n this._triggerArray = [].slice.call(document.querySelectorAll(\n `[data-toggle=\"collapse\"][href=\"#${element.id}\"],` +\n `[data-toggle=\"collapse\"][data-target=\"#${element.id}\"]`\n ))\n\n const toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))\n for (let i = 0, len = toggleList.length; i < len; i++) {\n const elem = toggleList[i]\n const selector = Util.getSelectorFromElement(elem)\n const filterElement = [].slice.call(document.querySelectorAll(selector))\n .filter((foundElem) => foundElem === element)\n\n if (selector !== null && filterElement.length > 0) {\n this._selector = selector\n this._triggerArray.push(elem)\n }\n }\n\n this._parent = this._config.parent ? this._getParent() : null\n\n if (!this._config.parent) {\n this._addAriaAndCollapsedClass(this._element, this._triggerArray)\n }\n\n if (this._config.toggle) {\n this.toggle()\n }\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n toggle() {\n if ($(this._element).hasClass(ClassName.SHOW)) {\n this.hide()\n } else {\n this.show()\n }\n }\n\n show() {\n if (this._isTransitioning ||\n $(this._element).hasClass(ClassName.SHOW)) {\n return\n }\n\n let actives\n let activesData\n\n if (this._parent) {\n actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES))\n .filter((elem) => {\n if (typeof this._config.parent === 'string') {\n return elem.getAttribute('data-parent') === this._config.parent\n }\n\n return elem.classList.contains(ClassName.COLLAPSE)\n })\n\n if (actives.length === 0) {\n actives = null\n }\n }\n\n if (actives) {\n activesData = $(actives).not(this._selector).data(DATA_KEY)\n if (activesData && activesData._isTransitioning) {\n return\n }\n }\n\n const startEvent = $.Event(Event.SHOW)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n if (actives) {\n Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide')\n if (!activesData) {\n $(actives).data(DATA_KEY, null)\n }\n }\n\n const dimension = this._getDimension()\n\n $(this._element)\n .removeClass(ClassName.COLLAPSE)\n .addClass(ClassName.COLLAPSING)\n\n this._element.style[dimension] = 0\n\n if (this._triggerArray.length) {\n $(this._triggerArray)\n .removeClass(ClassName.COLLAPSED)\n .attr('aria-expanded', true)\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n $(this._element)\n .removeClass(ClassName.COLLAPSING)\n .addClass(ClassName.COLLAPSE)\n .addClass(ClassName.SHOW)\n\n this._element.style[dimension] = ''\n\n this.setTransitioning(false)\n\n $(this._element).trigger(Event.SHOWN)\n }\n\n const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)\n const scrollSize = `scroll${capitalizedDimension}`\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n\n this._element.style[dimension] = `${this._element[scrollSize]}px`\n }\n\n hide() {\n if (this._isTransitioning ||\n !$(this._element).hasClass(ClassName.SHOW)) {\n return\n }\n\n const startEvent = $.Event(Event.HIDE)\n $(this._element).trigger(startEvent)\n if (startEvent.isDefaultPrevented()) {\n return\n }\n\n const dimension = this._getDimension()\n\n this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`\n\n Util.reflow(this._element)\n\n $(this._element)\n .addClass(ClassName.COLLAPSING)\n .removeClass(ClassName.COLLAPSE)\n .removeClass(ClassName.SHOW)\n\n const triggerArrayLength = this._triggerArray.length\n if (triggerArrayLength > 0) {\n for (let i = 0; i < triggerArrayLength; i++) {\n const trigger = this._triggerArray[i]\n const selector = Util.getSelectorFromElement(trigger)\n\n if (selector !== null) {\n const $elem = $([].slice.call(document.querySelectorAll(selector)))\n if (!$elem.hasClass(ClassName.SHOW)) {\n $(trigger).addClass(ClassName.COLLAPSED)\n .attr('aria-expanded', false)\n }\n }\n }\n }\n\n this.setTransitioning(true)\n\n const complete = () => {\n this.setTransitioning(false)\n $(this._element)\n .removeClass(ClassName.COLLAPSING)\n .addClass(ClassName.COLLAPSE)\n .trigger(Event.HIDDEN)\n }\n\n this._element.style[dimension] = ''\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n }\n\n setTransitioning(isTransitioning) {\n this._isTransitioning = isTransitioning\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._parent = null\n this._element = null\n this._triggerArray = null\n this._isTransitioning = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n config.toggle = Boolean(config.toggle) // Coerce string values\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _getDimension() {\n const hasWidth = $(this._element).hasClass(Dimension.WIDTH)\n return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT\n }\n\n _getParent() {\n let parent\n\n if (Util.isElement(this._config.parent)) {\n parent = this._config.parent\n\n // It's a jQuery object\n if (typeof this._config.parent.jquery !== 'undefined') {\n parent = this._config.parent[0]\n }\n } else {\n parent = document.querySelector(this._config.parent)\n }\n\n const selector =\n `[data-toggle=\"collapse\"][data-parent=\"${this._config.parent}\"]`\n\n const children = [].slice.call(parent.querySelectorAll(selector))\n $(children).each((i, element) => {\n this._addAriaAndCollapsedClass(\n Collapse._getTargetFromElement(element),\n [element]\n )\n })\n\n return parent\n }\n\n _addAriaAndCollapsedClass(element, triggerArray) {\n const isOpen = $(element).hasClass(ClassName.SHOW)\n\n if (triggerArray.length) {\n $(triggerArray)\n .toggleClass(ClassName.COLLAPSED, !isOpen)\n .attr('aria-expanded', isOpen)\n }\n }\n\n // Static\n\n static _getTargetFromElement(element) {\n const selector = Util.getSelectorFromElement(element)\n return selector ? document.querySelector(selector) : null\n }\n\n static _jQueryInterface(config) {\n return this.each(function () {\n const $this = $(this)\n let data = $this.data(DATA_KEY)\n const _config = {\n ...Default,\n ...$this.data(),\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (!data && _config.toggle && /show|hide/.test(config)) {\n _config.toggle = false\n }\n\n if (!data) {\n data = new Collapse(this, _config)\n $this.data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {\n // preventDefault only for elements (which change the URL) not inside the collapsible element\n if (event.currentTarget.tagName === 'A') {\n event.preventDefault()\n }\n\n const $trigger = $(this)\n const selector = Util.getSelectorFromElement(this)\n const selectors = [].slice.call(document.querySelectorAll(selector))\n\n $(selectors).each(function () {\n const $target = $(this)\n const data = $target.data(DATA_KEY)\n const config = data ? 'toggle' : $trigger.data()\n Collapse._jQueryInterface.call($target, config)\n })\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Collapse._jQueryInterface\n$.fn[NAME].Constructor = Collapse\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Collapse._jQueryInterface\n}\n\nexport default Collapse\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): dropdown.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Popper from 'popper.js'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'dropdown'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.dropdown'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key\nconst SPACE_KEYCODE = 32 // KeyboardEvent.which value for space key\nconst TAB_KEYCODE = 9 // KeyboardEvent.which value for tab key\nconst ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key\nconst ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key\nconst RIGHT_MOUSE_BUTTON_WHICH = 3 // MouseEvent.which value for the right button (assuming a right-handed mouse)\nconst REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEYCODE}|${ARROW_DOWN_KEYCODE}|${ESCAPE_KEYCODE}`)\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n CLICK : `click${EVENT_KEY}`,\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`,\n KEYDOWN_DATA_API : `keydown${EVENT_KEY}${DATA_API_KEY}`,\n KEYUP_DATA_API : `keyup${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n DISABLED : 'disabled',\n SHOW : 'show',\n DROPUP : 'dropup',\n DROPRIGHT : 'dropright',\n DROPLEFT : 'dropleft',\n MENURIGHT : 'dropdown-menu-right',\n MENULEFT : 'dropdown-menu-left',\n POSITION_STATIC : 'position-static'\n}\n\nconst Selector = {\n DATA_TOGGLE : '[data-toggle=\"dropdown\"]',\n FORM_CHILD : '.dropdown form',\n MENU : '.dropdown-menu',\n NAVBAR_NAV : '.navbar-nav',\n VISIBLE_ITEMS : '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'\n}\n\nconst AttachmentMap = {\n TOP : 'top-start',\n TOPEND : 'top-end',\n BOTTOM : 'bottom-start',\n BOTTOMEND : 'bottom-end',\n RIGHT : 'right-start',\n RIGHTEND : 'right-end',\n LEFT : 'left-start',\n LEFTEND : 'left-end'\n}\n\nconst Default = {\n offset : 0,\n flip : true,\n boundary : 'scrollParent',\n reference : 'toggle',\n display : 'dynamic'\n}\n\nconst DefaultType = {\n offset : '(number|string|function)',\n flip : 'boolean',\n boundary : '(string|element)',\n reference : '(string|element)',\n display : 'string'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Dropdown {\n constructor(element, config) {\n this._element = element\n this._popper = null\n this._config = this._getConfig(config)\n this._menu = this._getMenuElement()\n this._inNavbar = this._detectNavbar()\n\n this._addEventListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Public\n\n toggle() {\n if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED)) {\n return\n }\n\n const parent = Dropdown._getParentFromElement(this._element)\n const isActive = $(this._menu).hasClass(ClassName.SHOW)\n\n Dropdown._clearMenus()\n\n if (isActive) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n const showEvent = $.Event(Event.SHOW, relatedTarget)\n\n $(parent).trigger(showEvent)\n\n if (showEvent.isDefaultPrevented()) {\n return\n }\n\n // Disable totally Popper.js for Dropdown in Navbar\n if (!this._inNavbar) {\n /**\n * Check for Popper dependency\n * Popper - https://popper.js.org\n */\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s dropdowns require Popper.js (https://popper.js.org/)')\n }\n\n let referenceElement = this._element\n\n if (this._config.reference === 'parent') {\n referenceElement = parent\n } else if (Util.isElement(this._config.reference)) {\n referenceElement = this._config.reference\n\n // Check if it's jQuery element\n if (typeof this._config.reference.jquery !== 'undefined') {\n referenceElement = this._config.reference[0]\n }\n }\n\n // If boundary is not `scrollParent`, then set position to `static`\n // to allow the menu to \"escape\" the scroll parent's boundaries\n // https://github.com/twbs/bootstrap/issues/24251\n if (this._config.boundary !== 'scrollParent') {\n $(parent).addClass(ClassName.POSITION_STATIC)\n }\n this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig())\n }\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement &&\n $(parent).closest(Selector.NAVBAR_NAV).length === 0) {\n $(document.body).children().on('mouseover', null, $.noop)\n }\n\n this._element.focus()\n this._element.setAttribute('aria-expanded', true)\n\n $(this._menu).toggleClass(ClassName.SHOW)\n $(parent)\n .toggleClass(ClassName.SHOW)\n .trigger($.Event(Event.SHOWN, relatedTarget))\n }\n\n show() {\n if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || $(this._menu).hasClass(ClassName.SHOW)) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n const showEvent = $.Event(Event.SHOW, relatedTarget)\n const parent = Dropdown._getParentFromElement(this._element)\n\n $(parent).trigger(showEvent)\n\n if (showEvent.isDefaultPrevented()) {\n return\n }\n\n $(this._menu).toggleClass(ClassName.SHOW)\n $(parent)\n .toggleClass(ClassName.SHOW)\n .trigger($.Event(Event.SHOWN, relatedTarget))\n }\n\n hide() {\n if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || !$(this._menu).hasClass(ClassName.SHOW)) {\n return\n }\n\n const relatedTarget = {\n relatedTarget: this._element\n }\n const hideEvent = $.Event(Event.HIDE, relatedTarget)\n const parent = Dropdown._getParentFromElement(this._element)\n\n $(parent).trigger(hideEvent)\n\n if (hideEvent.isDefaultPrevented()) {\n return\n }\n\n $(this._menu).toggleClass(ClassName.SHOW)\n $(parent)\n .toggleClass(ClassName.SHOW)\n .trigger($.Event(Event.HIDDEN, relatedTarget))\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n $(this._element).off(EVENT_KEY)\n this._element = null\n this._menu = null\n if (this._popper !== null) {\n this._popper.destroy()\n this._popper = null\n }\n }\n\n update() {\n this._inNavbar = this._detectNavbar()\n if (this._popper !== null) {\n this._popper.scheduleUpdate()\n }\n }\n\n // Private\n\n _addEventListeners() {\n $(this._element).on(Event.CLICK, (event) => {\n event.preventDefault()\n event.stopPropagation()\n this.toggle()\n })\n }\n\n _getConfig(config) {\n config = {\n ...this.constructor.Default,\n ...$(this._element).data(),\n ...config\n }\n\n Util.typeCheckConfig(\n NAME,\n config,\n this.constructor.DefaultType\n )\n\n return config\n }\n\n _getMenuElement() {\n if (!this._menu) {\n const parent = Dropdown._getParentFromElement(this._element)\n\n if (parent) {\n this._menu = parent.querySelector(Selector.MENU)\n }\n }\n return this._menu\n }\n\n _getPlacement() {\n const $parentDropdown = $(this._element.parentNode)\n let placement = AttachmentMap.BOTTOM\n\n // Handle dropup\n if ($parentDropdown.hasClass(ClassName.DROPUP)) {\n placement = AttachmentMap.TOP\n if ($(this._menu).hasClass(ClassName.MENURIGHT)) {\n placement = AttachmentMap.TOPEND\n }\n } else if ($parentDropdown.hasClass(ClassName.DROPRIGHT)) {\n placement = AttachmentMap.RIGHT\n } else if ($parentDropdown.hasClass(ClassName.DROPLEFT)) {\n placement = AttachmentMap.LEFT\n } else if ($(this._menu).hasClass(ClassName.MENURIGHT)) {\n placement = AttachmentMap.BOTTOMEND\n }\n return placement\n }\n\n _detectNavbar() {\n return $(this._element).closest('.navbar').length > 0\n }\n\n _getPopperConfig() {\n const offsetConf = {}\n if (typeof this._config.offset === 'function') {\n offsetConf.fn = (data) => {\n data.offsets = {\n ...data.offsets,\n ...this._config.offset(data.offsets) || {}\n }\n return data\n }\n } else {\n offsetConf.offset = this._config.offset\n }\n\n const popperConfig = {\n placement: this._getPlacement(),\n modifiers: {\n offset: offsetConf,\n flip: {\n enabled: this._config.flip\n },\n preventOverflow: {\n boundariesElement: this._config.boundary\n }\n }\n }\n\n // Disable Popper.js if we have a static display\n if (this._config.display === 'static') {\n popperConfig.modifiers.applyStyle = {\n enabled: false\n }\n }\n return popperConfig\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' ? config : null\n\n if (!data) {\n data = new Dropdown(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n\n static _clearMenus(event) {\n if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH ||\n event.type === 'keyup' && event.which !== TAB_KEYCODE)) {\n return\n }\n\n const toggles = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE))\n\n for (let i = 0, len = toggles.length; i < len; i++) {\n const parent = Dropdown._getParentFromElement(toggles[i])\n const context = $(toggles[i]).data(DATA_KEY)\n const relatedTarget = {\n relatedTarget: toggles[i]\n }\n\n if (event && event.type === 'click') {\n relatedTarget.clickEvent = event\n }\n\n if (!context) {\n continue\n }\n\n const dropdownMenu = context._menu\n if (!$(parent).hasClass(ClassName.SHOW)) {\n continue\n }\n\n if (event && (event.type === 'click' &&\n /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) &&\n $.contains(parent, event.target)) {\n continue\n }\n\n const hideEvent = $.Event(Event.HIDE, relatedTarget)\n $(parent).trigger(hideEvent)\n if (hideEvent.isDefaultPrevented()) {\n continue\n }\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().off('mouseover', null, $.noop)\n }\n\n toggles[i].setAttribute('aria-expanded', 'false')\n\n $(dropdownMenu).removeClass(ClassName.SHOW)\n $(parent)\n .removeClass(ClassName.SHOW)\n .trigger($.Event(Event.HIDDEN, relatedTarget))\n }\n }\n\n static _getParentFromElement(element) {\n let parent\n const selector = Util.getSelectorFromElement(element)\n\n if (selector) {\n parent = document.querySelector(selector)\n }\n\n return parent || element.parentNode\n }\n\n // eslint-disable-next-line complexity\n static _dataApiKeydownHandler(event) {\n // If not input/textarea:\n // - And not a key in REGEXP_KEYDOWN => not a dropdown command\n // If input/textarea:\n // - If space key => not a dropdown command\n // - If key is other than escape\n // - If key is not up or down => not a dropdown command\n // - If trigger inside the menu => not a dropdown command\n if (/input|textarea/i.test(event.target.tagName)\n ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE &&\n (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE ||\n $(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {\n return\n }\n\n event.preventDefault()\n event.stopPropagation()\n\n if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {\n return\n }\n\n const parent = Dropdown._getParentFromElement(this)\n const isActive = $(parent).hasClass(ClassName.SHOW)\n\n if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {\n if (event.which === ESCAPE_KEYCODE) {\n const toggle = parent.querySelector(Selector.DATA_TOGGLE)\n $(toggle).trigger('focus')\n }\n\n $(this).trigger('click')\n return\n }\n\n const items = [].slice.call(parent.querySelectorAll(Selector.VISIBLE_ITEMS))\n\n if (items.length === 0) {\n return\n }\n\n let index = items.indexOf(event.target)\n\n if (event.which === ARROW_UP_KEYCODE && index > 0) { // Up\n index--\n }\n\n if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) { // Down\n index++\n }\n\n if (index < 0) {\n index = 0\n }\n\n items[index].focus()\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document)\n .on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler)\n .on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler)\n .on(`${Event.CLICK_DATA_API} ${Event.KEYUP_DATA_API}`, Dropdown._clearMenus)\n .on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {\n event.preventDefault()\n event.stopPropagation()\n Dropdown._jQueryInterface.call($(this), 'toggle')\n })\n .on(Event.CLICK_DATA_API, Selector.FORM_CHILD, (e) => {\n e.stopPropagation()\n })\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Dropdown._jQueryInterface\n$.fn[NAME].Constructor = Dropdown\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Dropdown._jQueryInterface\n}\n\n\nexport default Dropdown\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): modal.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'modal'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.modal'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst ESCAPE_KEYCODE = 27 // KeyboardEvent.which value for Escape (Esc) key\n\nconst Default = {\n backdrop : true,\n keyboard : true,\n focus : true,\n show : true\n}\n\nconst DefaultType = {\n backdrop : '(boolean|string)',\n keyboard : 'boolean',\n focus : 'boolean',\n show : 'boolean'\n}\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n FOCUSIN : `focusin${EVENT_KEY}`,\n RESIZE : `resize${EVENT_KEY}`,\n CLICK_DISMISS : `click.dismiss${EVENT_KEY}`,\n KEYDOWN_DISMISS : `keydown.dismiss${EVENT_KEY}`,\n MOUSEUP_DISMISS : `mouseup.dismiss${EVENT_KEY}`,\n MOUSEDOWN_DISMISS : `mousedown.dismiss${EVENT_KEY}`,\n CLICK_DATA_API : `click${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n SCROLLBAR_MEASURER : 'modal-scrollbar-measure',\n BACKDROP : 'modal-backdrop',\n OPEN : 'modal-open',\n FADE : 'fade',\n SHOW : 'show'\n}\n\nconst Selector = {\n DIALOG : '.modal-dialog',\n DATA_TOGGLE : '[data-toggle=\"modal\"]',\n DATA_DISMISS : '[data-dismiss=\"modal\"]',\n FIXED_CONTENT : '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',\n STICKY_CONTENT : '.sticky-top'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Modal {\n constructor(element, config) {\n this._config = this._getConfig(config)\n this._element = element\n this._dialog = element.querySelector(Selector.DIALOG)\n this._backdrop = null\n this._isShown = false\n this._isBodyOverflowing = false\n this._ignoreBackdropClick = false\n this._isTransitioning = false\n this._scrollbarWidth = 0\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n toggle(relatedTarget) {\n return this._isShown ? this.hide() : this.show(relatedTarget)\n }\n\n show(relatedTarget) {\n if (this._isShown || this._isTransitioning) {\n return\n }\n\n if ($(this._element).hasClass(ClassName.FADE)) {\n this._isTransitioning = true\n }\n\n const showEvent = $.Event(Event.SHOW, {\n relatedTarget\n })\n\n $(this._element).trigger(showEvent)\n\n if (this._isShown || showEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = true\n\n this._checkScrollbar()\n this._setScrollbar()\n\n this._adjustDialog()\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(this._element).on(\n Event.CLICK_DISMISS,\n Selector.DATA_DISMISS,\n (event) => this.hide(event)\n )\n\n $(this._dialog).on(Event.MOUSEDOWN_DISMISS, () => {\n $(this._element).one(Event.MOUSEUP_DISMISS, (event) => {\n if ($(event.target).is(this._element)) {\n this._ignoreBackdropClick = true\n }\n })\n })\n\n this._showBackdrop(() => this._showElement(relatedTarget))\n }\n\n hide(event) {\n if (event) {\n event.preventDefault()\n }\n\n if (!this._isShown || this._isTransitioning) {\n return\n }\n\n const hideEvent = $.Event(Event.HIDE)\n\n $(this._element).trigger(hideEvent)\n\n if (!this._isShown || hideEvent.isDefaultPrevented()) {\n return\n }\n\n this._isShown = false\n const transition = $(this._element).hasClass(ClassName.FADE)\n\n if (transition) {\n this._isTransitioning = true\n }\n\n this._setEscapeEvent()\n this._setResizeEvent()\n\n $(document).off(Event.FOCUSIN)\n\n $(this._element).removeClass(ClassName.SHOW)\n\n $(this._element).off(Event.CLICK_DISMISS)\n $(this._dialog).off(Event.MOUSEDOWN_DISMISS)\n\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._element)\n\n $(this._element)\n .one(Util.TRANSITION_END, (event) => this._hideModal(event))\n .emulateTransitionEnd(transitionDuration)\n } else {\n this._hideModal()\n }\n }\n\n dispose() {\n [window, this._element, this._dialog]\n .forEach((htmlElement) => $(htmlElement).off(EVENT_KEY))\n\n /**\n * `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API`\n * Do not move `document` in `htmlElements` array\n * It will remove `Event.CLICK_DATA_API` event that should remain\n */\n $(document).off(Event.FOCUSIN)\n\n $.removeData(this._element, DATA_KEY)\n\n this._config = null\n this._element = null\n this._dialog = null\n this._backdrop = null\n this._isShown = null\n this._isBodyOverflowing = null\n this._ignoreBackdropClick = null\n this._isTransitioning = null\n this._scrollbarWidth = null\n }\n\n handleUpdate() {\n this._adjustDialog()\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...config\n }\n Util.typeCheckConfig(NAME, config, DefaultType)\n return config\n }\n\n _showElement(relatedTarget) {\n const transition = $(this._element).hasClass(ClassName.FADE)\n\n if (!this._element.parentNode ||\n this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {\n // Don't move modal's DOM position\n document.body.appendChild(this._element)\n }\n\n this._element.style.display = 'block'\n this._element.removeAttribute('aria-hidden')\n this._element.setAttribute('aria-modal', true)\n this._element.scrollTop = 0\n\n if (transition) {\n Util.reflow(this._element)\n }\n\n $(this._element).addClass(ClassName.SHOW)\n\n if (this._config.focus) {\n this._enforceFocus()\n }\n\n const shownEvent = $.Event(Event.SHOWN, {\n relatedTarget\n })\n\n const transitionComplete = () => {\n if (this._config.focus) {\n this._element.focus()\n }\n this._isTransitioning = false\n $(this._element).trigger(shownEvent)\n }\n\n if (transition) {\n const transitionDuration = Util.getTransitionDurationFromElement(this._dialog)\n\n $(this._dialog)\n .one(Util.TRANSITION_END, transitionComplete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n transitionComplete()\n }\n }\n\n _enforceFocus() {\n $(document)\n .off(Event.FOCUSIN) // Guard against infinite focus loop\n .on(Event.FOCUSIN, (event) => {\n if (document !== event.target &&\n this._element !== event.target &&\n $(this._element).has(event.target).length === 0) {\n this._element.focus()\n }\n })\n }\n\n _setEscapeEvent() {\n if (this._isShown && this._config.keyboard) {\n $(this._element).on(Event.KEYDOWN_DISMISS, (event) => {\n if (event.which === ESCAPE_KEYCODE) {\n event.preventDefault()\n this.hide()\n }\n })\n } else if (!this._isShown) {\n $(this._element).off(Event.KEYDOWN_DISMISS)\n }\n }\n\n _setResizeEvent() {\n if (this._isShown) {\n $(window).on(Event.RESIZE, (event) => this.handleUpdate(event))\n } else {\n $(window).off(Event.RESIZE)\n }\n }\n\n _hideModal() {\n this._element.style.display = 'none'\n this._element.setAttribute('aria-hidden', true)\n this._element.removeAttribute('aria-modal')\n this._isTransitioning = false\n this._showBackdrop(() => {\n $(document.body).removeClass(ClassName.OPEN)\n this._resetAdjustments()\n this._resetScrollbar()\n $(this._element).trigger(Event.HIDDEN)\n })\n }\n\n _removeBackdrop() {\n if (this._backdrop) {\n $(this._backdrop).remove()\n this._backdrop = null\n }\n }\n\n _showBackdrop(callback) {\n const animate = $(this._element).hasClass(ClassName.FADE)\n ? ClassName.FADE : ''\n\n if (this._isShown && this._config.backdrop) {\n this._backdrop = document.createElement('div')\n this._backdrop.className = ClassName.BACKDROP\n\n if (animate) {\n this._backdrop.classList.add(animate)\n }\n\n $(this._backdrop).appendTo(document.body)\n\n $(this._element).on(Event.CLICK_DISMISS, (event) => {\n if (this._ignoreBackdropClick) {\n this._ignoreBackdropClick = false\n return\n }\n if (event.target !== event.currentTarget) {\n return\n }\n if (this._config.backdrop === 'static') {\n this._element.focus()\n } else {\n this.hide()\n }\n })\n\n if (animate) {\n Util.reflow(this._backdrop)\n }\n\n $(this._backdrop).addClass(ClassName.SHOW)\n\n if (!callback) {\n return\n }\n\n if (!animate) {\n callback()\n return\n }\n\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callback)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else if (!this._isShown && this._backdrop) {\n $(this._backdrop).removeClass(ClassName.SHOW)\n\n const callbackRemove = () => {\n this._removeBackdrop()\n if (callback) {\n callback()\n }\n }\n\n if ($(this._element).hasClass(ClassName.FADE)) {\n const backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop)\n\n $(this._backdrop)\n .one(Util.TRANSITION_END, callbackRemove)\n .emulateTransitionEnd(backdropTransitionDuration)\n } else {\n callbackRemove()\n }\n } else if (callback) {\n callback()\n }\n }\n\n // ----------------------------------------------------------------------\n // the following methods are used to handle overflowing modals\n // todo (fat): these should probably be refactored out of modal.js\n // ----------------------------------------------------------------------\n\n _adjustDialog() {\n const isModalOverflowing =\n this._element.scrollHeight > document.documentElement.clientHeight\n\n if (!this._isBodyOverflowing && isModalOverflowing) {\n this._element.style.paddingLeft = `${this._scrollbarWidth}px`\n }\n\n if (this._isBodyOverflowing && !isModalOverflowing) {\n this._element.style.paddingRight = `${this._scrollbarWidth}px`\n }\n }\n\n _resetAdjustments() {\n this._element.style.paddingLeft = ''\n this._element.style.paddingRight = ''\n }\n\n _checkScrollbar() {\n const rect = document.body.getBoundingClientRect()\n this._isBodyOverflowing = rect.left + rect.right < window.innerWidth\n this._scrollbarWidth = this._getScrollbarWidth()\n }\n\n _setScrollbar() {\n if (this._isBodyOverflowing) {\n // Note: DOMNode.style.paddingRight returns the actual value or '' if not set\n // while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set\n const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT))\n const stickyContent = [].slice.call(document.querySelectorAll(Selector.STICKY_CONTENT))\n\n // Adjust fixed content padding\n $(fixedContent).each((index, element) => {\n const actualPadding = element.style.paddingRight\n const calculatedPadding = $(element).css('padding-right')\n $(element)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n })\n\n // Adjust sticky content margin\n $(stickyContent).each((index, element) => {\n const actualMargin = element.style.marginRight\n const calculatedMargin = $(element).css('margin-right')\n $(element)\n .data('margin-right', actualMargin)\n .css('margin-right', `${parseFloat(calculatedMargin) - this._scrollbarWidth}px`)\n })\n\n // Adjust body padding\n const actualPadding = document.body.style.paddingRight\n const calculatedPadding = $(document.body).css('padding-right')\n $(document.body)\n .data('padding-right', actualPadding)\n .css('padding-right', `${parseFloat(calculatedPadding) + this._scrollbarWidth}px`)\n }\n\n $(document.body).addClass(ClassName.OPEN)\n }\n\n _resetScrollbar() {\n // Restore fixed content padding\n const fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT))\n $(fixedContent).each((index, element) => {\n const padding = $(element).data('padding-right')\n $(element).removeData('padding-right')\n element.style.paddingRight = padding ? padding : ''\n })\n\n // Restore sticky content\n const elements = [].slice.call(document.querySelectorAll(`${Selector.STICKY_CONTENT}`))\n $(elements).each((index, element) => {\n const margin = $(element).data('margin-right')\n if (typeof margin !== 'undefined') {\n $(element).css('margin-right', margin).removeData('margin-right')\n }\n })\n\n // Restore body padding\n const padding = $(document.body).data('padding-right')\n $(document.body).removeData('padding-right')\n document.body.style.paddingRight = padding ? padding : ''\n }\n\n _getScrollbarWidth() { // thx d.walsh\n const scrollDiv = document.createElement('div')\n scrollDiv.className = ClassName.SCROLLBAR_MEASURER\n document.body.appendChild(scrollDiv)\n const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth\n document.body.removeChild(scrollDiv)\n return scrollbarWidth\n }\n\n // Static\n\n static _jQueryInterface(config, relatedTarget) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = {\n ...Default,\n ...$(this).data(),\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (!data) {\n data = new Modal(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config](relatedTarget)\n } else if (_config.show) {\n data.show(relatedTarget)\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * Data Api implementation\n * ------------------------------------------------------------------------\n */\n\n$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {\n let target\n const selector = Util.getSelectorFromElement(this)\n\n if (selector) {\n target = document.querySelector(selector)\n }\n\n const config = $(target).data(DATA_KEY)\n ? 'toggle' : {\n ...$(target).data(),\n ...$(this).data()\n }\n\n if (this.tagName === 'A' || this.tagName === 'AREA') {\n event.preventDefault()\n }\n\n const $target = $(target).one(Event.SHOW, (showEvent) => {\n if (showEvent.isDefaultPrevented()) {\n // Only register focus restorer if modal will actually get shown\n return\n }\n\n $target.one(Event.HIDDEN, () => {\n if ($(this).is(':visible')) {\n this.focus()\n }\n })\n })\n\n Modal._jQueryInterface.call($(target), config, this)\n})\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Modal._jQueryInterface\n$.fn[NAME].Constructor = Modal\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Modal._jQueryInterface\n}\n\nexport default Modal\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): tooltip.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Popper from 'popper.js'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'tooltip'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.tooltip'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst CLASS_PREFIX = 'bs-tooltip'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\n\nconst DefaultType = {\n animation : 'boolean',\n template : 'string',\n title : '(string|element|function)',\n trigger : 'string',\n delay : '(number|object)',\n html : 'boolean',\n selector : '(string|boolean)',\n placement : '(string|function)',\n offset : '(number|string)',\n container : '(string|element|boolean)',\n fallbackPlacement : '(string|array)',\n boundary : '(string|element)'\n}\n\nconst AttachmentMap = {\n AUTO : 'auto',\n TOP : 'top',\n RIGHT : 'right',\n BOTTOM : 'bottom',\n LEFT : 'left'\n}\n\nconst Default = {\n animation : true,\n template : '
' +\n '
' +\n '
',\n trigger : 'hover focus',\n title : '',\n delay : 0,\n html : false,\n selector : false,\n placement : 'top',\n offset : 0,\n container : false,\n fallbackPlacement : 'flip',\n boundary : 'scrollParent'\n}\n\nconst HoverState = {\n SHOW : 'show',\n OUT : 'out'\n}\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n INSERTED : `inserted${EVENT_KEY}`,\n CLICK : `click${EVENT_KEY}`,\n FOCUSIN : `focusin${EVENT_KEY}`,\n FOCUSOUT : `focusout${EVENT_KEY}`,\n MOUSEENTER : `mouseenter${EVENT_KEY}`,\n MOUSELEAVE : `mouseleave${EVENT_KEY}`\n}\n\nconst ClassName = {\n FADE : 'fade',\n SHOW : 'show'\n}\n\nconst Selector = {\n TOOLTIP : '.tooltip',\n TOOLTIP_INNER : '.tooltip-inner',\n ARROW : '.arrow'\n}\n\nconst Trigger = {\n HOVER : 'hover',\n FOCUS : 'focus',\n CLICK : 'click',\n MANUAL : 'manual'\n}\n\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Tooltip {\n constructor(element, config) {\n /**\n * Check for Popper dependency\n * Popper - https://popper.js.org\n */\n if (typeof Popper === 'undefined') {\n throw new TypeError('Bootstrap\\'s tooltips require Popper.js (https://popper.js.org/)')\n }\n\n // private\n this._isEnabled = true\n this._timeout = 0\n this._hoverState = ''\n this._activeTrigger = {}\n this._popper = null\n\n // Protected\n this.element = element\n this.config = this._getConfig(config)\n this.tip = null\n\n this._setListeners()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get DATA_KEY() {\n return DATA_KEY\n }\n\n static get Event() {\n return Event\n }\n\n static get EVENT_KEY() {\n return EVENT_KEY\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Public\n\n enable() {\n this._isEnabled = true\n }\n\n disable() {\n this._isEnabled = false\n }\n\n toggleEnabled() {\n this._isEnabled = !this._isEnabled\n }\n\n toggle(event) {\n if (!this._isEnabled) {\n return\n }\n\n if (event) {\n const dataKey = this.constructor.DATA_KEY\n let context = $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n context._activeTrigger.click = !context._activeTrigger.click\n\n if (context._isWithActiveTrigger()) {\n context._enter(null, context)\n } else {\n context._leave(null, context)\n }\n } else {\n if ($(this.getTipElement()).hasClass(ClassName.SHOW)) {\n this._leave(null, this)\n return\n }\n\n this._enter(null, this)\n }\n }\n\n dispose() {\n clearTimeout(this._timeout)\n\n $.removeData(this.element, this.constructor.DATA_KEY)\n\n $(this.element).off(this.constructor.EVENT_KEY)\n $(this.element).closest('.modal').off('hide.bs.modal')\n\n if (this.tip) {\n $(this.tip).remove()\n }\n\n this._isEnabled = null\n this._timeout = null\n this._hoverState = null\n this._activeTrigger = null\n if (this._popper !== null) {\n this._popper.destroy()\n }\n\n this._popper = null\n this.element = null\n this.config = null\n this.tip = null\n }\n\n show() {\n if ($(this.element).css('display') === 'none') {\n throw new Error('Please use show on visible elements')\n }\n\n const showEvent = $.Event(this.constructor.Event.SHOW)\n if (this.isWithContent() && this._isEnabled) {\n $(this.element).trigger(showEvent)\n\n const shadowRoot = Util.findShadowRoot(this.element)\n const isInTheDom = $.contains(\n shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement,\n this.element\n )\n\n if (showEvent.isDefaultPrevented() || !isInTheDom) {\n return\n }\n\n const tip = this.getTipElement()\n const tipId = Util.getUID(this.constructor.NAME)\n\n tip.setAttribute('id', tipId)\n this.element.setAttribute('aria-describedby', tipId)\n\n this.setContent()\n\n if (this.config.animation) {\n $(tip).addClass(ClassName.FADE)\n }\n\n const placement = typeof this.config.placement === 'function'\n ? this.config.placement.call(this, tip, this.element)\n : this.config.placement\n\n const attachment = this._getAttachment(placement)\n this.addAttachmentClass(attachment)\n\n const container = this._getContainer()\n $(tip).data(this.constructor.DATA_KEY, this)\n\n if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {\n $(tip).appendTo(container)\n }\n\n $(this.element).trigger(this.constructor.Event.INSERTED)\n\n this._popper = new Popper(this.element, tip, {\n placement: attachment,\n modifiers: {\n offset: {\n offset: this.config.offset\n },\n flip: {\n behavior: this.config.fallbackPlacement\n },\n arrow: {\n element: Selector.ARROW\n },\n preventOverflow: {\n boundariesElement: this.config.boundary\n }\n },\n onCreate: (data) => {\n if (data.originalPlacement !== data.placement) {\n this._handlePopperPlacementChange(data)\n }\n },\n onUpdate: (data) => this._handlePopperPlacementChange(data)\n })\n\n $(tip).addClass(ClassName.SHOW)\n\n // If this is a touch-enabled device we add extra\n // empty mouseover listeners to the body's immediate children;\n // only needed because of broken event delegation on iOS\n // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().on('mouseover', null, $.noop)\n }\n\n const complete = () => {\n if (this.config.animation) {\n this._fixTransition()\n }\n const prevHoverState = this._hoverState\n this._hoverState = null\n\n $(this.element).trigger(this.constructor.Event.SHOWN)\n\n if (prevHoverState === HoverState.OUT) {\n this._leave(null, this)\n }\n }\n\n if ($(this.tip).hasClass(ClassName.FADE)) {\n const transitionDuration = Util.getTransitionDurationFromElement(this.tip)\n\n $(this.tip)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n complete()\n }\n }\n }\n\n hide(callback) {\n const tip = this.getTipElement()\n const hideEvent = $.Event(this.constructor.Event.HIDE)\n const complete = () => {\n if (this._hoverState !== HoverState.SHOW && tip.parentNode) {\n tip.parentNode.removeChild(tip)\n }\n\n this._cleanTipClass()\n this.element.removeAttribute('aria-describedby')\n $(this.element).trigger(this.constructor.Event.HIDDEN)\n if (this._popper !== null) {\n this._popper.destroy()\n }\n\n if (callback) {\n callback()\n }\n }\n\n $(this.element).trigger(hideEvent)\n\n if (hideEvent.isDefaultPrevented()) {\n return\n }\n\n $(tip).removeClass(ClassName.SHOW)\n\n // If this is a touch-enabled device we remove the extra\n // empty mouseover listeners we added for iOS support\n if ('ontouchstart' in document.documentElement) {\n $(document.body).children().off('mouseover', null, $.noop)\n }\n\n this._activeTrigger[Trigger.CLICK] = false\n this._activeTrigger[Trigger.FOCUS] = false\n this._activeTrigger[Trigger.HOVER] = false\n\n if ($(this.tip).hasClass(ClassName.FADE)) {\n const transitionDuration = Util.getTransitionDurationFromElement(tip)\n\n $(tip)\n .one(Util.TRANSITION_END, complete)\n .emulateTransitionEnd(transitionDuration)\n } else {\n complete()\n }\n\n this._hoverState = ''\n }\n\n update() {\n if (this._popper !== null) {\n this._popper.scheduleUpdate()\n }\n }\n\n // Protected\n\n isWithContent() {\n return Boolean(this.getTitle())\n }\n\n addAttachmentClass(attachment) {\n $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)\n }\n\n getTipElement() {\n this.tip = this.tip || $(this.config.template)[0]\n return this.tip\n }\n\n setContent() {\n const tip = this.getTipElement()\n this.setElementContent($(tip.querySelectorAll(Selector.TOOLTIP_INNER)), this.getTitle())\n $(tip).removeClass(`${ClassName.FADE} ${ClassName.SHOW}`)\n }\n\n setElementContent($element, content) {\n const html = this.config.html\n if (typeof content === 'object' && (content.nodeType || content.jquery)) {\n // Content is a DOM node or a jQuery\n if (html) {\n if (!$(content).parent().is($element)) {\n $element.empty().append(content)\n }\n } else {\n $element.text($(content).text())\n }\n } else {\n $element[html ? 'html' : 'text'](content)\n }\n }\n\n getTitle() {\n let title = this.element.getAttribute('data-original-title')\n\n if (!title) {\n title = typeof this.config.title === 'function'\n ? this.config.title.call(this.element)\n : this.config.title\n }\n\n return title\n }\n\n // Private\n\n _getContainer() {\n if (this.config.container === false) {\n return document.body\n }\n\n if (Util.isElement(this.config.container)) {\n return $(this.config.container)\n }\n\n return $(document).find(this.config.container)\n }\n\n _getAttachment(placement) {\n return AttachmentMap[placement.toUpperCase()]\n }\n\n _setListeners() {\n const triggers = this.config.trigger.split(' ')\n\n triggers.forEach((trigger) => {\n if (trigger === 'click') {\n $(this.element).on(\n this.constructor.Event.CLICK,\n this.config.selector,\n (event) => this.toggle(event)\n )\n } else if (trigger !== Trigger.MANUAL) {\n const eventIn = trigger === Trigger.HOVER\n ? this.constructor.Event.MOUSEENTER\n : this.constructor.Event.FOCUSIN\n const eventOut = trigger === Trigger.HOVER\n ? this.constructor.Event.MOUSELEAVE\n : this.constructor.Event.FOCUSOUT\n\n $(this.element)\n .on(\n eventIn,\n this.config.selector,\n (event) => this._enter(event)\n )\n .on(\n eventOut,\n this.config.selector,\n (event) => this._leave(event)\n )\n }\n })\n\n $(this.element).closest('.modal').on(\n 'hide.bs.modal',\n () => {\n if (this.element) {\n this.hide()\n }\n }\n )\n\n if (this.config.selector) {\n this.config = {\n ...this.config,\n trigger: 'manual',\n selector: ''\n }\n } else {\n this._fixTitle()\n }\n }\n\n _fixTitle() {\n const titleType = typeof this.element.getAttribute('data-original-title')\n\n if (this.element.getAttribute('title') || titleType !== 'string') {\n this.element.setAttribute(\n 'data-original-title',\n this.element.getAttribute('title') || ''\n )\n\n this.element.setAttribute('title', '')\n }\n }\n\n _enter(event, context) {\n const dataKey = this.constructor.DATA_KEY\n context = context || $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER\n ] = true\n }\n\n if ($(context.getTipElement()).hasClass(ClassName.SHOW) || context._hoverState === HoverState.SHOW) {\n context._hoverState = HoverState.SHOW\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HoverState.SHOW\n\n if (!context.config.delay || !context.config.delay.show) {\n context.show()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HoverState.SHOW) {\n context.show()\n }\n }, context.config.delay.show)\n }\n\n _leave(event, context) {\n const dataKey = this.constructor.DATA_KEY\n context = context || $(event.currentTarget).data(dataKey)\n\n if (!context) {\n context = new this.constructor(\n event.currentTarget,\n this._getDelegateConfig()\n )\n $(event.currentTarget).data(dataKey, context)\n }\n\n if (event) {\n context._activeTrigger[\n event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER\n ] = false\n }\n\n if (context._isWithActiveTrigger()) {\n return\n }\n\n clearTimeout(context._timeout)\n\n context._hoverState = HoverState.OUT\n\n if (!context.config.delay || !context.config.delay.hide) {\n context.hide()\n return\n }\n\n context._timeout = setTimeout(() => {\n if (context._hoverState === HoverState.OUT) {\n context.hide()\n }\n }, context.config.delay.hide)\n }\n\n _isWithActiveTrigger() {\n for (const trigger in this._activeTrigger) {\n if (this._activeTrigger[trigger]) {\n return true\n }\n }\n\n return false\n }\n\n _getConfig(config) {\n config = {\n ...this.constructor.Default,\n ...$(this.element).data(),\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (typeof config.delay === 'number') {\n config.delay = {\n show: config.delay,\n hide: config.delay\n }\n }\n\n if (typeof config.title === 'number') {\n config.title = config.title.toString()\n }\n\n if (typeof config.content === 'number') {\n config.content = config.content.toString()\n }\n\n Util.typeCheckConfig(\n NAME,\n config,\n this.constructor.DefaultType\n )\n\n return config\n }\n\n _getDelegateConfig() {\n const config = {}\n\n if (this.config) {\n for (const key in this.config) {\n if (this.constructor.Default[key] !== this.config[key]) {\n config[key] = this.config[key]\n }\n }\n }\n\n return config\n }\n\n _cleanTipClass() {\n const $tip = $(this.getTipElement())\n const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length) {\n $tip.removeClass(tabClass.join(''))\n }\n }\n\n _handlePopperPlacementChange(popperData) {\n const popperInstance = popperData.instance\n this.tip = popperInstance.popper\n this._cleanTipClass()\n this.addAttachmentClass(this._getAttachment(popperData.placement))\n }\n\n _fixTransition() {\n const tip = this.getTipElement()\n const initConfigAnimation = this.config.animation\n\n if (tip.getAttribute('x-placement') !== null) {\n return\n }\n\n $(tip).removeClass(ClassName.FADE)\n this.config.animation = false\n this.hide()\n this.show()\n this.config.animation = initConfigAnimation\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' && config\n\n if (!data && /dispose|hide/.test(config)) {\n return\n }\n\n if (!data) {\n data = new Tooltip(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Tooltip._jQueryInterface\n$.fn[NAME].Constructor = Tooltip\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Tooltip._jQueryInterface\n}\n\nexport default Tooltip\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): popover.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Tooltip from './tooltip'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'popover'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.popover'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\nconst CLASS_PREFIX = 'bs-popover'\nconst BSCLS_PREFIX_REGEX = new RegExp(`(^|\\\\s)${CLASS_PREFIX}\\\\S+`, 'g')\n\nconst Default = {\n ...Tooltip.Default,\n placement : 'right',\n trigger : 'click',\n content : '',\n template : '
' +\n '
' +\n '

' +\n '
'\n}\n\nconst DefaultType = {\n ...Tooltip.DefaultType,\n content : '(string|element|function)'\n}\n\nconst ClassName = {\n FADE : 'fade',\n SHOW : 'show'\n}\n\nconst Selector = {\n TITLE : '.popover-header',\n CONTENT : '.popover-body'\n}\n\nconst Event = {\n HIDE : `hide${EVENT_KEY}`,\n HIDDEN : `hidden${EVENT_KEY}`,\n SHOW : `show${EVENT_KEY}`,\n SHOWN : `shown${EVENT_KEY}`,\n INSERTED : `inserted${EVENT_KEY}`,\n CLICK : `click${EVENT_KEY}`,\n FOCUSIN : `focusin${EVENT_KEY}`,\n FOCUSOUT : `focusout${EVENT_KEY}`,\n MOUSEENTER : `mouseenter${EVENT_KEY}`,\n MOUSELEAVE : `mouseleave${EVENT_KEY}`\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass Popover extends Tooltip {\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n static get NAME() {\n return NAME\n }\n\n static get DATA_KEY() {\n return DATA_KEY\n }\n\n static get Event() {\n return Event\n }\n\n static get EVENT_KEY() {\n return EVENT_KEY\n }\n\n static get DefaultType() {\n return DefaultType\n }\n\n // Overrides\n\n isWithContent() {\n return this.getTitle() || this._getContent()\n }\n\n addAttachmentClass(attachment) {\n $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)\n }\n\n getTipElement() {\n this.tip = this.tip || $(this.config.template)[0]\n return this.tip\n }\n\n setContent() {\n const $tip = $(this.getTipElement())\n\n // We use append for html objects to maintain js events\n this.setElementContent($tip.find(Selector.TITLE), this.getTitle())\n let content = this._getContent()\n if (typeof content === 'function') {\n content = content.call(this.element)\n }\n this.setElementContent($tip.find(Selector.CONTENT), content)\n\n $tip.removeClass(`${ClassName.FADE} ${ClassName.SHOW}`)\n }\n\n // Private\n\n _getContent() {\n return this.element.getAttribute('data-content') ||\n this.config.content\n }\n\n _cleanTipClass() {\n const $tip = $(this.getTipElement())\n const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)\n if (tabClass !== null && tabClass.length > 0) {\n $tip.removeClass(tabClass.join(''))\n }\n }\n\n // Static\n\n static _jQueryInterface(config) {\n return this.each(function () {\n let data = $(this).data(DATA_KEY)\n const _config = typeof config === 'object' ? config : null\n\n if (!data && /dispose|hide/.test(config)) {\n return\n }\n\n if (!data) {\n data = new Popover(this, _config)\n $(this).data(DATA_KEY, data)\n }\n\n if (typeof config === 'string') {\n if (typeof data[config] === 'undefined') {\n throw new TypeError(`No method named \"${config}\"`)\n }\n data[config]()\n }\n })\n }\n}\n\n/**\n * ------------------------------------------------------------------------\n * jQuery\n * ------------------------------------------------------------------------\n */\n\n$.fn[NAME] = Popover._jQueryInterface\n$.fn[NAME].Constructor = Popover\n$.fn[NAME].noConflict = () => {\n $.fn[NAME] = JQUERY_NO_CONFLICT\n return Popover._jQueryInterface\n}\n\nexport default Popover\n","/**\n * --------------------------------------------------------------------------\n * Bootstrap (v4.2.1): scrollspy.js\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n * --------------------------------------------------------------------------\n */\n\nimport $ from 'jquery'\nimport Util from './util'\n\n/**\n * ------------------------------------------------------------------------\n * Constants\n * ------------------------------------------------------------------------\n */\n\nconst NAME = 'scrollspy'\nconst VERSION = '4.2.1'\nconst DATA_KEY = 'bs.scrollspy'\nconst EVENT_KEY = `.${DATA_KEY}`\nconst DATA_API_KEY = '.data-api'\nconst JQUERY_NO_CONFLICT = $.fn[NAME]\n\nconst Default = {\n offset : 10,\n method : 'auto',\n target : ''\n}\n\nconst DefaultType = {\n offset : 'number',\n method : 'string',\n target : '(string|element)'\n}\n\nconst Event = {\n ACTIVATE : `activate${EVENT_KEY}`,\n SCROLL : `scroll${EVENT_KEY}`,\n LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}`\n}\n\nconst ClassName = {\n DROPDOWN_ITEM : 'dropdown-item',\n DROPDOWN_MENU : 'dropdown-menu',\n ACTIVE : 'active'\n}\n\nconst Selector = {\n DATA_SPY : '[data-spy=\"scroll\"]',\n ACTIVE : '.active',\n NAV_LIST_GROUP : '.nav, .list-group',\n NAV_LINKS : '.nav-link',\n NAV_ITEMS : '.nav-item',\n LIST_ITEMS : '.list-group-item',\n DROPDOWN : '.dropdown',\n DROPDOWN_ITEMS : '.dropdown-item',\n DROPDOWN_TOGGLE : '.dropdown-toggle'\n}\n\nconst OffsetMethod = {\n OFFSET : 'offset',\n POSITION : 'position'\n}\n\n/**\n * ------------------------------------------------------------------------\n * Class Definition\n * ------------------------------------------------------------------------\n */\n\nclass ScrollSpy {\n constructor(element, config) {\n this._element = element\n this._scrollElement = element.tagName === 'BODY' ? window : element\n this._config = this._getConfig(config)\n this._selector = `${this._config.target} ${Selector.NAV_LINKS},` +\n `${this._config.target} ${Selector.LIST_ITEMS},` +\n `${this._config.target} ${Selector.DROPDOWN_ITEMS}`\n this._offsets = []\n this._targets = []\n this._activeTarget = null\n this._scrollHeight = 0\n\n $(this._scrollElement).on(Event.SCROLL, (event) => this._process(event))\n\n this.refresh()\n this._process()\n }\n\n // Getters\n\n static get VERSION() {\n return VERSION\n }\n\n static get Default() {\n return Default\n }\n\n // Public\n\n refresh() {\n const autoMethod = this._scrollElement === this._scrollElement.window\n ? OffsetMethod.OFFSET : OffsetMethod.POSITION\n\n const offsetMethod = this._config.method === 'auto'\n ? autoMethod : this._config.method\n\n const offsetBase = offsetMethod === OffsetMethod.POSITION\n ? this._getScrollTop() : 0\n\n this._offsets = []\n this._targets = []\n\n this._scrollHeight = this._getScrollHeight()\n\n const targets = [].slice.call(document.querySelectorAll(this._selector))\n\n targets\n .map((element) => {\n let target\n const targetSelector = Util.getSelectorFromElement(element)\n\n if (targetSelector) {\n target = document.querySelector(targetSelector)\n }\n\n if (target) {\n const targetBCR = target.getBoundingClientRect()\n if (targetBCR.width || targetBCR.height) {\n // TODO (fat): remove sketch reliance on jQuery position/offset\n return [\n $(target)[offsetMethod]().top + offsetBase,\n targetSelector\n ]\n }\n }\n return null\n })\n .filter((item) => item)\n .sort((a, b) => a[0] - b[0])\n .forEach((item) => {\n this._offsets.push(item[0])\n this._targets.push(item[1])\n })\n }\n\n dispose() {\n $.removeData(this._element, DATA_KEY)\n $(this._scrollElement).off(EVENT_KEY)\n\n this._element = null\n this._scrollElement = null\n this._config = null\n this._selector = null\n this._offsets = null\n this._targets = null\n this._activeTarget = null\n this._scrollHeight = null\n }\n\n // Private\n\n _getConfig(config) {\n config = {\n ...Default,\n ...typeof config === 'object' && config ? config : {}\n }\n\n if (typeof config.target !== 'string') {\n let id = $(config.target).attr('id')\n if (!id) {\n id = Util.getUID(NAME)\n $(config.target).attr('id', id)\n }\n config.target = `#${id}`\n }\n\n Util.typeCheckConfig(NAME, config, DefaultType)\n\n return config\n }\n\n _getScrollTop() {\n return this._scrollElement === window\n ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop\n }\n\n _getScrollHeight() {\n return this._scrollElement.scrollHeight || Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight\n )\n }\n\n _getOffsetHeight() {\n return this._scrollElement === window\n ? window.innerHeight : this._scrollElement.getBoundingClientRect().height\n }\n\n _process() {\n const scrollTop = this._getScrollTop() + this._config.offset\n const scrollHeight = this._getScrollHeight()\n const maxScroll = this._config.offset +\n scrollHeight -\n this._getOffsetHeight()\n\n if (this._scrollHeight !== scrollHeight) {\n this.refresh()\n }\n\n if (scrollTop >= maxScroll) {\n const target = this._targets[this._targets.length - 1]\n\n if (this._activeTarget !== target) {\n this._activate(target)\n }\n return\n }\n\n if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {\n this._activeTarget = null\n this._clear()\n return\n }\n\n const offsetLength = this._offsets.length\n for (let i = offsetLength; i--;) {\n const isActiveTarget = this._activeTarget !== this._targets[i] &&\n scrollTop >= this._offsets[i] &&\n (typeof this._offsets[i + 1] === 'undefined' ||\n scrollTop < this._offsets[i + 1])\n\n if (isActiveTarget) {\n this._activate(this._targets[i])\n }\n }\n }\n\n _activate(target) {\n this._activeTarget = target\n\n this._clear()\n\n const queries = this._selector\n .split(',')\n .map((selector) => `${selector}[data-target=\"${target}\"],${selector}[href=\"${target}\"]`)\n\n const $link = $([].slice.call(document.querySelectorAll(queries.join(','))))\n\n if ($link.hasClass(ClassName.DROPDOWN_ITEM)) {\n $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE)\n $link.addClass(ClassName.ACTIVE)\n } else {\n // Set triggered link as active\n $link.addClass(ClassName.ACTIVE)\n // Set triggered links parents as active\n // With both
    and