Delivery in LibraryLoader - added new Delivery loader

Extracted methods from existing Delivery Action on FTrack
Fixed Delivery Action for sequences
This commit is contained in:
Petr Kalis 2021-05-20 15:37:04 +02:00
parent 5c8a1a0c94
commit 3e2a16af94
4 changed files with 659 additions and 220 deletions

296
openpype/lib/delivery.py Normal file
View file

@ -0,0 +1,296 @@
"""Functions useful for delivery action or loader"""
import os
from avalon import pipeline
from avalon.vendor import filelink
import shutil
import clique
import collections
def sizeof_fmt(num, suffix='B'):
"""Returns formatted string with size in appropriate unit"""
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Yi', suffix)
def path_from_represenation(representation, anatomy):
try:
template = representation["data"]["template"]
except KeyError:
return None
try:
context = representation["context"]
context["root"] = anatomy.roots
path = pipeline.format_template_with_optional_keys(
context, template
)
except KeyError:
# Template references unavailable data
return None
return os.path.normpath(path)
def copy_file(src_path, dst_path):
"""Hardlink file if possible(to save space), copy if not"""
if os.path.exists(dst_path):
return
try:
filelink.create(
src_path,
dst_path,
filelink.HARDLINK
)
except OSError:
shutil.copyfile(src_path, dst_path)
def get_format_dict(anatomy, location_path):
"""Returns replaced root values from user provider value.
Args:
anatomy (Anatomy)
location_path (str): user provided value
Returns:
(dict): prepared for formatting of a template
"""
format_dict = {}
if location_path:
location_path = location_path.replace("\\", "/")
root_names = anatomy.root_names_from_templates(
anatomy.templates["delivery"]
)
if root_names is None:
format_dict["root"] = location_path
else:
format_dict["root"] = {}
for name in root_names:
format_dict["root"][name] = location_path
return format_dict
def check_destination_path(repre_id,
anatomy, anatomy_data,
datetime_data, template_name):
""" Try to create destination path based on 'template_name'.
In the case that path cannot be filled, template contains unmatched
keys, provide error message to filter out repre later.
Args:
anatomy (Anatomy)
anatomy_data (dict): context to fill anatomy
datetime_data (dict): values with actual date
template_name (str): to pick correct delivery template
Returns:
(collections.defauldict): {"TYPE_OF_ERROR":"ERROR_DETAIL"}
"""
anatomy_data.update(datetime_data)
anatomy_filled = anatomy.format_all(anatomy_data)
dest_path = anatomy_filled["delivery"][template_name]
report_items = collections.defaultdict(list)
sub_msg = None
if not dest_path.solved:
msg = (
"Missing keys in Representation's context"
" for anatomy template \"{}\"."
).format(template_name)
if dest_path.missing_keys:
keys = ", ".join(dest_path.missing_keys)
sub_msg = (
"Representation: {}<br>- Missing keys: \"{}\"<br>"
).format(repre_id, keys)
if dest_path.invalid_types:
items = []
for key, value in dest_path.invalid_types.items():
items.append("\"{}\" {}".format(key, str(value)))
keys = ", ".join(items)
sub_msg = (
"Representation: {}<br>"
"- Invalid value DataType: \"{}\"<br>"
).format(repre_id, keys)
report_items[msg].append(sub_msg)
return report_items
def process_single_file(
src_path, _repre, anatomy, template_name, anatomy_data, format_dict,
report_items, log
):
"""Copy single file to calculated path based on template
Args:
src_path(str): path of source representation file
_repre (dict): full repre, used only in process_sequence, here only
as to share same signature
anatomy (Anatomy)
template_name (string): user selected delivery template name
anatomy_data (dict): data from repre to fill anatomy with
format_dict (dict): root dictionary with names and values
report_items (collections.defaultdict): to return error messages
log (Logger): for log printing
Returns:
(collections.defaultdict , int)
"""
anatomy_filled = anatomy.format(anatomy_data)
if format_dict:
template_result = anatomy_filled["delivery"][template_name]
delivery_path = template_result.rootless.format(**format_dict)
else:
delivery_path = anatomy_filled["delivery"][template_name]
delivery_folder = os.path.dirname(delivery_path)
if not os.path.exists(delivery_folder):
os.makedirs(delivery_folder)
log.debug("Copying single: {} -> {}".format(src_path, delivery_path))
print("Copying single: {} -> {}".format(src_path, delivery_path))
copy_file(src_path, delivery_path)
return report_items, 1
def process_sequence(
src_path, repre, anatomy, template_name, anatomy_data, format_dict,
report_items, log
):
""" For Pype2(mainly - works in 3 too) where representation might not
contain files.
Uses listing physical files (not 'files' on repre as a)might not be
present, b)might not be reliable for representation and copying them.
TODO Should be refactored when files are sufficient to drive all
representations.
Args:
src_path(str): path of source representation file
repre (dict): full representation
anatomy (Anatomy)
template_name (string): user selected delivery template name
anatomy_data (dict): data from repre to fill anatomy with
format_dict (dict): root dictionary with names and values
report_items (collections.defaultdict): to return error messages
log (Logger): for log printing
Returns:
(collections.defaultdict , int)
"""
dir_path, file_name = os.path.split(str(src_path))
context = repre["context"]
ext = context.get("ext", context.get("representation"))
if not ext:
msg = "Source extension not found, cannot find collection"
report_items[msg].append(src_path)
log.warning("{} <{}>".format(msg, context))
return report_items, 0
ext = "." + ext
src_collections, remainder = clique.assemble(os.listdir(dir_path))
src_collection = None
for col in src_collections:
if col.tail != ext:
continue
src_collection = col
break
if src_collection is None:
msg = "Source collection of files was not found"
report_items[msg].append(src_path)
log.warning("{} <{}>".format(msg, src_path))
return report_items, 0
frame_indicator = "@####@"
anatomy_data["frame"] = frame_indicator
anatomy_filled = anatomy.format(anatomy_data)
if format_dict:
template_result = anatomy_filled["delivery"][template_name]
delivery_path = template_result.rootless.format(**format_dict)
else:
delivery_path = anatomy_filled["delivery"][template_name]
delivery_folder = os.path.dirname(delivery_path)
dst_head, dst_tail = delivery_path.split(frame_indicator)
dst_padding = src_collection.padding
dst_collection = clique.Collection(
head=dst_head,
tail=dst_tail,
padding=dst_padding
)
if not os.path.exists(delivery_folder):
os.makedirs(delivery_folder)
src_head = src_collection.head
src_tail = src_collection.tail
uploaded = 0
for index in src_collection.indexes:
src_padding = src_collection.format("{padding}") % index
src_file_name = "{}{}{}".format(src_head, src_padding, src_tail)
src = os.path.normpath(
os.path.join(dir_path, src_file_name)
)
dst_padding = dst_collection.format("{padding}") % index
dst = "{}{}{}".format(dst_head, dst_padding, dst_tail)
log.debug("Copying single: {} -> {}".format(src, dst))
copy_file(src, dst)
uploaded += 1
return report_items, uploaded
def report(report_items):
"""Returns dict with final status of delivery (succes, fail etc.)."""
items = []
title = "Delivery report"
for msg, _items in report_items.items():
if not _items:
continue
if items:
items.append({"type": "label", "value": "---"})
items.append({
"type": "label",
"value": "# {}".format(msg)
})
if not isinstance(_items, (list, tuple)):
_items = [_items]
__items = []
for item in _items:
__items.append(str(item))
items.append({
"type": "label",
"value": '<p>{}</p>'.format("<br>".join(__items))
})
if not items:
return {
"success": True,
"message": "Delivery Finished"
}
return {
"items": items,
"title": title,
"success": False,
"message": "Delivery Finished"
}

View file

@ -1,18 +1,21 @@
import os
import copy
import json
import shutil
import collections
import clique
from bson.objectid import ObjectId
from avalon import pipeline
from avalon.vendor import filelink
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 openpype.lib.delivery import (
path_from_represenation,
get_format_dict,
check_destination_path,
process_single_file,
process_sequence,
report
)
from avalon.api import AvalonMongoDB
@ -450,18 +453,7 @@ class Delivery(BaseAction):
anatomy = Anatomy(project_name)
format_dict = {}
if location_path:
location_path = location_path.replace("\\", "/")
root_names = anatomy.root_names_from_templates(
anatomy.templates["delivery"]
)
if root_names is None:
format_dict["root"] = location_path
else:
format_dict["root"] = {}
for name in root_names:
format_dict["root"][name] = location_path
format_dict = get_format_dict(anatomy, location_path)
datetime_data = config.get_datetime_data()
for repre in repres_to_deliver:
@ -471,41 +463,14 @@ class Delivery(BaseAction):
debug_msg += " with published path {}.".format(source_path)
self.log.debug(debug_msg)
# Get destination repre path
anatomy_data = copy.deepcopy(repre["context"])
anatomy_data.update(datetime_data)
anatomy_filled = anatomy.format_all(anatomy_data)
test_path = anatomy_filled["delivery"][anatomy_name]
repre_report_items = check_destination_path(anatomy,
anatomy_data,
datetime_data,
anatomy_name)
if not test_path.solved:
msg = (
"Missing keys in Representation's context"
" for anatomy template \"{}\"."
).format(anatomy_name)
if test_path.missing_keys:
keys = ", ".join(test_path.missing_keys)
sub_msg = (
"Representation: {}<br>- Missing keys: \"{}\"<br>"
).format(str(repre["_id"]), keys)
if test_path.invalid_types:
items = []
for key, value in test_path.invalid_types.items():
items.append("\"{}\" {}".format(key, str(value)))
keys = ", ".join(items)
sub_msg = (
"Representation: {}<br>"
"- Invalid value DataType: \"{}\"<br>"
).format(str(repre["_id"]), keys)
report_items[msg].append(sub_msg)
self.log.warning(
"{} Representation: \"{}\" Filled: <{}>".format(
msg, str(repre["_id"]), str(test_path)
)
)
if repre_report_items:
report_items.update(repre_report_items)
continue
# Get source repre path
@ -514,187 +479,25 @@ class Delivery(BaseAction):
if frame:
repre["context"]["frame"] = len(str(frame)) * "#"
repre_path = self.path_from_represenation(repre, anatomy)
repre_path = path_from_represenation(repre, anatomy)
# TODO add backup solution where root of path from component
# is repalced with root
# is replaced with root
args = (
repre_path,
repre,
anatomy,
anatomy_name,
anatomy_data,
format_dict,
report_items
report_items,
self.log
)
if not frame:
self.process_single_file(*args)
process_single_file(*args)
else:
self.process_sequence(*args)
process_sequence(*args)
return self.report(report_items)
def process_single_file(
self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict,
report_items
):
anatomy_filled = anatomy.format(anatomy_data)
if format_dict:
template_result = anatomy_filled["delivery"][anatomy_name]
delivery_path = template_result.rootless.format(**format_dict)
else:
delivery_path = anatomy_filled["delivery"][anatomy_name]
delivery_folder = os.path.dirname(delivery_path)
if not os.path.exists(delivery_folder):
os.makedirs(delivery_folder)
self.copy_file(repre_path, delivery_path)
def process_sequence(
self, repre_path, anatomy, anatomy_name, anatomy_data, format_dict,
report_items
):
dir_path, file_name = os.path.split(str(repre_path))
base_name, ext = os.path.splitext(file_name)
file_name_items = None
if "#" in base_name:
file_name_items = [part for part in base_name.split("#") if part]
elif "%" in base_name:
file_name_items = base_name.split("%")
if not file_name_items:
msg = "Source file was not found"
report_items[msg].append(repre_path)
self.log.warning("{} <{}>".format(msg, repre_path))
return
src_collections, remainder = clique.assemble(os.listdir(dir_path))
src_collection = None
for col in src_collections:
if col.tail != ext:
continue
# skip if collection don't have same basename
if not col.head.startswith(file_name_items[0]):
continue
src_collection = col
break
if src_collection is None:
# TODO log error!
msg = "Source collection of files was not found"
report_items[msg].append(repre_path)
self.log.warning("{} <{}>".format(msg, repre_path))
return
frame_indicator = "@####@"
anatomy_data["frame"] = frame_indicator
anatomy_filled = anatomy.format(anatomy_data)
if format_dict:
template_result = anatomy_filled["delivery"][anatomy_name]
delivery_path = template_result.rootless.format(**format_dict)
else:
delivery_path = anatomy_filled["delivery"][anatomy_name]
delivery_folder = os.path.dirname(delivery_path)
dst_head, dst_tail = delivery_path.split(frame_indicator)
dst_padding = src_collection.padding
dst_collection = clique.Collection(
head=dst_head,
tail=dst_tail,
padding=dst_padding
)
if not os.path.exists(delivery_folder):
os.makedirs(delivery_folder)
src_head = src_collection.head
src_tail = src_collection.tail
for index in src_collection.indexes:
src_padding = src_collection.format("{padding}") % index
src_file_name = "{}{}{}".format(src_head, src_padding, src_tail)
src = os.path.normpath(
os.path.join(dir_path, src_file_name)
)
dst_padding = dst_collection.format("{padding}") % index
dst = "{}{}{}".format(dst_head, dst_padding, dst_tail)
self.copy_file(src, dst)
def path_from_represenation(self, representation, anatomy):
try:
template = representation["data"]["template"]
except KeyError:
return None
try:
context = representation["context"]
context["root"] = anatomy.roots
path = pipeline.format_template_with_optional_keys(
context, template
)
except KeyError:
# Template references unavailable data
return None
return os.path.normpath(path)
def copy_file(self, src_path, dst_path):
if os.path.exists(dst_path):
return
try:
filelink.create(
src_path,
dst_path,
filelink.HARDLINK
)
except OSError:
shutil.copyfile(src_path, dst_path)
def report(self, report_items):
items = []
title = "Delivery report"
for msg, _items in report_items.items():
if not _items:
continue
if items:
items.append({"type": "label", "value": "---"})
items.append({
"type": "label",
"value": "# {}".format(msg)
})
if not isinstance(_items, (list, tuple)):
_items = [_items]
__items = []
for item in _items:
__items.append(str(item))
items.append({
"type": "label",
"value": '<p>{}</p>'.format("<br>".join(__items))
})
if not items:
return {
"success": True,
"message": "Delivery Finished"
}
return {
"items": items,
"title": title,
"success": False,
"message": "Delivery Finished"
}
return report(report_items)
def register(session):

View file

@ -0,0 +1,309 @@
import collections
import os
import copy
from avalon import api, style
from avalon.vendor.Qt import QtWidgets, QtCore, QtGui
from avalon.api import AvalonMongoDB
from openpype.api import Anatomy, config
from openpype import resources
from openpype.api import get_anatomy_settings
from openpype.lib.delivery import (
sizeof_fmt,
path_from_represenation,
get_format_dict,
check_destination_path,
process_single_file,
process_sequence,
report
)
class Delivery(api.SubsetLoader):
"""Export selected versions to folder structure from Template"""
is_multiple_contexts_compatible = True
sequence_splitter = "__sequence_splitter__"
representations = ["*"]
families = ["*"]
# tool_names = ["library_loader"]
label = "Delivery Versions"
order = 35
icon = "upload"
color = "#d8d8d8"
def message(self, text):
msgBox = QtWidgets.QMessageBox()
msgBox.setText(text)
msgBox.setStyleSheet(style.load_stylesheet())
msgBox.setWindowFlags(
msgBox.windowFlags() | QtCore.Qt.FramelessWindowHint
)
msgBox.exec_()
def load(self, contexts, name=None, namespace=None, options=None):
try:
dialog = DeliveryOptionsDialog(contexts, self.log)
dialog.exec_()
except Exception:
self.log.error("Failed to deliver versions.", exc_info=True)
class DeliveryOptionsDialog(QtWidgets.QDialog):
"""Dialog to select template where to deliver selected representations."""
SIZE_W = 950
SIZE_H = 350
def __init__(self, contexts, log=None, parent=None):
super(DeliveryOptionsDialog, self).__init__(parent=parent)
self.project = contexts[0]["project"]["name"]
self._representations = None
self.log = log
self.currently_uploaded = 0
self.dbcon = AvalonMongoDB()
self.dbcon.Session["AVALON_PROJECT"] = self.project
self.dbcon.install()
self._set_representations(contexts)
self.setWindowTitle("OpenPype - Deliver versions")
icon = QtGui.QIcon(resources.pype_icon_filepath())
self.setWindowIcon(icon)
self.setWindowFlags(
QtCore.Qt.WindowCloseButtonHint |
QtCore.Qt.WindowMinimizeButtonHint
)
self.setStyleSheet(style.load_stylesheet())
self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H))
layout = QtWidgets.QVBoxLayout()
input_layout = QtWidgets.QFormLayout()
input_layout.setContentsMargins(10, 15, 5, 5)
dropdown = QtWidgets.QComboBox()
self.templates = self._get_templates(self.project)
for name, _ in self.templates.items():
dropdown.addItem(name)
template_label = QtWidgets.QLabel()
template_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
template_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
root_line_edit = QtWidgets.QLineEdit()
root_line_edit.setText("c:/PETR_TEST") # TEMP
repre_checkboxes_layout = QtWidgets.QFormLayout()
repre_checkboxes_layout.setContentsMargins(10, 5, 5, 20)
self._representation_checkboxes = {}
for repre in self._get_representation_names():
checkbox = QtWidgets.QCheckBox()
checkbox.setChecked(True)
self._representation_checkboxes[repre] = checkbox
checkbox.stateChanged.connect(self._update_selected_label)
repre_checkboxes_layout.addRow(repre, checkbox)
selected_label = QtWidgets.QLabel()
input_layout.addRow("Selected representations", selected_label)
input_layout.addRow("Delivery template", dropdown)
input_layout.addRow("Template value", template_label)
input_layout.addRow("Root", root_line_edit)
input_layout.addRow("Representations", repre_checkboxes_layout)
btn_delivery = QtWidgets.QPushButton("Deliver")
progress_bar = QtWidgets.QProgressBar(self)
progress_bar.setMinimum = 0
progress_bar.setMaximum = 100
progress_bar.hide()
text_area = QtWidgets.QTextEdit()
text_area.setReadOnly(True)
text_area.hide()
text_area.setMinimumHeight(100)
layout.addLayout(input_layout)
layout.addWidget(btn_delivery)
layout.addWidget(progress_bar)
layout.addWidget(text_area)
self.setLayout(layout)
self.selected_label = selected_label
self.template_label = template_label
self.dropdown = dropdown
self.root_line_edit = root_line_edit
self.progress_bar = progress_bar
self.text_area = text_area
self.btn_delivery = btn_delivery
self.files_selected, self.size_selected = \
self._get_counts(self._get_selected_repres())
self._update_selected_label()
self._update_template_value()
btn_delivery.clicked.connect(self.deliver)
dropdown.currentIndexChanged.connect(self._update_template_value)
def deliver(self):
"""Main method to loop through all selected representations"""
self.progress_bar.show()
self.btn_delivery.setEnabled(False)
# self.resize(self.width(), self.height() + 50)
report_items = collections.defaultdict(list)
selected_repres = self._get_selected_repres()
anatomy = Anatomy(self.project)
datetime_data = config.get_datetime_data()
template_name = self.dropdown.currentText()
format_dict = get_format_dict(anatomy, self.root_line_edit.text())
for repre in self._representations:
if repre["name"] not in selected_repres:
continue
repre_path = path_from_represenation(repre, anatomy)
if not os.path.exists(repre_path):
msg = "{} doesn't exist for {}".format(repre_path,
repre["_id"])
report_items["Source file was not found"].append(msg)
continue
anatomy_data = copy.deepcopy(repre["context"])
new_report_items = check_destination_path(str(repre["_id"]),
anatomy,
anatomy_data,
datetime_data,
template_name)
report_items.update(new_report_items)
if new_report_items:
continue
args = [
repre_path,
repre,
anatomy,
template_name,
anatomy_data,
format_dict,
report_items,
self.log
]
if repre.get("files"):
for repre_file in repre["files"]:
src_path = anatomy.fill_root(repre_file["path"])
args[0] = src_path
new_report_items, uploaded = process_single_file(*args)
report_items.update(new_report_items)
self._update_progress(uploaded)
else: # fallback for Pype2 and representations without files
frame = repre['context'].get('frame')
if frame:
repre["context"]["frame"] = len(str(frame)) * "#"
if not frame:
new_report_items, uploaded = process_single_file(*args)
else:
new_report_items, uploaded = process_sequence(*args)
report_items.update(new_report_items)
self._update_progress(uploaded)
self.text_area.setText(self._format_report(report(report_items),
report_items))
self.text_area.show()
self.resize(self.width(), self.height() + 125)
def _get_representation_names(self):
"""Get set of representation names for checkbox filtering."""
return set([repre["name"] for repre in self._representations])
def _get_templates(self, project_name):
"""Adds list of delivery templates from Anatomy to dropdown."""
settings = get_anatomy_settings(project_name)
templates = {}
for template_name, value in settings["templates"]["delivery"].items():
templates[template_name] = value
return templates
def _set_representations(self, contexts):
version_ids = [context["version"]["_id"] for context in contexts]
repres = list(self.dbcon.find({
"type": "representation",
"parent": {"$in": version_ids}
}))
self._representations = repres
def _get_counts(self, selected_repres=None):
"""Returns tuple of number of selected files and their size."""
files_selected = 0
size_selected = 0
for repre in self._representations:
if repre["name"] in selected_repres:
for repre_file in repre.get("files", []):
files_selected += 1
size_selected += repre_file["size"]
return files_selected, size_selected
def _prepare_label(self):
"""Provides text with no of selected files and their size."""
label = "{} files, size {}".format(self.files_selected,
sizeof_fmt(self.size_selected))
return label
def _get_selected_repres(self):
"""Returns list of representation names filtered from checkboxes."""
selected_repres = []
for repre_name, chckbox in self._representation_checkboxes.items():
if chckbox.isChecked():
selected_repres.append(repre_name)
return selected_repres
def _update_selected_label(self):
"""Updates label with list of number of selected files."""
selected_repres = self._get_selected_repres()
self.files_selected, self.size_selected = \
self._get_counts(selected_repres)
self.selected_label.setText(self._prepare_label())
def _update_template_value(self, _index=None):
"""Sets template value to label after selection in dropdown."""
name = self.dropdown.currentText()
template_value = self.templates.get(name)
if template_value:
self.template_label.setText(template_value)
def _update_progress(self, uploaded):
"""Update progress bar after each repre copied."""
self.currently_uploaded += uploaded
ratio = self.currently_uploaded / self.files_selected
self.progress_bar.setValue(ratio * self.progress_bar.maximum())
def _format_report(self, result, report_items):
"""Format final result and error details as html."""
txt = "<h2>{}</h2>".format(result["message"])
for header, data in report_items.items():
txt += "<h3>{}</h3>".format(header)
for item in data:
txt += "{}<br>".format(item)
return txt

31
vendor/debug_library_loader.py vendored Normal file
View file

@ -0,0 +1,31 @@
import os
import pyblish
import pyblish.cli
import pyblish.plugin
os.environ["AVALON_MONGO"] = "mongodb://localhost:27017"
os.environ["OPENPYPE_MONGO"] = "mongodb://localhost:27017"
os.environ["AVALON_DB"] = "avalon"
os.environ["OPENPYPE_DATABASE_NAME"] = "avalon"
os.environ["AVALON_TIMEOUT"] = '3000'
os.environ["OPENPYPE_DEBUG"] = "3"
os.environ["AVALON_CONFIG"] = "pype"
os.environ["AVALON_ASSET"] = "Jungle"
os.environ["AVALON_PROJECT"] = "petr_second"
from avalon.tools.libraryloader import show
import openpype
openpype.install()
# REGISTERED = pyblish.plugin.registered_paths()
# PACKAGEPATH = pyblish.lib.main_package_path()
# ENVIRONMENT = os.environ.get("PYBLISHPLUGINPATH", "")
# PLUGINPATH = os.path.join(PACKAGEPATH, '..', 'tests', 'plugins')
#
# REGISTERED.append("C:\\Users\\petrk\\PycharmProjects\\Pype3.0\\pype\\openpype\\plugins\\load")
# pyblish.plugin.deregister_all_paths()
# for path in REGISTERED:
# register_plugin_path(avalon.Loader, LOAD_PATH)
# pyblish.plugin.register_plugin_path(path)
show(debug=True, show_projects=True, show_libraries=True)