From 3e2a16af94b8538ceda2df531abc83fb7f0fb1e5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 20 May 2021 15:37:04 +0200 Subject: [PATCH] Delivery in LibraryLoader - added new Delivery loader Extracted methods from existing Delivery Action on FTrack Fixed Delivery Action for sequences --- openpype/lib/delivery.py | 296 +++++++++++++++++ .../event_handlers_user/action_delivery.py | 243 ++------------ openpype/plugins/load/delivery.py | 309 ++++++++++++++++++ vendor/debug_library_loader.py | 31 ++ 4 files changed, 659 insertions(+), 220 deletions(-) create mode 100644 openpype/lib/delivery.py create mode 100644 openpype/plugins/load/delivery.py create mode 100644 vendor/debug_library_loader.py diff --git a/openpype/lib/delivery.py b/openpype/lib/delivery.py new file mode 100644 index 0000000000..d5b18bca0a --- /dev/null +++ b/openpype/lib/delivery.py @@ -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: {}
- Missing keys: \"{}\"
" + ).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: {}
" + "- Invalid value DataType: \"{}\"
" + ).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": '

{}

'.format("
".join(__items)) + }) + + if not items: + return { + "success": True, + "message": "Delivery Finished" + } + + return { + "items": items, + "title": title, + "success": False, + "message": "Delivery Finished" + } diff --git a/openpype/modules/ftrack/event_handlers_user/action_delivery.py b/openpype/modules/ftrack/event_handlers_user/action_delivery.py index 88fdbe3669..1cf2a701d8 100644 --- a/openpype/modules/ftrack/event_handlers_user/action_delivery.py +++ b/openpype/modules/ftrack/event_handlers_user/action_delivery.py @@ -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: {}
- Missing keys: \"{}\"
" - ).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: {}
" - "- Invalid value DataType: \"{}\"
" - ).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": '

{}

'.format("
".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): diff --git a/openpype/plugins/load/delivery.py b/openpype/plugins/load/delivery.py new file mode 100644 index 0000000000..013ea73dd2 --- /dev/null +++ b/openpype/plugins/load/delivery.py @@ -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 = "

{}

".format(result["message"]) + for header, data in report_items.items(): + txt += "

{}

".format(header) + for item in data: + txt += "{}
".format(item) + + return txt diff --git a/vendor/debug_library_loader.py b/vendor/debug_library_loader.py new file mode 100644 index 0000000000..1bbd963f16 --- /dev/null +++ b/vendor/debug_library_loader.py @@ -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) \ No newline at end of file