diff --git a/pype/__init__.py b/pype/__init__.py index bcbedc9a90..c2311dd528 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -35,6 +35,8 @@ def patched_discover(superclass): plugins = _original_discover(superclass) # determine host application to use for finding presets + if avalon.registered_host() is None: + return plugins host = avalon.registered_host().__name__.split(".")[-1] # map plugin superclass to preset json. Currenly suppoted is load and diff --git a/pype/ftrack/actions/action_cust_attr_doctor.py b/pype/ftrack/actions/action_cust_attr_doctor.py index 1b8f250e5b..b875f52ab8 100644 --- a/pype/ftrack/actions/action_cust_attr_doctor.py +++ b/pype/ftrack/actions/action_cust_attr_doctor.py @@ -23,10 +23,10 @@ class CustomAttributeDoctor(BaseAction): icon = '{}/ftrack/action_icons/PypeDoctor.svg'.format( os.environ.get('PYPE_STATICS_SERVER', '') ) - hierarchical_ca = ['handle_start', 'handle_end', 'fstart', 'fend'] + hierarchical_ca = ['handleStart', 'handleEnd', 'frameStart', 'frameEnd'] hierarchical_alternatives = { - 'handle_start': 'handles', - 'handle_end': 'handles' + 'handleStart': 'handles', + 'handleEnd': 'handles' } # Roles for new custom attributes @@ -34,22 +34,22 @@ class CustomAttributeDoctor(BaseAction): write_roles = ['ALL',] data_ca = { - 'handle_start': { + 'handleStart': { 'label': 'Frame handles start', 'type': 'number', 'config': json.dumps({'isdecimal': False}) }, - 'handle_end': { + 'handleEnd': { 'label': 'Frame handles end', 'type': 'number', 'config': json.dumps({'isdecimal': False}) }, - 'fstart': { + 'frameStart': { 'label': 'Frame start', 'type': 'number', 'config': json.dumps({'isdecimal': False}) }, - 'fend': { + 'frameEnd': { 'label': 'Frame end', 'type': 'number', 'config': json.dumps({'isdecimal': False}) diff --git a/pype/maya/lib.py b/pype/maya/lib.py index e54dac78f2..bd48862721 100644 --- a/pype/maya/lib.py +++ b/pype/maya/lib.py @@ -39,19 +39,17 @@ SHAPE_ATTRS = {"castsShadows", "doubleSided", "opposite"} -RENDER_ATTRS = {"vray": - { +RENDER_ATTRS = {"vray": { "node": "vraySettings", "prefix": "fileNamePrefix", "padding": "fileNamePadding", "ext": "imageFormatStr" - }, - "default": - { + }, + "default": { "node": "defaultRenderGlobals", "prefix": "imageFilePrefix", "padding": "extensionPadding" - } + } } @@ -341,19 +339,6 @@ def undo_chunk(): cmds.undoInfo(closeChunk=True) -@contextlib.contextmanager -def renderlayer(layer): - """Set the renderlayer during the context""" - - original = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) - - try: - cmds.editRenderLayerGlobals(currentRenderLayer=layer) - yield - finally: - cmds.editRenderLayerGlobals(currentRenderLayer=original) - - @contextlib.contextmanager def evaluation(mode="off"): """Set the evaluation manager during context. @@ -832,7 +817,8 @@ def is_visible(node, # Display layers set overrideEnabled and overrideVisibility on members if cmds.attributeQuery('overrideEnabled', node=node, exists=True): override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node)) - override_visibility = cmds.getAttr('{}.overrideVisibility'.format(node)) + override_visibility = cmds.getAttr('{}.overrideVisibility'.format( + node)) if override_enabled and override_visibility: return False @@ -854,8 +840,8 @@ def extract_alembic(file, startFrame=None, endFrame=None, selection=True, - uvWrite= True, - eulerFilter= True, + uvWrite=True, + eulerFilter=True, dataFormat="ogawa", verbose=False, **kwargs): @@ -1470,8 +1456,8 @@ def apply_shaders(relationships, shadernodes, nodes): member_uuids = [member["uuid"] for member in data["members"]] filtered_nodes = list() - for uuid in member_uuids: - filtered_nodes.extend(nodes_by_id[uuid]) + for m_uuid in member_uuids: + filtered_nodes.extend(nodes_by_id[m_uuid]) id_shading_engines = shading_engines_by_id[shader_uuid] if not id_shading_engines: @@ -2110,6 +2096,7 @@ def bake_to_world_space(nodes, return world_space_nodes + def load_capture_preset(path=None, data=None): import capture_gui import capture @@ -2119,14 +2106,14 @@ def load_capture_preset(path=None, data=None): else: path = path preset = capture_gui.lib.load_json(path) - print preset + print(preset) options = dict() # CODEC id = 'Codec' for key in preset[id]: - options[str(key)]= preset[id][key] + options[str(key)] = preset[id][key] # GENERIC id = 'Generic' @@ -2142,7 +2129,6 @@ def load_capture_preset(path=None, data=None): options['height'] = preset[id]['height'] options['width'] = preset[id]['width'] - # DISPLAY OPTIONS id = 'Display Options' disp_options = {} @@ -2154,7 +2140,6 @@ def load_capture_preset(path=None, data=None): options['display_options'] = disp_options - # VIEWPORT OPTIONS temp_options = {} id = 'Renderer' @@ -2163,11 +2148,12 @@ def load_capture_preset(path=None, data=None): temp_options2 = {} id = 'Viewport Options' - light_options = { 0: "default", - 1: 'all', - 2: 'selected', - 3: 'flat', - 4: 'nolights'} + light_options = { + 0: "default", + 1: 'all', + 2: 'selected', + 3: 'flat', + 4: 'nolights'} for key in preset[id]: if key == 'high_quality': temp_options2['multiSampleEnable'] = True @@ -2190,7 +2176,10 @@ def load_capture_preset(path=None, data=None): else: temp_options[str(key)] = preset[id][key] - for key in ['override_viewport_options', 'high_quality', 'alphaCut', "gpuCacheDisplayFilter"]: + for key in ['override_viewport_options', + 'high_quality', + 'alphaCut', + 'gpuCacheDisplayFilter']: temp_options.pop(key, None) for key in ['ssaoEnable']: @@ -2199,7 +2188,6 @@ def load_capture_preset(path=None, data=None): options['viewport_options'] = temp_options options['viewport2_options'] = temp_options2 - # use active sound track scene = capture.parse_active_scene() options['sound'] = scene['sound'] @@ -2363,31 +2351,51 @@ class shelf(): if item['type'] == 'button': self.addButon(item['name'], command=item['command']) if item['type'] == 'menuItem': - self.addMenuItem(item['parent'], item['name'], command=item['command']) + self.addMenuItem(item['parent'], + item['name'], + command=item['command']) if item['type'] == 'subMenu': - self.addMenuItem(item['parent'], item['name'], command=item['command']) + self.addMenuItem(item['parent'], + item['name'], + command=item['command']) - def addButon(self, label, icon="commandButton.png", command=_null, doubleCommand=_null): - '''Adds a shelf button with the specified label, command, double click command and image.''' + def addButon(self, label, icon="commandButton.png", + command=_null, doubleCommand=_null): + ''' + Adds a shelf button with the specified label, command, + double click command and image. + ''' cmds.setParent(self.name) if icon: icon = self.iconPath + icon - cmds.shelfButton(width=37, height=37, image=icon, l=label, command=command, dcc=doubleCommand, imageOverlayLabel=label, olb=self.labelBackground, olc=self.labelColour) + cmds.shelfButton(width=37, height=37, image=icon, label=label, + command=command, dcc=doubleCommand, + imageOverlayLabel=label, olb=self.labelBackground, + olc=self.labelColour) def addMenuItem(self, parent, label, command=_null, icon=""): - '''Adds a shelf button with the specified label, command, double click command and image.''' + ''' + Adds a shelf button with the specified label, command, + double click command and image. + ''' if icon: icon = self.iconPath + icon - return cmds.menuItem(p=parent, l=label, c=command, i="") + return cmds.menuItem(p=parent, label=label, c=command, i="") def addSubMenu(self, parent, label, icon=None): - '''Adds a sub menu item with the specified label and icon to the specified parent popup menu.''' + ''' + Adds a sub menu item with the specified label and icon to + the specified parent popup menu. + ''' if icon: icon = self.iconPath + icon - return cmds.menuItem(p=parent, l=label, i=icon, subMenu=1) + return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1) def _cleanOldShelf(self): - '''Checks if the shelf exists and empties it if it does or creates it if it does not.''' + ''' + Checks if the shelf exists and empties it if it does + or creates it if it does not. + ''' if cmds.shelfLayout(self.name, ex=1): if cmds.shelfLayout(self.name, q=1, ca=1): for each in cmds.shelfLayout(self.name, q=1, ca=1): diff --git a/pype/plugins/ftrack/publish/integrate_remove_components.py b/pype/plugins/ftrack/publish/integrate_remove_components.py index a215ee1b97..bad50f7200 100644 --- a/pype/plugins/ftrack/publish/integrate_remove_components.py +++ b/pype/plugins/ftrack/publish/integrate_remove_components.py @@ -17,6 +17,9 @@ class IntegrateCleanComponentData(pyblish.api.InstancePlugin): for comp in instance.data['representations']: self.log.debug('component {}'.format(comp)) + + if "%" in comp['published_path'] or "#" in comp['published_path']: + continue if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): os.remove(comp['published_path']) diff --git a/pype/plugins/global/publish/collect_context.py b/pype/plugins/global/publish/collect_context.py index b718f18fa8..5f443ac5fb 100644 --- a/pype/plugins/global/publish/collect_context.py +++ b/pype/plugins/global/publish/collect_context.py @@ -46,9 +46,25 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): in_data = json.load(f) asset_name = in_data['asset'] + family_preset_key = in_data.get('family_preset_key', '') family = in_data['family'] subset = in_data['subset'] + # Load presets + presets = context.data.get("presets") + if not presets: + from pypeapp import config + presets = config.get_presets() + + # Get from presets anatomy key that will be used for getting template + # - default integrate new is used if not set + anatomy_key = presets.get( + "standalone_publish", {}).get( + "families", {}).get( + family_preset_key, {}).get( + "anatomy_template" + ) + project = io.find_one({'type': 'project'}) asset = io.find_one({ 'type': 'asset', @@ -65,6 +81,8 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): "label": subset, "name": subset, "family": family, + "frameStart": in_data.get("representations", [None])[0].get("frameStart", None), + "frameEnd": in_data.get("representations", [None])[0].get("frameEnd", None), "families": [family, 'ftrack'], }) self.log.info("collected instance: {}".format(instance.data)) @@ -78,7 +96,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): component['destination'] = component['files'] component['stagingDir'] = component['stagingDir'] - component['anatomy_template'] = 'render' + # Do not set anatomy_template if not specified + if anatomy_key: + component['anatomy_template'] = anatomy_key if isinstance(component['files'], list): collections, remainder = clique.assemble(component['files']) self.log.debug("collecting sequence: {}".format(collections)) diff --git a/pype/plugins/global/publish/collect_output_repre_config.py b/pype/plugins/global/publish/collect_output_repre_config.py index f02199e778..73ab050bcf 100644 --- a/pype/plugins/global/publish/collect_output_repre_config.py +++ b/pype/plugins/global/publish/collect_output_repre_config.py @@ -15,7 +15,7 @@ class CollectOutputRepreConfig(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder label = "Collect Config for representation" - hosts = ["shell"] + hosts = ["shell", "standalonepublisher"] def process(self, context): config_data = config.get_presets()["ftrack"]["output_representation"] diff --git a/pype/plugins/global/publish/collect_presets.py b/pype/plugins/global/publish/collect_presets.py index abf85a6f01..5e79c555e2 100644 --- a/pype/plugins/global/publish/collect_presets.py +++ b/pype/plugins/global/publish/collect_presets.py @@ -14,7 +14,7 @@ from pypeapp import config class CollectPresets(api.ContextPlugin): """Collect Presets.""" - order = api.CollectorOrder + order = api.CollectorOrder - 0.491 label = "Collect Presets" def process(self, context): diff --git a/pype/plugins/global/publish/collect_scene_version.py b/pype/plugins/global/publish/collect_scene_version.py index 12075e2417..3fac823b5c 100644 --- a/pype/plugins/global/publish/collect_scene_version.py +++ b/pype/plugins/global/publish/collect_scene_version.py @@ -13,6 +13,8 @@ class CollectSceneVersion(pyblish.api.ContextPlugin): label = 'Collect Version' def process(self, context): + if "standalonepublisher" in context.data.get("host"): + return filename = os.path.basename(context.data.get('currentFile')) diff --git a/pype/plugins/global/publish/extract_thumbnail_sa.py b/pype/plugins/global/publish/extract_thumbnail_sa.py new file mode 100644 index 0000000000..f42985b560 --- /dev/null +++ b/pype/plugins/global/publish/extract_thumbnail_sa.py @@ -0,0 +1,126 @@ +import os +import tempfile +import subprocess +import pyblish.api +import pype.api + + +class ExtractThumbnail(pyblish.api.InstancePlugin): + """Extract jpeg thumbnail from component input from standalone publisher + + Uses jpeg file from component if possible (when single or multiple jpegs + are loaded to component selected as thumbnail) otherwise extracts from + input file/s single jpeg to temp. + """ + + label = "Extract Thumbnail" + hosts = ["standalonepublisher"] + order = pyblish.api.ExtractorOrder + + def process(self, instance): + repres = instance.data.get('representations') + if not repres: + return + + thumbnail_repre = None + for repre in repres: + if repre.get("thumbnail"): + thumbnail_repre = repre + break + + if not thumbnail_repre: + return + + files = thumbnail_repre.get("files") + if not files: + return + + if isinstance(files, list): + files_len = len(files) + file = str(files[0]) + else: + files_len = 1 + file = files + + is_jpeg = False + if file.endswith(".jpeg") or file.endswith(".jpg"): + is_jpeg = True + + if is_jpeg and files_len == 1: + # skip if already is single jpeg file + return + + elif is_jpeg: + # use first frame as thumbnail if is sequence of jpegs + full_thumbnail_path = file + self.log.info( + "For thumbnail is used file: {}".format(full_thumbnail_path) + ) + + else: + # Convert to jpeg if not yet + full_input_path = os.path.join(thumbnail_repre["stagingDir"], file) + self.log.info("input {}".format(full_input_path)) + + full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1] + self.log.info("output {}".format(full_thumbnail_path)) + + config_data = instance.context.data.get("output_repre_config", {}) + + proj_name = os.environ.get("AVALON_PROJECT", "__default__") + profile = config_data.get( + proj_name, + config_data.get("__default__", {}) + ) + + ffmpeg_path = os.getenv("FFMPEG_PATH", "") + if ffmpeg_path: + ffmpeg_path += "/ffmpeg" + else: + ffmpeg_path = "ffmpeg" + + jpeg_items = [] + jpeg_items.append(ffmpeg_path) + # override file if already exists + jpeg_items.append("-y") + # add input filters from peresets + if profile: + jpeg_items.extend(profile.get('input', [])) + # input file + jpeg_items.append("-i {}".format(full_input_path)) + # extract only single file + jpeg_items.append("-vframes 1") + # output file + jpeg_items.append(full_thumbnail_path) + + subprocess_jpeg = " ".join(jpeg_items) + + # run subprocess + self.log.debug("Executing: {}".format(subprocess_jpeg)) + subprocess.Popen( + subprocess_jpeg, + stdout=subprocess.PIPE, + shell=True + ) + + # remove thumbnail key from origin repre + thumbnail_repre.pop("thumbnail") + + filename = os.path.basename(full_thumbnail_path) + staging_dir = os.path.dirname(full_thumbnail_path) + + # create new thumbnail representation + representation = { + 'name': 'jpg', + 'ext': 'jpg', + 'files': filename, + "stagingDir": staging_dir, + "thumbnail": True, + "tags": [] + } + + # # add Delete tag when temp file was rendered + # if not is_jpeg: + # representation["tags"].append("delete") + + instance.data["representations"].append(representation) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index e5d8007d70..61881b2a34 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -307,7 +307,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get("frameStart"): frame_start_padding = len(str( repre.get("frameEnd"))) - index_frame_start = repre.get("frameStart") + index_frame_start = int(repre.get("frameStart")) dst_padding_exp = src_padding_exp for i in src_collection.indexes: @@ -322,7 +322,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_padding = dst_padding_exp % index_frame_start index_frame_start += 1 - dst = "{0}{1}{2}".format(dst_head, dst_padding, dst_tail) + dst = "{0}{1}{2}".format(dst_head, dst_padding, dst_tail).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) self.log.debug("source: {}".format(src)) @@ -357,7 +357,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): src = os.path.join(stagingdir, fname) anatomy_filled = anatomy.format(template_data) dst = os.path.normpath( - anatomy_filled[template_name]["path"]) + anatomy_filled[template_name]["path"]).replace("..", ".") instance.data["transfers"].append([src, dst]) @@ -440,6 +440,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Returns: None """ + src = os.path.normpath(src) + dst = os.path.normpath(dst) self.log.debug("Copying file .. {} -> {}".format(src, dst)) dirname = os.path.dirname(dst) diff --git a/pype/plugins/maya/create/create_assembly.py b/pype/plugins/maya/create/create_assembly.py index 2a00d4a29a..6d0321b718 100644 --- a/pype/plugins/maya/create/create_assembly.py +++ b/pype/plugins/maya/create/create_assembly.py @@ -7,5 +7,5 @@ class CreateAssembly(avalon.maya.Creator): name = "assembly" label = "Assembly" family = "assembly" - icon = "boxes" + icon = "cubes" defaults = ['Main'] diff --git a/pype/plugins/maya/create/create_layout.py b/pype/plugins/maya/create/create_layout.py index 3f6dd5d769..7f0c82d80e 100644 --- a/pype/plugins/maya/create/create_layout.py +++ b/pype/plugins/maya/create/create_layout.py @@ -7,5 +7,5 @@ class CreateLayout(avalon.maya.Creator): name = "layoutMain" label = "Layout" family = "layout" - icon = "boxes" + icon = "cubes" defaults = ["Main"] diff --git a/pype/plugins/maya/create/create_setdress.py b/pype/plugins/maya/create/create_setdress.py index 079ccbd029..d5fc001299 100644 --- a/pype/plugins/maya/create/create_setdress.py +++ b/pype/plugins/maya/create/create_setdress.py @@ -7,5 +7,5 @@ class CreateSetDress(avalon.maya.Creator): name = "setdressMain" label = "Set Dress" family = "setdress" - icon = "boxes" + icon = "cubes" defaults = ["Main", "Anim"] diff --git a/pype/plugins/maya/load/load_mayaascii.py b/pype/plugins/maya/load/load_mayaascii.py index 03a15b0524..b9a5de2782 100644 --- a/pype/plugins/maya/load/load_mayaascii.py +++ b/pype/plugins/maya/load/load_mayaascii.py @@ -45,7 +45,23 @@ class MayaAsciiLoader(pype.maya.plugin.ReferenceLoader): cmds.setAttr(groupName + ".useOutlinerColor", 1) cmds.setAttr(groupName + ".outlinerColor", c[0], c[1], c[2]) - + cmds.setAttr(groupName + ".displayHandle", 1) + # get bounding box + bbox = cmds.exactWorldBoundingBox(groupName) + # get pivot position on world space + pivot = cmds.xform(groupName, q=True, sp=True, ws=True) + # center of bounding box + cx = (bbox[0] + bbox[3]) / 2 + cy = (bbox[1] + bbox[4]) / 2 + cz = (bbox[2] + bbox[5]) / 2 + # add pivot position to calculate offset + cx = cx + pivot[0] + cy = cy + pivot[1] + cz = cz + pivot[2] + # set selection handle offset to center of bounding box + cmds.setAttr(groupName + ".selectHandleX", cx) + cmds.setAttr(groupName + ".selectHandleY", cy) + cmds.setAttr(groupName + ".selectHandleZ", cz) return nodes def switch(self, container, representation): diff --git a/pype/plugins/maya/load/load_reference.py b/pype/plugins/maya/load/load_reference.py index fb4b90a1cd..f855cb55f9 100644 --- a/pype/plugins/maya/load/load_reference.py +++ b/pype/plugins/maya/load/load_reference.py @@ -1,4 +1,4 @@ -from avalon import api + import pype.maya.plugin import os from pypeapp import config @@ -58,6 +58,9 @@ class ReferenceLoader(pype.maya.plugin.ReferenceLoader): for root in roots: root.setParent(groupNode) + cmds.setAttr(groupName + ".displayHandle", 1) + groupNode + presets = config.get_presets(project=os.environ['AVALON_PROJECT']) colors = presets['plugins']['maya']['load']['colors'] c = colors.get(family) @@ -67,11 +70,30 @@ class ReferenceLoader(pype.maya.plugin.ReferenceLoader): self[:] = nodes + cmds.setAttr(groupName + ".displayHandle", 1) + # get bounding box + bbox = cmds.exactWorldBoundingBox(groupName) + # get pivot position on world space + pivot = cmds.xform(groupName, q=True, sp=True, ws=True) + # center of bounding box + cx = (bbox[0] + bbox[3]) / 2 + cy = (bbox[1] + bbox[4]) / 2 + cz = (bbox[2] + bbox[5]) / 2 + # add pivot position to calculate offset + cx = cx + pivot[0] + cy = cy + pivot[1] + cz = cz + pivot[2] + # set selection handle offset to center of bounding box + cmds.setAttr(groupName + ".selectHandleX", cx) + cmds.setAttr(groupName + ".selectHandleY", cy) + cmds.setAttr(groupName + ".selectHandleZ", cz) + return nodes def switch(self, container, representation): self.update(container, representation) + # for backwards compatibility class AbcLoader(ReferenceLoader): label = "Deprecated loader (don't use)" @@ -79,6 +101,7 @@ class AbcLoader(ReferenceLoader): representations = ["abc"] tool_names = [] + # for backwards compatibility class ModelLoader(ReferenceLoader): label = "Deprecated loader (don't use)" diff --git a/pype/plugins/nuke/publish/collect_active_viewer.py b/pype/plugins/nuke/_publish_unused/collect_active_viewer.py similarity index 83% rename from pype/plugins/nuke/publish/collect_active_viewer.py rename to pype/plugins/nuke/_publish_unused/collect_active_viewer.py index 5dc17d8768..5a6cc02b88 100644 --- a/pype/plugins/nuke/publish/collect_active_viewer.py +++ b/pype/plugins/nuke/_publish_unused/collect_active_viewer.py @@ -11,5 +11,4 @@ class CollectActiveViewer(pyblish.api.ContextPlugin): hosts = ["nuke"] def process(self, context): - context.data["ViewerProcess"] = nuke.ViewerProcess.node() context.data["ActiveViewer"] = nuke.activeViewer() diff --git a/pype/plugins/nuke/publish/validate_active_viewer.py b/pype/plugins/nuke/_publish_unused/validate_active_viewer.py similarity index 65% rename from pype/plugins/nuke/publish/validate_active_viewer.py rename to pype/plugins/nuke/_publish_unused/validate_active_viewer.py index bcf7cab6b3..618a7f1502 100644 --- a/pype/plugins/nuke/publish/validate_active_viewer.py +++ b/pype/plugins/nuke/_publish_unused/validate_active_viewer.py @@ -16,3 +16,9 @@ class ValidateActiveViewer(pyblish.api.ContextPlugin): assert viewer_process_node, ( "Missing active viewer process! Please click on output write node and push key number 1-9" ) + active_viewer = context.data["ActiveViewer"] + active_input = active_viewer.activeInput() + + assert active_input is not None, ( + "Missing active viewer input! Please click on output write node and push key number 1-9" + ) diff --git a/pype/plugins/nuke/load/load_luts.py b/pype/plugins/nuke/load/load_luts.py index 7e1302fffe..4f7c19a588 100644 --- a/pype/plugins/nuke/load/load_luts.py +++ b/pype/plugins/nuke/load/load_luts.py @@ -14,6 +14,7 @@ class LoadLuts(api.Loader): order = 0 icon = "cc" color = style.colors.light + ignore_attr = ["useLifetime"] def load(self, context, name, namespace, data): """ @@ -83,6 +84,8 @@ class LoadLuts(api.Loader): for ef_name, ef_val in nodes_order.items(): node = nuke.createNode(ef_val["class"]) for k, v in ef_val["node"].items(): + if k in self.ignore_attr: + continue if isinstance(v, list) and len(v) > 4: node[k].setAnimated() for i, value in enumerate(v): @@ -194,6 +197,8 @@ class LoadLuts(api.Loader): for ef_name, ef_val in nodes_order.items(): node = nuke.createNode(ef_val["class"]) for k, v in ef_val["node"].items(): + if k in self.ignore_attr: + continue if isinstance(v, list) and len(v) > 3: node[k].setAnimated() for i, value in enumerate(v): diff --git a/pype/plugins/nuke/load/load_luts_ip.py b/pype/plugins/nuke/load/load_luts_ip.py index b0a30d78e4..b30f84cc42 100644 --- a/pype/plugins/nuke/load/load_luts_ip.py +++ b/pype/plugins/nuke/load/load_luts_ip.py @@ -14,6 +14,7 @@ class LoadLutsInputProcess(api.Loader): order = 0 icon = "eye" color = style.colors.alert + ignore_attr = ["useLifetime"] def load(self, context, name, namespace, data): """ @@ -83,6 +84,8 @@ class LoadLutsInputProcess(api.Loader): for ef_name, ef_val in nodes_order.items(): node = nuke.createNode(ef_val["class"]) for k, v in ef_val["node"].items(): + if k in self.ignore_attr: + continue if isinstance(v, list) and len(v) > 4: node[k].setAnimated() for i, value in enumerate(v): @@ -196,6 +199,8 @@ class LoadLutsInputProcess(api.Loader): for ef_name, ef_val in nodes_order.items(): node = nuke.createNode(ef_val["class"]) for k, v in ef_val["node"].items(): + if k in self.ignore_attr: + continue if isinstance(v, list) and len(v) > 3: node[k].setAnimated() for i, value in enumerate(v): diff --git a/pype/plugins/nuke/publish/collect_legacy_write.py b/pype/plugins/nuke/publish/collect_legacy_write.py new file mode 100644 index 0000000000..05dbe4216c --- /dev/null +++ b/pype/plugins/nuke/publish/collect_legacy_write.py @@ -0,0 +1,25 @@ +import nuke + +import pyblish.api + + +class CollectWriteLegacy(pyblish.api.ContextPlugin): + """Collect legacy write nodes.""" + + order = pyblish.api.CollectorOrder + label = "Collect Write Legacy" + hosts = ["nuke", "nukeassist"] + + def process(self, context): + + for node in nuke.allNodes(): + if node.Class() != "Write": + continue + + if "avalon" not in node.knobs().keys(): + continue + + instance = context.create_instance( + node.name(), family="write.legacy" + ) + instance.append(node) diff --git a/pype/plugins/nuke/publish/extract_ouput_node.py b/pype/plugins/nuke/publish/extract_ouput_node.py index 4d7533f010..a144761e5f 100644 --- a/pype/plugins/nuke/publish/extract_ouput_node.py +++ b/pype/plugins/nuke/publish/extract_ouput_node.py @@ -15,21 +15,17 @@ class CreateOutputNode(pyblish.api.ContextPlugin): def process(self, context): # capture selection state with maintained_selection(): - # deselect all allNodes - self.log.info(context.data["ActiveViewer"]) + active_node = [node for inst in context[:] + for node in inst[:] + if "ak:family" in node.knobs()] - active_viewer = context.data["ActiveViewer"] - active_input = active_viewer.activeInput() - active_node = active_viewer.node() - - - last_viewer_node = active_node.input(active_input) - - name = last_viewer_node.name() - self.log.info("Node name: {}".format(name)) + if active_node: + self.log.info(active_node) + active_node = active_node[0] + self.log.info(active_node) + active_node['selected'].setValue(True) # select only instance render node - last_viewer_node['selected'].setValue(True) output_node = nuke.createNode("Output") # deselect all and select the original selection diff --git a/pype/plugins/nuke/publish/extract_review_data.py b/pype/plugins/nuke/publish/extract_review_data.py index 40c3e37434..08eba5bb1e 100644 --- a/pype/plugins/nuke/publish/extract_review_data.py +++ b/pype/plugins/nuke/publish/extract_review_data.py @@ -3,7 +3,6 @@ import nuke import pyblish.api import pype - class ExtractReviewData(pype.api.Extractor): """Extracts movie and thumbnail with baked in luts @@ -48,9 +47,9 @@ class ExtractReviewData(pype.api.Extractor): assert instance.data['representations'][0]['files'], "Instance data files should't be empty!" - import nuke temporary_nodes = [] - stagingDir = instance.data['representations'][0]["stagingDir"].replace("\\", "/") + stagingDir = instance.data[ + 'representations'][0]["stagingDir"].replace("\\", "/") self.log.debug("StagingDir `{0}`...".format(stagingDir)) collection = instance.data.get("collection", None) @@ -70,16 +69,24 @@ class ExtractReviewData(pype.api.Extractor): first_frame = instance.data.get("frameStart", None) last_frame = instance.data.get("frameEnd", None) - node = previous_node = nuke.createNode("Read") + rnode = nuke.createNode("Read") - node["file"].setValue( + rnode["file"].setValue( os.path.join(stagingDir, fname).replace("\\", "/")) - node["first"].setValue(first_frame) - node["origfirst"].setValue(first_frame) - node["last"].setValue(last_frame) - node["origlast"].setValue(last_frame) - temporary_nodes.append(node) + rnode["first"].setValue(first_frame) + rnode["origfirst"].setValue(first_frame) + rnode["last"].setValue(last_frame) + rnode["origlast"].setValue(last_frame) + temporary_nodes.append(rnode) + previous_node = rnode + + # get input process and connect it to baking + ipn = self.get_view_process_node() + if ipn is not None: + ipn.setInput(0, previous_node) + previous_node = ipn + temporary_nodes.append(ipn) reformat_node = nuke.createNode("Reformat") @@ -95,22 +102,10 @@ class ExtractReviewData(pype.api.Extractor): previous_node = reformat_node temporary_nodes.append(reformat_node) - viewer_process_node = instance.context.data.get("ViewerProcess") - dag_node = None - if viewer_process_node: - dag_node = nuke.createNode(viewer_process_node.Class()) - dag_node.setInput(0, previous_node) - previous_node = dag_node - temporary_nodes.append(dag_node) - # Copy viewer process values - excludedKnobs = ["name", "xpos", "ypos"] - for item in viewer_process_node.knobs().keys(): - if item not in excludedKnobs and item in dag_node.knobs(): - x1 = viewer_process_node[item] - x2 = dag_node[item] - x2.fromScript(x1.toScript(False)) - else: - self.log.warning("No viewer node found.") + dag_node = nuke.createNode("OCIODisplay") + dag_node.setInput(0, previous_node) + previous_node = dag_node + temporary_nodes.append(dag_node) # create write node write_node = nuke.createNode("Write") @@ -164,3 +159,28 @@ class ExtractReviewData(pype.api.Extractor): # Clean up for node in temporary_nodes: nuke.delete(node) + + def get_view_process_node(self): + + # Select only the target node + if nuke.selectedNodes(): + [n.setSelected(False) for n in nuke.selectedNodes()] + + for v in [n for n in nuke.allNodes() + if "Viewer" in n.Class()]: + ip = v['input_process'].getValue() + ipn = v['input_process_node'].getValue() + if "VIEWER_INPUT" not in ipn and ip: + ipn_orig = nuke.toNode(ipn) + ipn_orig.setSelected(True) + + if ipn_orig: + nuke.nodeCopy('%clipboard%') + + [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all + + nuke.nodePaste('%clipboard%') + + ipn = nuke.selectedNode() + + return ipn diff --git a/pype/plugins/nuke/publish/validate_rendered_frames.py b/pype/plugins/nuke/publish/validate_rendered_frames.py index 85cbe7b2c0..3887b5d5b7 100644 --- a/pype/plugins/nuke/publish/validate_rendered_frames.py +++ b/pype/plugins/nuke/publish/validate_rendered_frames.py @@ -81,3 +81,5 @@ class ValidateRenderedFrames(pyblish.api.InstancePlugin): ).format(__name__) instance.data['collection'] = collection + + return diff --git a/pype/plugins/nuke/publish/validate_write_legacy.py b/pype/plugins/nuke/publish/validate_write_legacy.py new file mode 100644 index 0000000000..b452e60ba4 --- /dev/null +++ b/pype/plugins/nuke/publish/validate_write_legacy.py @@ -0,0 +1,62 @@ +import toml +import os + +import nuke + +from avalon import api +import pyblish.api + + +class RepairWriteLegacyAction(pyblish.api.Action): + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + + # Get the errored instances + failed = [] + for result in context.data["results"]: + if (result["error"] is not None and result["instance"] is not None + and result["instance"] not in failed): + failed.append(result["instance"]) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(failed, plugin) + + for instance in instances: + data = toml.loads(instance[0]["avalon"].value()) + data["xpos"] = instance[0].xpos() + data["ypos"] = instance[0].ypos() + data["input"] = instance[0].input(0) + data["publish"] = instance[0]["publish"].value() + data["render"] = instance[0]["render"].value() + data["render_farm"] = instance[0]["render_farm"].value() + + nuke.delete(instance[0]) + + family = "render{}".format(os.environ["AVALON_TASK"].capitalize()) + api.create(data["subset"], data["asset"], family) + node = nuke.toNode(data["subset"]) + node.setXYpos(data["xpos"], data["ypos"]) + node.setInput(0, data["input"]) + node["publish"].setValue(data["publish"]) + node["render"].setValue(data["render"]) + node["render_farm"].setValue(data["render_farm"]) + + +class ValidateWriteLegacy(pyblish.api.InstancePlugin): + """Validate legacy write nodes.""" + + order = pyblish.api.ValidatorOrder + optional = True + families = ["write.legacy"] + label = "Write Legacy" + hosts = ["nuke"] + actions = [RepairWriteLegacyAction] + + def process(self, instance): + + msg = "Clean up legacy write node \"{}\"".format(instance) + assert False, msg diff --git a/pype/plugins/nukestudio/publish/collect_effects.py b/pype/plugins/nukestudio/publish/collect_effects.py index 11693ab1fe..0aee0adf2e 100644 --- a/pype/plugins/nukestudio/publish/collect_effects.py +++ b/pype/plugins/nukestudio/publish/collect_effects.py @@ -14,7 +14,11 @@ class CollectVideoTracksLuts(pyblish.api.InstancePlugin): self.log.debug("Finding soft effect for subset: `{}`".format(instance.data.get("subset"))) # taking active sequence - subset = instance.data["subset"] + subset = instance.data.get("subset") + + if not subset: + return + track_effects = instance.context.data.get("trackEffects", {}) track_index = instance.data["trackIndex"] effects = instance.data["effects"] @@ -74,7 +78,7 @@ class CollectVideoTracksLuts(pyblish.api.InstancePlugin): 'channels', 'maskChannelMask', 'maskChannelInput', 'note_font', 'note_font_size', 'unpremult', 'postage_stamp_frame', 'maskChannel', 'export_cc', - 'select_cccid', 'mix', 'version'] + 'select_cccid', 'mix', 'version', 'matrix'] # loop trough all knobs and collect not ignored # and any with any value diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/nukestudio/publish/collect_plates.py index 7f6f4138cb..2ebbfde551 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/nukestudio/publish/collect_plates.py @@ -175,6 +175,8 @@ class CollectPlatesData(api.InstancePlugin): if os.path.exists(mov_path): # adding mov into the representations self.log.debug("__ mov_path: {}".format(mov_path)) + instance.data["label"] += " - review" + plates_mov_representation = { 'files': mov_file, 'stagingDir': staging_dir, diff --git a/pype/standalonepublish/publish.py b/pype/standalonepublish/publish.py index 13b505666c..f199aaf84e 100644 --- a/pype/standalonepublish/publish.py +++ b/pype/standalonepublish/publish.py @@ -103,7 +103,7 @@ def avalon_api_publish(data, gui=True): "-pp", os.pathsep.join(pyblish.api.registered_paths()) ] - os.environ["PYBLISH_HOSTS"] = "shell" + os.environ["PYBLISH_HOSTS"] = "standalonepublisher" os.environ["SAPUBLISH_INPATH"] = json_data_path if gui: @@ -139,7 +139,7 @@ def cli_publish(data, gui=True): if gui: args += ["gui"] - os.environ["PYBLISH_HOSTS"] = "shell" + os.environ["PYBLISH_HOSTS"] = "standalonepublisher" os.environ["SAPUBLISH_INPATH"] = json_data_path os.environ["SAPUBLISH_OUTPATH"] = return_data_path diff --git a/pype/standalonepublish/widgets/__init__.py b/pype/standalonepublish/widgets/__init__.py index 4c6a0e85a5..c6e0dd9a47 100644 --- a/pype/standalonepublish/widgets/__init__.py +++ b/pype/standalonepublish/widgets/__init__.py @@ -6,6 +6,7 @@ HelpRole = QtCore.Qt.UserRole + 2 FamilyRole = QtCore.Qt.UserRole + 3 ExistsRole = QtCore.Qt.UserRole + 4 PluginRole = QtCore.Qt.UserRole + 5 +PluginKeyRole = QtCore.Qt.UserRole + 6 from ..resources import get_resource from .button_from_svgs import SvgResizable, SvgButton diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index e60db892db..a5a686bae1 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -220,15 +220,21 @@ class DropDataFrame(QtWidgets.QFrame): self._process_data(data) def load_data_with_probe(self, filepath): + ffprobe_path = os.getenv("FFMPEG_PATH", "") + if ffprobe_path: + ffprobe_path += '/ffprobe' + else: + ffprobe_path = 'ffprobe' + args = [ - 'ffprobe', + ffprobe_path, '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', filepath ] ffprobe_p = subprocess.Popen( - args, + ' '.join(args), stdout=subprocess.PIPE, shell=True ) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 63776b1df3..26eb8077d9 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -5,7 +5,7 @@ import json from collections import namedtuple from . import QtWidgets, QtCore -from . import HelpRole, FamilyRole, ExistsRole, PluginRole +from . import HelpRole, FamilyRole, ExistsRole, PluginRole, PluginKeyRole from . import FamilyDescriptionWidget from pypeapp import config @@ -116,8 +116,10 @@ class FamilyWidget(QtWidgets.QWidget): def collect_data(self): plugin = self.list_families.currentItem().data(PluginRole) + key = self.list_families.currentItem().data(PluginKeyRole) family = plugin.family.rsplit(".", 1)[-1] data = { + 'family_preset_key': key, 'family': family, 'subset': self.input_result.text(), 'version': self.version_spinbox.value() @@ -318,7 +320,7 @@ class FamilyWidget(QtWidgets.QWidget): has_families = False presets = config.get_presets().get('standalone_publish', {}) - for creator in presets.get('families', {}).values(): + for key, creator in presets.get('families', {}).items(): creator = namedtuple("Creator", creator.keys())(*creator.values()) label = creator.label or creator.family @@ -327,6 +329,7 @@ class FamilyWidget(QtWidgets.QWidget): item.setData(HelpRole, creator.help or "") item.setData(FamilyRole, creator.family) item.setData(PluginRole, creator) + item.setData(PluginKeyRole, key) item.setData(ExistsRole, False) self.list_families.addItem(item)