diff --git a/openpype/hosts/flame/api/__init__.py b/openpype/hosts/flame/api/__init__.py index 8e5418c78b..56bbadd2fc 100644 --- a/openpype/hosts/flame/api/__init__.py +++ b/openpype/hosts/flame/api/__init__.py @@ -28,7 +28,8 @@ from .lib import ( get_reformated_filename, get_frame_from_filename, get_padding_from_filename, - maintained_object_duplication + maintained_object_duplication, + get_clip_segment ) from .utils import ( setup, @@ -52,7 +53,10 @@ from .menu import ( ) from .plugin import ( Creator, - PublishableClip + PublishableClip, + ClipLoader, + OpenClipSolver + ) from .workio import ( open_file, @@ -96,6 +100,7 @@ __all__ = [ "get_frame_from_filename", "get_padding_from_filename", "maintained_object_duplication", + "get_clip_segment", # pipeline "install", @@ -122,6 +127,8 @@ __all__ = [ # plugin "Creator", "PublishableClip", + "ClipLoader", + "OpenClipSolver", # workio "open_file", diff --git a/openpype/hosts/flame/api/lib.py b/openpype/hosts/flame/api/lib.py index f3c918caab..bbb7c38119 100644 --- a/openpype/hosts/flame/api/lib.py +++ b/openpype/hosts/flame/api/lib.py @@ -692,3 +692,18 @@ def maintained_object_duplication(item): finally: # delete the item at the end flame.delete(duplicate) + + +def get_clip_segment(flame_clip): + name = flame_clip.name.get_value() + version = flame_clip.versions[0] + track = version.tracks[0] + segments = track.segments + + if len(segments) < 1: + raise ValueError("Clip `{}` has no segments!".format(name)) + + if len(segments) > 1: + raise ValueError("Clip `{}` has too many segments!".format(name)) + + return segments[0] diff --git a/openpype/hosts/flame/api/pipeline.py b/openpype/hosts/flame/api/pipeline.py index 11380d4f58..af071439ef 100644 --- a/openpype/hosts/flame/api/pipeline.py +++ b/openpype/hosts/flame/api/pipeline.py @@ -4,6 +4,7 @@ Basic avalon integration import os import contextlib from avalon import api as avalon +from avalon.pipeline import AVALON_CONTAINER_ID from pyblish import api as pyblish from openpype.api import Logger from .lib import ( @@ -56,14 +57,31 @@ def uninstall(): log.info("OpenPype Flame host uninstalled ...") -def containerise(tl_segment, +def containerise(flame_clip_segment, name, namespace, context, loader=None, data=None): - # TODO: containerise - pass + + data_imprint = { + "schema": "openpype:container-2.0", + "id": AVALON_CONTAINER_ID, + "name": str(name), + "namespace": str(namespace), + "loader": str(loader), + "representation": str(context["representation"]["_id"]), + } + + if data: + for k, v in data.items(): + data_imprint[k] = v + + log.debug("_ data_imprint: {}".format(data_imprint)) + + set_segment_data_marker(flame_clip_segment, data_imprint) + + return True def ls(): diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index f34999bcf3..db1793cba8 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -1,7 +1,14 @@ +import os import re +import shutil +import sys +from avalon.vendor import qargparse +from xml.etree import ElementTree as ET +import six from Qt import QtWidgets, QtCore import openpype.api as openpype from openpype import style +import avalon.api as avalon from . import ( lib as flib, pipeline as fpipeline, @@ -644,3 +651,274 @@ class PublishableClip: # Publishing plugin functions # Loader plugin functions + +class ClipLoader(avalon.Loader): + """A basic clip loader for Flame + + This will implement the basic behavior for a loader to inherit from that + will containerize the reference and will implement the `remove` and + `update` logic. + + """ + + options = [ + qargparse.Boolean( + "handles", + label="Set handles", + default=0, + help="Also set handles to clip as In/Out marks" + ) + ] + + +class OpenClipSolver: + media_script_path = "/opt/Autodesk/mio/current/dl_get_media_info" + tmp_name = "_tmp.clip" + tmp_file = None + create_new_clip = False + + out_feed_nb_ticks = None + out_feed_fps = None + out_feed_drop_mode = None + + log = log + + def __init__(self, openclip_file_path, feed_data): + # test if media script paht exists + self._validate_media_script_path() + + # new feed variables: + feed_path = feed_data["path"] + self.feed_version_name = feed_data["version"] + self.feed_colorspace = feed_data.get("colorspace") + + if feed_data.get("logger"): + self.log = feed_data["logger"] + + # derivate other feed variables + self.feed_basename = os.path.basename(feed_path) + self.feed_dir = os.path.dirname(feed_path) + self.feed_ext = os.path.splitext(self.feed_basename)[1][1:].lower() + + if not os.path.isfile(openclip_file_path): + # openclip does not exist yet and will be created + self.tmp_file = self.out_file = openclip_file_path + self.create_new_clip = True + + else: + # output a temp file + self.out_file = openclip_file_path + self.tmp_file = os.path.join(self.feed_dir, self.tmp_name) + self._clear_tmp_file() + + self.log.info("Temp File: {}".format(self.tmp_file)) + + def make(self): + self._generate_media_info_file() + + if self.create_new_clip: + # New openClip + self._create_new_open_clip() + else: + self._update_open_clip() + + def _validate_media_script_path(self): + if not os.path.isfile(self.media_script_path): + raise IOError("Media Scirpt does not exist: `{}`".format( + self.media_script_path)) + + def _generate_media_info_file(self): + # Create cmd arguments for gettig xml file info file + cmd_args = [ + self.media_script_path, + "-e", self.feed_ext, + "-o", self.tmp_file, + self.feed_dir + ] + + # execute creation of clip xml template data + try: + openpype.run_subprocess(cmd_args) + except TypeError: + self.log.error("Error creating self.tmp_file") + six.reraise(*sys.exc_info()) + + def _clear_tmp_file(self): + if os.path.isfile(self.tmp_file): + os.remove(self.tmp_file) + + def _clear_handler(self, xml_object): + for handler in xml_object.findall("./handler"): + self.log.debug("Handler found") + xml_object.remove(handler) + + def _create_new_open_clip(self): + self.log.info("Building new openClip") + + tmp_xml = ET.parse(self.tmp_file) + + tmp_xml_feeds = tmp_xml.find('tracks/track/feeds') + tmp_xml_feeds.set('currentVersion', self.feed_version_name) + for tmp_feed in tmp_xml_feeds: + tmp_feed.set('vuid', self.feed_version_name) + + # add colorspace if any is set + if self.feed_colorspace: + self._add_colorspace(tmp_feed, self.feed_colorspace) + + self._clear_handler(tmp_feed) + + tmp_xml_versions_obj = tmp_xml.find('versions') + tmp_xml_versions_obj.set('currentVersion', self.feed_version_name) + for xml_new_version in tmp_xml_versions_obj: + xml_new_version.set('uid', self.feed_version_name) + xml_new_version.set('type', 'version') + + xml_data = self._fix_xml_data(tmp_xml) + self.log.info("Adding feed version: {}".format(self.feed_basename)) + + self._write_result_xml_to_file(xml_data) + + self.log.info("openClip Updated: {}".format(self.tmp_file)) + + def _update_open_clip(self): + self.log.info("Updating openClip ..") + + out_xml = ET.parse(self.out_file) + tmp_xml = ET.parse(self.tmp_file) + + self.log.debug(">> out_xml: {}".format(out_xml)) + self.log.debug(">> tmp_xml: {}".format(tmp_xml)) + + # Get new feed from tmp file + tmp_xml_feed = tmp_xml.find('tracks/track/feeds/feed') + + self._clear_handler(tmp_xml_feed) + self._get_time_info_from_origin(out_xml) + + if self.out_feed_fps: + tmp_feed_fps_obj = tmp_xml_feed.find( + "startTimecode/rate") + tmp_feed_fps_obj.text = self.out_feed_fps + if self.out_feed_nb_ticks: + tmp_feed_nb_ticks_obj = tmp_xml_feed.find( + "startTimecode/nbTicks") + tmp_feed_nb_ticks_obj.text = self.out_feed_nb_ticks + if self.out_feed_drop_mode: + tmp_feed_drop_mode_obj = tmp_xml_feed.find( + "startTimecode/dropMode") + tmp_feed_drop_mode_obj.text = self.out_feed_drop_mode + + new_path_obj = tmp_xml_feed.find( + "spans/span/path") + new_path = new_path_obj.text + + feed_added = False + if not self._feed_exists(out_xml, new_path): + tmp_xml_feed.set('vuid', self.feed_version_name) + # Append new temp file feed to .clip source out xml + out_track = out_xml.find("tracks/track") + # add colorspace if any is set + if self.feed_colorspace: + self._add_colorspace(tmp_xml_feed, self.feed_colorspace) + + out_feeds = out_track.find('feeds') + out_feeds.set('currentVersion', self.feed_version_name) + out_feeds.append(tmp_xml_feed) + + self.log.info( + "Appending new feed: {}".format( + self.feed_version_name)) + feed_added = True + + if feed_added: + # Append vUID to versions + out_xml_versions_obj = out_xml.find('versions') + out_xml_versions_obj.set( + 'currentVersion', self.feed_version_name) + new_version_obj = ET.Element( + "version", {"type": "version", "uid": self.feed_version_name}) + out_xml_versions_obj.insert(0, new_version_obj) + + xml_data = self._fix_xml_data(out_xml) + + # fist create backup + self._create_openclip_backup_file(self.out_file) + + self.log.info("Adding feed version: {}".format( + self.feed_version_name)) + + self._write_result_xml_to_file(xml_data) + + self.log.info("openClip Updated: {}".format(self.out_file)) + + self._clear_tmp_file() + + def _get_time_info_from_origin(self, xml_data): + try: + for out_track in xml_data.iter('track'): + for out_feed in out_track.iter('feed'): + out_feed_nb_ticks_obj = out_feed.find( + 'startTimecode/nbTicks') + self.out_feed_nb_ticks = out_feed_nb_ticks_obj.text + out_feed_fps_obj = out_feed.find( + 'startTimecode/rate') + self.out_feed_fps = out_feed_fps_obj.text + out_feed_drop_mode_obj = out_feed.find( + 'startTimecode/dropMode') + self.out_feed_drop_mode = out_feed_drop_mode_obj.text + break + else: + continue + except Exception as msg: + self.log.warning(msg) + + def _feed_exists(self, xml_data, path): + # loop all available feed paths and check if + # the path is not already in file + for src_path in xml_data.iter('path'): + if path == src_path.text: + self.log.warning( + "Not appending file as it already is in .clip file") + return True + + def _fix_xml_data(self, xml_data): + xml_root = xml_data.getroot() + self._clear_handler(xml_root) + return ET.tostring(xml_root).decode('utf-8') + + def _write_result_xml_to_file(self, xml_data): + with open(self.out_file, "w") as f: + f.write(xml_data) + + def _create_openclip_backup_file(self, file): + bck_file = "{}.bak".format(file) + # if backup does not exist + if not os.path.isfile(bck_file): + shutil.copy2(file, bck_file) + else: + # in case it exists and is already multiplied + created = False + for _i in range(1, 99): + bck_file = "{name}.bak.{idx:0>2}".format( + name=file, + idx=_i) + # create numbered backup file + if not os.path.isfile(bck_file): + shutil.copy2(file, bck_file) + created = True + break + # in case numbered does not exists + if not created: + bck_file = "{}.bak.last".format(file) + shutil.copy2(file, bck_file) + + def _add_colorspace(self, feed_obj, profile_name): + feed_storage_obj = feed_obj.find("storageFormat") + feed_clr_obj = feed_storage_obj.find("colourSpace") + if feed_clr_obj is not None: + feed_clr_obj = ET.Element( + "colourSpace", {"type": "string"}) + feed_storage_obj.append(feed_clr_obj) + + feed_clr_obj.text = profile_name diff --git a/openpype/hosts/flame/api/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py b/openpype/hosts/flame/api/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py index 688b8b6ae3..5a72706ba1 100644 --- a/openpype/hosts/flame/api/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py +++ b/openpype/hosts/flame/api/utility_scripts/openpype_flame_to_ftrack/openpype_flame_to_ftrack.py @@ -3,6 +3,10 @@ from __future__ import print_function import os import sys +# only testing dependency for nested modules in package +import six # noqa + + SCRIPT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.join(SCRIPT_DIR, "modules") sys.path.append(PACKAGE_DIR) diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py new file mode 100644 index 0000000000..8ba01d6937 --- /dev/null +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -0,0 +1,247 @@ +import os +import flame +from pprint import pformat +import openpype.hosts.flame.api as opfapi + + +class LoadClip(opfapi.ClipLoader): + """Load a subset to timeline as clip + + Place clip to timeline on its asset origin timings collected + during conforming to project + """ + + families = ["render2d", "source", "plate", "render", "review"] + representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + + label = "Load as clip" + order = -10 + icon = "code-fork" + color = "orange" + + # settings + reel_group_name = "OpenPype_Reels" + reel_name = "Loaded" + clip_name_template = "{asset}_{subset}_{representation}" + + def load(self, context, name, namespace, options): + + # get flame objects + fproject = flame.project.current_project + self.fpd = fproject.current_workspace.desktop + + # load clip to timeline and get main variables + namespace = namespace + version = context['version'] + version_data = version.get("data", {}) + version_name = version.get("name", None) + colorspace = version_data.get("colorspace", None) + clip_name = self.clip_name_template.format( + **context["representation"]["context"]) + + # todo: settings in imageio + # convert colorspace with ocio to flame mapping + # in imageio flame section + colorspace = colorspace + + # create workfile path + workfile_dir = os.environ["AVALON_WORKDIR"] + openclip_dir = os.path.join( + workfile_dir, clip_name + ) + openclip_path = os.path.join( + openclip_dir, clip_name + ".clip" + ) + if not os.path.exists(openclip_dir): + os.makedirs(openclip_dir) + + # prepare clip data from context ad send it to openClipLoader + loading_context = { + "path": self.fname.replace("\\", "/"), + "colorspace": colorspace, + "version": "v{:0>3}".format(version_name), + "logger": self.log + + } + self.log.debug(pformat( + loading_context + )) + self.log.debug(openclip_path) + + # make openpype clip file + opfapi.OpenClipSolver(openclip_path, loading_context).make() + + # prepare Reel group in actual desktop + opc = self._get_clip( + clip_name, + openclip_path + ) + + # add additional metadata from the version to imprint Avalon knob + add_keys = [ + "frameStart", "frameEnd", "source", "author", + "fps", "handleStart", "handleEnd" + ] + + # move all version data keys to tag data + data_imprint = {} + for key in add_keys: + data_imprint.update({ + key: version_data.get(key, str(None)) + }) + + # add variables related to version context + data_imprint.update({ + "version": version_name, + "colorspace": colorspace, + "objectName": clip_name + }) + + # TODO: finish the containerisation + # opc_segment = opfapi.get_clip_segment(opc) + + # return opfapi.containerise( + # opc_segment, + # name, namespace, context, + # self.__class__.__name__, + # data_imprint) + + return opc + + def _get_clip(self, name, clip_path): + reel = self._get_reel() + # with maintained openclip as opc + matching_clip = [cl for cl in reel.clips + if cl.name.get_value() == name] + if matching_clip: + return matching_clip.pop() + else: + created_clips = flame.import_clips(str(clip_path), reel) + return created_clips.pop() + + def _get_reel(self): + + matching_rgroup = [ + rg for rg in self.fpd.reel_groups + if rg.name.get_value() == self.reel_group_name + ] + + if not matching_rgroup: + reel_group = self.fpd.create_reel_group(str(self.reel_group_name)) + for _r in reel_group.reels: + if "reel" not in _r.name.get_value().lower(): + continue + self.log.debug("Removing: {}".format(_r.name)) + flame.delete(_r) + else: + reel_group = matching_rgroup.pop() + + matching_reel = [ + re for re in reel_group.reels + if re.name.get_value() == self.reel_name + ] + + if not matching_reel: + reel_group = reel_group.create_reel(str(self.reel_name)) + else: + reel_group = matching_reel.pop() + + return reel_group + + def _get_segment_from_clip(self, clip): + # unwrapping segment from input clip + pass + + # def switch(self, container, representation): + # self.update(container, representation) + + # def update(self, container, representation): + # """ Updating previously loaded clips + # """ + + # # load clip to timeline and get main variables + # name = container['name'] + # namespace = container['namespace'] + # track_item = phiero.get_track_items( + # track_item_name=namespace) + # version = io.find_one({ + # "type": "version", + # "_id": representation["parent"] + # }) + # version_data = version.get("data", {}) + # version_name = version.get("name", None) + # colorspace = version_data.get("colorspace", None) + # object_name = "{}_{}".format(name, namespace) + # file = api.get_representation_path(representation).replace("\\", "/") + # clip = track_item.source() + + # # reconnect media to new path + # clip.reconnectMedia(file) + + # # set colorspace + # if colorspace: + # clip.setSourceMediaColourTransform(colorspace) + + # # add additional metadata from the version to imprint Avalon knob + # add_keys = [ + # "frameStart", "frameEnd", "source", "author", + # "fps", "handleStart", "handleEnd" + # ] + + # # move all version data keys to tag data + # data_imprint = {} + # for key in add_keys: + # data_imprint.update({ + # key: version_data.get(key, str(None)) + # }) + + # # add variables related to version context + # data_imprint.update({ + # "representation": str(representation["_id"]), + # "version": version_name, + # "colorspace": colorspace, + # "objectName": object_name + # }) + + # # update color of clip regarding the version order + # self.set_item_color(track_item, version) + + # return phiero.update_container(track_item, data_imprint) + + # def remove(self, container): + # """ Removing previously loaded clips + # """ + # # load clip to timeline and get main variables + # namespace = container['namespace'] + # track_item = phiero.get_track_items( + # track_item_name=namespace) + # track = track_item.parent() + + # # remove track item from track + # track.removeItem(track_item) + + # @classmethod + # def multiselection(cls, track_item): + # if not cls.track: + # cls.track = track_item.parent() + # cls.sequence = cls.track.parent() + + # @classmethod + # def set_item_color(cls, track_item, version): + + # clip = track_item.source() + # # define version name + # version_name = version.get("name", None) + # # get all versions in list + # versions = io.find({ + # "type": "version", + # "parent": version["parent"] + # }).distinct('name') + + # max_version = max(versions) + + # # set clip colour + # if version_name == max_version: + # clip.binItem().setColor(cls.clip_color_last) + # else: + # clip.binItem().setColor(cls.clip_color) diff --git a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py index d1222f2492..db85bede85 100644 --- a/openpype/hosts/flame/plugins/publish/extract_subset_resources.py +++ b/openpype/hosts/flame/plugins/publish/extract_subset_resources.py @@ -22,6 +22,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "ext": "jpg", "xml_preset_file": "Jpeg (8-bit).xml", "xml_preset_dir": "", + "colorspace_out": "Output - sRGB", "representation_add_range": False, "representation_tags": ["thumbnail"] }, @@ -29,6 +30,7 @@ class ExtractSubsetResources(openpype.api.Extractor): "ext": "mov", "xml_preset_file": "Apple iPad (1920x1080).xml", "xml_preset_dir": "", + "colorspace_out": "Output - Rec.709", "representation_add_range": True, "representation_tags": [ "review", @@ -45,7 +47,6 @@ class ExtractSubsetResources(openpype.api.Extractor): export_presets_mapping = {} def process(self, instance): - if ( self.keep_original_representation and "representations" not in instance.data @@ -84,6 +85,7 @@ class ExtractSubsetResources(openpype.api.Extractor): preset_file = preset_config["xml_preset_file"] preset_dir = preset_config["xml_preset_dir"] repre_tags = preset_config["representation_tags"] + color_out = preset_config["colorspace_out"] # validate xml preset file is filled if preset_file == "": @@ -129,17 +131,31 @@ class ExtractSubsetResources(openpype.api.Extractor): opfapi.export_clip( export_dir_path, duplclip, preset_path, **kwargs) + extension = preset_config["ext"] # create representation data representation_data = { "name": unique_name, "outputName": unique_name, - "ext": preset_config["ext"], + "ext": extension, "stagingDir": export_dir_path, - "tags": repre_tags + "tags": repre_tags, + "data": { + "colorspace": color_out + } } + # collect all available content of export dir files = os.listdir(export_dir_path) + # make sure no nested folders inside + n_stage_dir, n_files = self._unfolds_nested_folders( + export_dir_path, files, extension) + + # fix representation in case of nested folders + if n_stage_dir: + representation_data["stagingDir"] = n_stage_dir + files = n_files + # add files to represetation but add # imagesequence as list if ( @@ -170,3 +186,63 @@ class ExtractSubsetResources(openpype.api.Extractor): self.log.debug("All representations: {}".format( pformat(instance.data["representations"]))) + + def _unfolds_nested_folders(self, stage_dir, files_list, ext): + """Unfolds nested folders + + Args: + stage_dir (str): path string with directory + files_list (list): list of file names + ext (str): extension (jpg)[without dot] + + Raises: + IOError: in case no files were collected form any directory + + Returns: + str, list: new staging dir path, new list of file names + or + None, None: In case single file in `files_list` + """ + # exclude single files which are having extension + # the same as input ext attr + if ( + # only one file in list + len(files_list) == 1 + # file is having extension as input + and ext in os.path.splitext(files_list[0])[-1] + ): + return None, None + elif ( + # more then one file in list + len(files_list) >= 1 + # extension is correct + and ext in os.path.splitext(files_list[0])[-1] + # test file exists + and os.path.exists( + os.path.join(stage_dir, files_list[0]) + ) + ): + return None, None + + new_stage_dir = None + new_files_list = [] + for file in files_list: + search_path = os.path.join(stage_dir, file) + if not os.path.isdir(search_path): + continue + for root, _dirs, files in os.walk(search_path): + for _file in files: + _fn, _ext = os.path.splitext(_file) + if ext.lower() != _ext[1:].lower(): + continue + new_files_list.append(_file) + if not new_stage_dir: + new_stage_dir = root + + if not new_stage_dir: + raise AssertionError( + "Files in `{}` are not correct! Check `{}`".format( + files_list, stage_dir) + ) + + return new_stage_dir, new_files_list diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index c81069ef5c..b601f9bcba 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -24,12 +24,38 @@ "export_presets_mapping": { "exr16fpdwaa": { "ext": "exr", - "xml_preset_dir": "", "xml_preset_file": "OpenEXR (16-bit fp DWAA).xml", + "xml_preset_dir": "", + "colorspace_out": "ACES - ACEScg", "representation_add_range": true, "representation_tags": [] } } } + }, + "load": { + "LoadClip": { + "enabled": true, + "families": [ + "render2d", + "source", + "plate", + "render", + "review" + ], + "representations": [ + "exr", + "dpx", + "jpg", + "jpeg", + "png", + "h264", + "mov", + "mp4" + ], + "reel_group_name": "OpenPype_Reels", + "reel_name": "Loaded", + "clip_name_template": "{asset}_{subset}_{representation}" + } } } \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 76576ebf73..9ef05fa832 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -166,6 +166,11 @@ "label": "XML preset folder (optional)", "type": "text" }, + { + "key": "colorspace_out", + "label": "Output color (imageio)", + "type": "text" + }, { "type": "separator" }, @@ -189,6 +194,61 @@ ] } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "load", + "label": "Loader plugins", + "children": [ + { + "type": "dict", + "collapsible": true, + "key": "LoadClip", + "label": "Load Clip", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "families", + "label": "Families", + "object_type": "text" + }, + { + "type": "list", + "key": "representations", + "label": "Representations", + "object_type": "text" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "reel_group_name", + "label": "Reel group name" + }, + { + "type": "text", + "key": "reel_name", + "label": "Reel name" + }, + { + "type": "separator" + }, + { + "type": "text", + "key": "clip_name_template", + "label": "Clip name template" + } + ] + } + ] } ] } diff --git a/openpype/tools/pyblish_pype/window.py b/openpype/tools/pyblish_pype/window.py index 7bb11745d6..9c4d4b10f4 100644 --- a/openpype/tools/pyblish_pype/window.py +++ b/openpype/tools/pyblish_pype/window.py @@ -1039,6 +1039,8 @@ class Window(QtWidgets.QDialog): and not self.controller.stopped ) self.button_suspend_logs.setEnabled(suspend_log_bool) + if not self.isVisible(): + self.setVisible(True) def on_was_skipped(self, plugin): plugin_item = self.plugin_model.plugin_items[plugin.id] @@ -1112,6 +1114,9 @@ class Window(QtWidgets.QDialog): plugin_item, instance_item ) + if not self.isVisible(): + self.setVisible(True) + # ------------------------------------------------------------------------- # # Functions