From 3fd9170e1fa061e0eb3f5b03a3c2640322ece093 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 12 Jan 2019 18:27:06 +0100 Subject: [PATCH 01/46] adding json export of all context and instances for debug, polishing integrate_render_frames --- pype/plugin.py | 5 +- .../publish/integrate_rendered_frames.py | 32 +++--- .../plugins/nuke/publish/extract_post_json.py | 107 ++++++++++++++++++ pype/plugins/nuke/publish/extract_review.py | 6 +- 4 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 pype/plugins/nuke/publish/extract_post_json.py diff --git a/pype/plugin.py b/pype/plugin.py index 0ba1fe5ded..9f8e6f09fd 100644 --- a/pype/plugin.py +++ b/pype/plugin.py @@ -1,4 +1,5 @@ import tempfile +import os import pyblish.api ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 @@ -28,7 +29,9 @@ class Extractor(pyblish.api.InstancePlugin): staging_dir = instance.data.get('stagingDir', None) if not staging_dir: - staging_dir = tempfile.mkdtemp(prefix="pyblish_tmp_") + staging_dir = os.path.normpath( + tempfile.mkdtemp(prefix="pyblish_tmp_") + ) instance.data['stagingDir'] = staging_dir return staging_dir diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index bc5b138fb8..c363fa49bb 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -39,8 +39,8 @@ class IntegrateFrames(pyblish.api.InstancePlugin): self.register(instance) - self.log.info("Integrating Asset in to the database ...") - self.log.info("instance.data: {}".format(instance.data)) + # self.log.info("Integrating Asset in to the database ...") + # self.log.info("instance.data: {}".format(instance.data)) if instance.data.get('transfer', True): self.integrate(instance) @@ -158,8 +158,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # Each should be a single representation (as such, a single extension) representations = [] destination_list = [] - self.log.debug("integrate_frames:instance.data[files]: {}".format( - instance.data["files"])) + for files in instance.data["files"]: # Collection # _______ @@ -193,7 +192,8 @@ class IntegrateFrames(pyblish.api.InstancePlugin): for i in src_collection.indexes: src_padding = src_collection.format("{padding}") % i - src_file_name = "{0}{1}{2}".format(src_head, src_padding, src_tail) + src_file_name = "{0}{1}{2}".format(src_head, + src_padding, src_tail) dst_padding = dst_collection.format("{padding}") % i dst = "{0}{1}{2}".format(dst_head, dst_padding, dst_tail) @@ -244,17 +244,17 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # Imprint shortcut to context # for performance reasons. "context": { - "root": root, - "project": PROJECT, - "projectcode": project['data']['code'], - 'task': api.Session["AVALON_TASK"], - "silo": asset['silo'], - "asset": ASSET, - "family": instance.data['family'], - "subset": subset["name"], - "VERSION": version["name"], - "hierarchy": hierarchy, - "representation": ext[1:] + "root": root, + "project": PROJECT, + "projectcode": project['data']['code'], + 'task': api.Session["AVALON_TASK"], + "silo": asset['silo'], + "asset": ASSET, + "family": instance.data['family'], + "subset": subset["name"], + "VERSION": version["name"], + "hierarchy": hierarchy, + "representation": ext[1:] } } diff --git a/pype/plugins/nuke/publish/extract_post_json.py b/pype/plugins/nuke/publish/extract_post_json.py new file mode 100644 index 0000000000..6954abff3d --- /dev/null +++ b/pype/plugins/nuke/publish/extract_post_json.py @@ -0,0 +1,107 @@ +import os +import json +import datetime +import time + +import clique +import pyblish.api + + +class ExtractJSON(pyblish.api.ContextPlugin): + """ Extract all instances to a serialized json file. """ + + order = pyblish.api.IntegratorOrder + 1 + label = "Extract to JSON" + families = ["write"] + + def process(self, context): + workspace = os.path.join( + os.path.dirname(context.data["currentFile"]), "workspace", + "instances") + + if not os.path.exists(workspace): + os.makedirs(workspace) + + context_data = context.data.copy() + out_data = dict(self.serialize(context_data)) + + instances_data = [] + for instance in context: + + data = {} + for key, value in instance.data.items(): + if isinstance(value, clique.Collection): + value = value.format() + + try: + json.dumps(value) + data[key] = value + except KeyError: + msg = "\"{0}\"".format(value) + msg += " in instance.data[\"{0}\"]".format(key) + msg += " could not be serialized." + self.log.debug(msg) + + instances_data.append(data) + + out_data["instances"] = instances_data + + timestamp = datetime.datetime.fromtimestamp( + time.time()).strftime("%Y%m%d-%H%M%S") + filename = timestamp + "_instances.json" + + with open(os.path.join(workspace, filename), "w") as outfile: + outfile.write(json.dumps(out_data, indent=4, sort_keys=True)) + + def serialize(self, data): + """ + Convert all nested content to serialized objects + + Args: + data (dict): nested data + + Returns: + dict: nested data + """ + + def encoding_obj(value): + try: + value = str(value).replace("\\", "/") + # value = getattr(value, '__dict__', str(value)) + except Exception: + pass + return value + + for key, value in dict(data).items(): + if key in ["records", "instances", "results"]: + # escape all record objects + data[key] = None + continue + + if hasattr(value, '__module__'): + # only deals with module objects + if "plugins" in value.__module__: + # only dealing with plugin objects + data[key] = str(value.__module__) + else: + if ".lib." in value.__module__: + # will allow only anatomy dict + data[key] = self.serialize(value) + else: + data[key] = None + continue + continue + + if isinstance(value, dict): + # loops if dictionary + data[key] = self.serialize(value) + + if isinstance(value, (list or tuple)): + # loops if list or tuple + for i, item in enumerate(value): + value[i] = self.serialize(item) + data[key] = value + + data[key] = encoding_obj(value) + + return data diff --git a/pype/plugins/nuke/publish/extract_review.py b/pype/plugins/nuke/publish/extract_review.py index 30de2039df..9ff7097e61 100644 --- a/pype/plugins/nuke/publish/extract_review.py +++ b/pype/plugins/nuke/publish/extract_review.py @@ -35,6 +35,7 @@ class ExtractDataForReview(pype.api.Extractor): self.log.debug("here:") self.log.debug("creating staging dir:") self.staging_dir(instance) + self.render_review_representation(instance, representation="mov") self.log.debug("review mov:") @@ -52,7 +53,7 @@ class ExtractDataForReview(pype.api.Extractor): staging_dir = instance.data["stagingDir"] file_name = collection.format("{head}mov") - review_mov = os.path.join(staging_dir, file_name) + review_mov = os.path.join(staging_dir, file_name).replace("\\", "/") if instance.data.get("baked_colorspace_movie"): args = [ @@ -110,9 +111,6 @@ class ExtractDataForReview(pype.api.Extractor): first_frame = min(collection.indexes) last_frame = max(collection.indexes) - self.log.warning("first_frame: {}".format(first_frame)) - self.log.warning("last_frame: {}".format(last_frame)) - node = previous_node = nuke.createNode("Read") node["file"].setValue( From 0a844ee7093c0eb80274e6430dab8f6b4eee2571 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 12 Jan 2019 18:27:28 +0100 Subject: [PATCH 02/46] cosmetic changes --- pype/plugins/global/publish/integrate_rendered_frames.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index c363fa49bb..1f14832492 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -233,6 +233,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): instance.data["transfers"].append([src, dst]) + self.log.debug('ext[1:]: {}'.format(ext[1:])) representation = { "schema": "pype:representation-2.0", "type": "representation", From 11cf14e2829f0aafeba1359b5e8560cc68033e1c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 18 Jan 2019 17:52:40 +0100 Subject: [PATCH 03/46] cleanup write publishing and loading --- .../validate_write_families.py | 32 ++++++++++--------- pype/plugins/nuke/load/load_sequence.py | 23 ++++++------- pype/plugins/nuke/publish/collect_families.py | 31 ++++++++---------- .../plugins/nuke/publish/collect_instances.py | 4 +-- pype/plugins/nuke/publish/collect_writes.py | 3 +- .../nuke/publish/validate_collection.py | 9 +++--- 6 files changed, 51 insertions(+), 51 deletions(-) rename pype/plugins/nuke/{publish => _publish_unused}/validate_write_families.py (57%) diff --git a/pype/plugins/nuke/publish/validate_write_families.py b/pype/plugins/nuke/_publish_unused/validate_write_families.py similarity index 57% rename from pype/plugins/nuke/publish/validate_write_families.py rename to pype/plugins/nuke/_publish_unused/validate_write_families.py index 1dfdbc06d5..087fb3be5e 100644 --- a/pype/plugins/nuke/publish/validate_write_families.py +++ b/pype/plugins/nuke/_publish_unused/validate_write_families.py @@ -20,29 +20,31 @@ class ValidateWriteFamilies(pyblish.api.InstancePlugin): """ Validates write families. """ order = pyblish.api.ValidatorOrder - label = "Check correct writes families" + label = "Valitade writes families" hosts = ["nuke"] families = ["write"] actions = [pype.nuke.actions.SelectInvalidAction, pype.api.RepairAction] - @staticmethod - def get_invalid(instance): - if not [f for f in instance.data["families"] - if ".frames" in f]: - return - - if not instance.data["files"]: - return (instance) + # @staticmethod + # def get_invalid(instance): + # for f in instance.data["families"]: + # if ".frames" in f: + # return + # + # if not instance.data["files"]: + # return (instance) def process(self, instance): self.log.debug('instance.data["files"]: {}'.format(instance.data['files'])) - invalid = self.get_invalid(instance) - if invalid: - raise ValueError(str("`{}`: Switch `Render` on! " - "> {}".format(__name__, invalid))) - - self.log.info("Checked correct writes families") + # if any(".frames" in f for f in instance.data["families"]): + # if not instance.data["files"]: + # raise ValueError("instance {} is set to publish frames\ + # but no files were collected, render the frames first or\ + # check 'render' checkbox onthe no to 'ON'".format(instance))) + # + # + # self.log.info("Checked correct writes families") @classmethod def repair(cls, instance): diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 1cd3688aaf..056867fd5f 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -36,13 +36,13 @@ def preserve_trim(node): if start_at_frame: node['frame_mode'].setValue("start at") node['frame'].setValue(str(script_start)) - log.info("start frame of reader was set to" + log.info("start frame of Read was set to" "{}".format(script_start)) if offset_frame: node['frame_mode'].setValue("offset") node['frame'].setValue(str((script_start + offset_frame))) - log.info("start frame of reader was set to" + log.info("start frame of Read was set to" "{}".format(script_start)) @@ -67,7 +67,7 @@ def loader_shift(node, frame, relative=True): if relative: node['frame_mode'].setValue("start at") - node['frame'].setValue(str(script_start)) + node['frame'].setValue(str(frame)) return int(script_start) @@ -75,7 +75,7 @@ def loader_shift(node, frame, relative=True): class LoadSequence(api.Loader): """Load image sequence into Nuke""" - families = ["write"] + families = ["write", "source"] representations = ["*"] label = "Load sequence" @@ -142,8 +142,9 @@ class LoadSequence(api.Loader): data_imprint = {} for k in add_keys: data_imprint.update({k: context["version"]['data'][k]}) + data_imprint.update({"objectName": read_name}) - containerise(r, + return containerise(r, name=name, namespace=namespace, context=context, @@ -168,9 +169,9 @@ class LoadSequence(api.Loader): update_container ) log.info("this i can see") - node = container["_tool"] - # TODO: prepare also for other readers img/geo/camera - assert node.Class() == "Reader", "Must be Reader" + node = nuke.toNode(container['objectName']) + # TODO: prepare also for other Read img/geo/camera + assert node.Class() == "Read", "Must be Read" root = api.get_representation_path(representation) file = ls_img_sequence(os.path.dirname(root), one=True) @@ -189,7 +190,7 @@ class LoadSequence(api.Loader): # Update the loader's path whilst preserving some values with preserve_trim(node): - node["file"] = file["path"] + node["file"].setValue(file["path"]) # Set the global in to the start frame of the sequence global_in_changed = loader_shift(node, start, relative=False) @@ -208,8 +209,8 @@ class LoadSequence(api.Loader): from avalon.nuke import viewer_update_and_undo_stop - node = container["_tool"] - assert node.Class() == "Reader", "Must be Reader" + node = nuke.toNode(container['objectName']) + assert node.Class() == "Read", "Must be Read" with viewer_update_and_undo_stop(): nuke.delete(node) diff --git a/pype/plugins/nuke/publish/collect_families.py b/pype/plugins/nuke/publish/collect_families.py index d0e61c349b..4fd09fe967 100644 --- a/pype/plugins/nuke/publish/collect_families.py +++ b/pype/plugins/nuke/publish/collect_families.py @@ -15,31 +15,28 @@ class CollectInstanceFamilies(pyblish.api.ContextPlugin): if "write" in instance.data["family"]: node = instance[0] - # set for ftrack to accept - instance.data["families"] = ["ftrack"] + families = [] + if instance.data.get('families'): + families.append(instance.data['families']) - if not node["render"].value(): - families = ["{}.frames".format( - instance.data["avalonKnob"]["families"])] - # to ignore staging dir op in integrate - instance.data['transfer'] = False - else: + # set for ftrack to accept + # instance.data["families"] = ["ftrack"] + + if node["render"].value(): # dealing with local/farm rendering if node["render_farm"].value(): - families = ["{}.farm".format( - instance.data["avalonKnob"]["families"])] + families.append("render.farm") else: - families = ["{}.local".format( - instance.data["avalonKnob"]["families"])] + families.append("render.local") + else: + families.append("render.frames") + # to ignore staging dir op in integrate + instance.data['transfer'] = False - instance.data["families"].extend(families) - - elif "source" in instance.data["family"]: - families = [] - families.append(instance.data["avalonKnob"]["families"]) instance.data["families"] = families + # Sort/grouped by family (preserving local index) context[:] = sorted(context, key=self.sort_by_family) diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 33e6d5a608..8a2bb06fff 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -56,8 +56,8 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): "fps": int(nuke.root()['fps'].value()) }) - if node.Class() == "Write": - instance.data["families"] = [avalon_knob_data["families"]] + # if node.Class() == "Write": + # instance.data["families"] = [avalon_knob_data["families"]] self.log.info("collected instance: {}".format(instance.data)) instances.append(instance) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index dd3247ae8f..89f78367a9 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -64,14 +64,13 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): # collect families in next file if "files" not in instance.data: instance.data["files"] = list() - try: collected_frames = os.listdir(output_dir) self.log.debug("collected_frames: {}".format(label)) instance.data["files"].append(collected_frames) except Exception: - pass + self.log.debug("couldn't collect frames: {}".format(label)) instance.data.update({ "path": path, diff --git a/pype/plugins/nuke/publish/validate_collection.py b/pype/plugins/nuke/publish/validate_collection.py index 54b3537055..a99e930661 100644 --- a/pype/plugins/nuke/publish/validate_collection.py +++ b/pype/plugins/nuke/publish/validate_collection.py @@ -20,20 +20,19 @@ class RepairCollectionAction(pyblish.api.Action): self.log.info("Rendering toggled ON") -class ValidateCollection(pyblish.api.InstancePlugin): +class ValidatePrerenderedFrames(pyblish.api.InstancePlugin): """ Validates file output. """ order = pyblish.api.ValidatorOrder + 0.1 families = ["render.frames", "still.frames", "prerender.frames"] - label = "Check prerendered frames" + label = "Validate prerendered frame" hosts = ["nuke"] actions = [RepairCollectionAction] def process(self, instance): self.log.debug('instance.data["files"]: {}'.format(instance.data['files'])) - if not instance.data["files"]: - return + assert instance.data["files"], "No frames have been collected" collections, remainder = clique.assemble(*instance.data['files']) self.log.info('collections: {}'.format(str(collections))) @@ -57,3 +56,5 @@ class ValidateCollection(pyblish.api.InstancePlugin): collection.indexes ) is frame_length, "{} missing frames. Use " "repair to render all frames".format(__name__) + + instance.data['collection'] = collection From e1601664f194eaeb3a7104a194423a45599d6522 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 18 Jan 2019 17:58:39 +0100 Subject: [PATCH 04/46] json extraction is causing troubles in nuke. temporarily setting to just maya host --- pype/plugins/global/publish/collect_json.py | 1 + pype/plugins/global/publish/extract_json.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_json.py b/pype/plugins/global/publish/collect_json.py index 1301bb2ee4..1cf79f4612 100644 --- a/pype/plugins/global/publish/collect_json.py +++ b/pype/plugins/global/publish/collect_json.py @@ -11,6 +11,7 @@ class CollectJSON(pyblish.api.ContextPlugin): label = "JSON" order = pyblish.api.CollectorOrder + hosts = ['maya'] def version_get(self, string, prefix): """ Extract version information from filenames. Code from Foundry"s diff --git a/pype/plugins/global/publish/extract_json.py b/pype/plugins/global/publish/extract_json.py index dc00fecb49..fa7f95147b 100644 --- a/pype/plugins/global/publish/extract_json.py +++ b/pype/plugins/global/publish/extract_json.py @@ -12,7 +12,7 @@ class ExtractJSON(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "JSON" - hosts = ['nuke', 'maya'] + hosts = ['maya'] def process(self, context): From 3613a6061426371156d32508c3668a266d56b601 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 18 Jan 2019 18:20:19 +0100 Subject: [PATCH 05/46] review cleanup, changing family to "review" instead of "render.review" --- pype/nuke/lib.py | 6 +-- .../extract_write_next_render.py | 1 + pype/plugins/nuke/publish/collect_review.py | 12 +++-- pype/plugins/nuke/publish/extract_review.py | 53 +++++-------------- 4 files changed, 24 insertions(+), 48 deletions(-) rename pype/plugins/nuke/{publish => _load_unused}/extract_write_next_render.py (97%) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 2f002ce130..4322e55c00 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -35,9 +35,9 @@ def writes_version_sync(): for each in nuke.allNodes(): if each.Class() == 'Write': avalon_knob_data = get_avalon_knob_data(each) - if avalon_knob_data['families'] not in ["render"]: - log.info(avalon_knob_data['families']) - continue + # if avalon_knob_data['families'] not in ["render"]: + # log.info(avalon_knob_data['families']) + # continue try: node_file = each['file'].value() log.info("node_file: {}".format(node_file)) diff --git a/pype/plugins/nuke/publish/extract_write_next_render.py b/pype/plugins/nuke/_load_unused/extract_write_next_render.py similarity index 97% rename from pype/plugins/nuke/publish/extract_write_next_render.py rename to pype/plugins/nuke/_load_unused/extract_write_next_render.py index d13e67a563..40bfe59ec2 100644 --- a/pype/plugins/nuke/publish/extract_write_next_render.py +++ b/pype/plugins/nuke/_load_unused/extract_write_next_render.py @@ -13,6 +13,7 @@ class WriteToRender(pyblish.api.InstancePlugin): families = ["write"] def process(self, instance): + return if [f for f in instance.data["families"] if ".frames" in f]: instance[0]["render"].setValue(True) diff --git a/pype/plugins/nuke/publish/collect_review.py b/pype/plugins/nuke/publish/collect_review.py index 03f5437e86..c6a3173af1 100644 --- a/pype/plugins/nuke/publish/collect_review.py +++ b/pype/plugins/nuke/publish/collect_review.py @@ -14,14 +14,16 @@ class CollectReview(pyblish.api.InstancePlugin): family_targets = [".local", ".frames"] def process(self, instance): + pass families = [(f, search) for f in instance.data["families"] for search in self.family_targets if search in f][0] if families: - root_femilies = families[0].replace(families[1], "") - instance.data["families"].append(".".join([ - root_femilies, - self.family - ])) + root_families = families[0].replace(families[1], "") + # instance.data["families"].append(".".join([ + # root_families, + # self.family + # ])) + instance.data["families"].append("review") self.log.info("Review collected: `{}`".format(instance)) diff --git a/pype/plugins/nuke/publish/extract_review.py b/pype/plugins/nuke/publish/extract_review.py index 9ff7097e61..cf0864e09d 100644 --- a/pype/plugins/nuke/publish/extract_review.py +++ b/pype/plugins/nuke/publish/extract_review.py @@ -2,6 +2,7 @@ import os import nuke import pyblish.api import pype +from pype.vendor import ffmpeg class ExtractDataForReview(pype.api.Extractor): @@ -12,27 +13,18 @@ class ExtractDataForReview(pype.api.Extractor): """ order = pyblish.api.ExtractorOrder + 0.01 - label = "Data for review" + label = "Extract Review" optional = True - families = ["write"] + families = ["review"] hosts = ["nuke"] - family_targets = [".local", ".review"] def process(self, instance): - families = [f for f in instance.data["families"] - for search in self.family_targets - if search in f] - if not families: - return - self.log.debug("here:") # Store selection selection = [i for i in nuke.allNodes() if i["selected"].getValue()] - self.log.debug("here:") # Deselect all nodes to prevent external connections [i["selected"].setValue(False) for i in nuke.allNodes()] - self.log.debug("here:") self.log.debug("creating staging dir:") self.staging_dir(instance) @@ -55,32 +47,18 @@ class ExtractDataForReview(pype.api.Extractor): review_mov = os.path.join(staging_dir, file_name).replace("\\", "/") - if instance.data.get("baked_colorspace_movie"): - args = [ - "ffmpeg", "-y", - "-i", instance.data["baked_colorspace_movie"], - "-pix_fmt", "yuv420p", - "-crf", "18", - "-timecode", "00:00:00:01", - ] - - args.append(review_mov) - - self.log.debug("Executing args: {0}".format(args)) - self.log.info("transcoding review mov: {0}".format(review_mov)) - p = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - stdin=subprocess.PIPE, - cwd=os.path.dirname(args[-1]) - ) + if instance.data.get("baked_colorspace_movie"): + input_movie = instance.data["baked_colorspace_movie"] + out, err = ( + ffmpeg + .input(input_movie) + .output(review_mov, pix_fmt='yuv420p', crf=18, timecode="00:00:00:01") + .overwrite_output() + .run() + ) - output = p.communicate()[0] - if p.returncode != 0: - raise ValueError(output) self.log.debug("Removing `{0}`...".format( instance.data["baked_colorspace_movie"])) @@ -101,12 +79,6 @@ class ExtractDataForReview(pype.api.Extractor): collection = instance.data.get("collection", None) - self.log.warning("instance.data['files']: {}".format(instance.data['files'])) - if not collection: - collections, remainder = clique.assemble(*instance.data['files']) - collection = collections[0] - instance.data["collection"] = collection - # Create nodes first_frame = min(collection.indexes) last_frame = max(collection.indexes) @@ -156,6 +128,7 @@ class ExtractDataForReview(pype.api.Extractor): if representation in "mov": file = collection.format("{head}baked.mov") path = os.path.join(staging_dir, file).replace("\\", "/") + self.log.debug("Path: {}".format(path)) instance.data["baked_colorspace_movie"] = path write_node["file"].setValue(path) write_node["file_type"].setValue("mov") From e5efe62a81ae3c3557dc8f6f9f2125e3cf44d415 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 21 Jan 2019 13:44:24 +0100 Subject: [PATCH 06/46] fixing families in loader --- .../publish/integrate_rendered_frames.py | 15 ++++--- pype/plugins/nuke/load/load_sequence.py | 41 ++++++++----------- pype/plugins/nuke/publish/collect_review.py | 2 +- pype/plugins/nuke/publish/extract_review.py | 2 +- .../nuke/publish/integrate_script_version.py | 2 + 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index eb2450293c..325a9e3442 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -199,7 +199,6 @@ class IntegrateFrames(pyblish.api.InstancePlugin): src = os.path.join(stagingdir, src_file_name) instance.data["transfers"].append([src, dst]) - template = anatomy.render.path else: # Single file @@ -229,31 +228,35 @@ class IntegrateFrames(pyblish.api.InstancePlugin): anatomy_filled = anatomy.format(template_data) dst = anatomy_filled.render.path - template = anatomy.render.path instance.data["transfers"].append([src, dst]) + template_data["frame"] = "#####" + anatomy_filled = anatomy.format(template_data) + path_to_save = anatomy_filled.render.path + template = anatomy.render.fullpath self.log.debug('ext[1:]: {}'.format(ext[1:])) + representation = { "schema": "pype:representation-2.0", "type": "representation", "parent": version_id, "name": ext[1:], - "data": {'path': dst, 'template': template}, + "data": {'path': path_to_save, 'template': template}, "dependencies": instance.data.get("dependencies", "").split(), # Imprint shortcut to context # for performance reasons. "context": { "root": root, - "project": PROJECT, - "projectcode": project['data']['code'], + "project": {"name": PROJECT, + "code": project['data']['code']}, 'task': api.Session["AVALON_TASK"], "silo": asset['silo'], "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "VERSION": version["name"], + "version": version["name"], "hierarchy": hierarchy, "representation": ext[1:] } diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 056867fd5f..23575a3f28 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -76,7 +76,7 @@ class LoadSequence(api.Loader): """Load image sequence into Nuke""" families = ["write", "source"] - representations = ["*"] + representations = ["exr"] label = "Load sequence" order = -10 @@ -86,44 +86,33 @@ class LoadSequence(api.Loader): def load(self, context, name, namespace, data): from avalon.nuke import ( containerise, - ls_img_sequence, viewer_update_and_undo_stop ) - for k, v in context.items(): - log.info("key: `{}`, value: {}\n".format(k, v)) + # for k, v in context.items(): + # log.info("key: `{}`, value: {}\n".format(k, v)) + + version = context['version'] + version_data = version.get("data", {}) + + first = version_data.get("startFrame", None) + last = version_data.get("endFrame", None) # Fallback to asset name when namespace is None if namespace is None: namespace = context['asset']['name'] - # Use the first file for now - # TODO: fix path fname - file = ls_img_sequence(os.path.dirname(self.fname), one=True) - log.info("file: {}\n".format(file)) + file = self.fname + log.info("file: {}\n".format(self.fname)) read_name = "Read_" + context["representation"]["context"]["subset"] + # Create the Loader with the filename path set with viewer_update_and_undo_stop(): # TODO: it might be universal read to img/geo/camera r = nuke.createNode( "Read", "name {}".format(read_name)) - r["file"].setValue(file['path']) - if len(file['frames']) is 1: - first = file['frames'][0][0] - last = file['frames'][0][1] - r["origfirst"].setValue(first) - r["first"].setValue(first) - r["origlast"].setValue(last) - r["last"].setValue(last) - else: - first = file['frames'][0][0] - last = file['frames'][:-1][1] - r["origfirst"].setValue(first) - r["first"].setValue(first) - r["origlast"].setValue(last) - r["last"].setValue(last) - log.warning("Missing frames in image sequence") + r["file"].setValue(self.fname) # Set colorspace defined in version data colorspace = context["version"]["data"].get("colorspace", None) @@ -134,6 +123,10 @@ class LoadSequence(api.Loader): start = context["version"]["data"].get("startFrame", None) if start is not None: loader_shift(r, start, relative=True) + r["origfirst"].setValue(first) + r["first"].setValue(first) + r["origlast"].setValue(last) + r["last"].setValue(last) # add additional metadata from the version to imprint to Avalon knob add_keys = ["startFrame", "endFrame", "handles", diff --git a/pype/plugins/nuke/publish/collect_review.py b/pype/plugins/nuke/publish/collect_review.py index c6a3173af1..f75c675b8f 100644 --- a/pype/plugins/nuke/publish/collect_review.py +++ b/pype/plugins/nuke/publish/collect_review.py @@ -25,5 +25,5 @@ class CollectReview(pyblish.api.InstancePlugin): # root_families, # self.family # ])) - instance.data["families"].append("review") + instance.data["families"].append("render.review") self.log.info("Review collected: `{}`".format(instance)) diff --git a/pype/plugins/nuke/publish/extract_review.py b/pype/plugins/nuke/publish/extract_review.py index cf0864e09d..e85185e919 100644 --- a/pype/plugins/nuke/publish/extract_review.py +++ b/pype/plugins/nuke/publish/extract_review.py @@ -16,7 +16,7 @@ class ExtractDataForReview(pype.api.Extractor): label = "Extract Review" optional = True - families = ["review"] + families = ["render.review"] hosts = ["nuke"] def process(self, instance): diff --git a/pype/plugins/nuke/publish/integrate_script_version.py b/pype/plugins/nuke/publish/integrate_script_version.py index aa37101af0..09dfeb4e7d 100644 --- a/pype/plugins/nuke/publish/integrate_script_version.py +++ b/pype/plugins/nuke/publish/integrate_script_version.py @@ -13,6 +13,8 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): families = ["nukescript", "render.local", "render.frames"] def process(self, context): + # return + # from pype.lib import version_up path = context.data["currentFile"] nuke.scriptSaveAs(version_up(path)) From 1d2e09feb655adf2860202d48c38772ef0172247 Mon Sep 17 00:00:00 2001 From: antirotor Date: Wed, 30 Jan 2019 17:49:13 +0100 Subject: [PATCH 07/46] fixed start, end frames, fps, code cleanup --- .../plugins/maya/publish/extract_quicktime.py | 111 ++++-------------- 1 file changed, 21 insertions(+), 90 deletions(-) diff --git a/pype/plugins/maya/publish/extract_quicktime.py b/pype/plugins/maya/publish/extract_quicktime.py index 9883f5b264..33e25822f5 100644 --- a/pype/plugins/maya/publish/extract_quicktime.py +++ b/pype/plugins/maya/publish/extract_quicktime.py @@ -1,7 +1,5 @@ import os import contextlib -import time -import sys import capture_gui import clique @@ -9,30 +7,18 @@ import clique import pype.maya.lib as lib import pype.api -from maya import cmds +from maya import cmds, mel import pymel.core as pm from pype.vendor import ffmpeg reload(ffmpeg) -import avalon.maya - -# import maya_utils as mu - -# from tweakHUD import master -# from tweakHUD import draft_hud as dHUD -# from tweakHUD import ftrackStrings as fStrings - -# -# def soundOffsetFunc(oSF, SF, H): -# tmOff = (oSF - H) - SF -# return tmOff - +# TODO: move codec settings to presets class ExtractQuicktime(pype.api.Extractor): - """Extract a Camera as Alembic. + """Extract Quicktime from viewport capture. - The cameras gets baked to world space by default. Only when the instance's - `bakeToWorldSpace` is set to False it will include its full hierarchy. + Takes review camera and creates review Quicktime video based on viewport + capture. """ @@ -44,8 +30,17 @@ class ExtractQuicktime(pype.api.Extractor): def process(self, instance): self.log.info("Extracting capture..") - start = instance.data.get("startFrame", 1) - end = instance.data.get("endFrame", 25) + # get scene fps + fps = mel.eval('currentTimeUnitToFPS()') + + # if start and end frames cannot be determined, get them + # from Maya timeline + start = instance.data.get("startFrame") + end = instance.data.get("endFrame") + if start is None: + start = cmds.playbackOptions(query=True, animationStartTime=True) + if end is None: + end = cmds.playbackOptions(query=True, animationEndTime=True) self.log.info("start: {}, end: {}".format(start, end)) handles = instance.data.get("handles", 0) if handles: @@ -53,46 +48,7 @@ class ExtractQuicktime(pype.api.Extractor): end += handles # get cameras - members = instance.data['setMembers'] camera = instance.data['review_camera'] - # cameras = cmds.ls(members, leaf=True, shapes=True, long=True, - # dag=True, type="camera") - - # # validate required settings - # assert len(cameras) == 1, "Not a single camera found in extraction" - # camera = cameras[0] - - - # project_code = ftrack_data['Project']['code'] - # task_type = ftrack_data['Task']['type'] - # - # # load Preset - # studio_repos = os.path.abspath(os.environ.get('studio_repos')) - # shot_preset_path = os.path.join(studio_repos, 'maya', - # 'capture_gui_presets', - # (project_code + '_' + task_type + '_' + asset + '.json')) - # - # task_preset_path = os.path.join(studio_repos, 'maya', - # 'capture_gui_presets', - # (project_code + '_' + task_type + '.json')) - # - # project_preset_path = os.path.join(studio_repos, 'maya', - # 'capture_gui_presets', - # (project_code + '.json')) - # - # default_preset_path = os.path.join(studio_repos, 'maya', - # 'capture_gui_presets', - # 'default.json') - # - # if os.path.isfile(shot_preset_path): - # preset_to_use = shot_preset_path - # elif os.path.isfile(task_preset_path): - # preset_to_use = task_preset_path - # elif os.path.isfile(project_preset_path): - # preset_to_use = project_preset_path - # else: - # preset_to_use = default_preset_path - capture_preset = "" try: preset = lib.load_capture_preset(capture_preset) @@ -100,15 +56,13 @@ class ExtractQuicktime(pype.api.Extractor): preset = {} self.log.info('using viewport preset: {}'.format(capture_preset)) - #preset["off_screen"] = False - preset['camera'] = camera preset['format'] = "image" # preset['compression'] = "qt" preset['quality'] = 50 preset['compression'] = "jpg" - preset['start_frame'] = 1 - preset['end_frame'] = 25 + preset['start_frame'] = start + preset['end_frame'] = end preset['camera_options'] = { "displayGateMask": False, "displayResolution": False, @@ -143,21 +97,19 @@ class ExtractQuicktime(pype.api.Extractor): self.log.info("file list {}".format(playblast)) # self.log.info("Calculating HUD data overlay") - # stagingdir = "C:/Users/milan.kolar/AppData/Local/Temp/pyblish_tmp_ucsymm" collected_frames = os.listdir(stagingdir) collections, remainder = clique.assemble(collected_frames) - input_path = os.path.join(stagingdir, collections[0].format('{head}{padding}{tail}')) + input_path = os.path.join( + stagingdir, collections[0].format('{head}{padding}{tail}')) self.log.info("input {}".format(input_path)) movieFile = filename + ".mov" full_movie_path = os.path.join(stagingdir, movieFile) self.log.info("output {}".format(full_movie_path)) - # fls = [os.path.join(stagingdir, filename).replace("\\","/") for f in os.listdir( dir_path ) if f.endswith(preset['compression'])] - # self.log.info("file list {}}".format(fls[0])) out, err = ( ffmpeg - .input(input_path, framerate=25) + .input(input_path, framerate=fps) .output(full_movie_path) .run(overwrite_output=True) ) @@ -166,27 +118,6 @@ class ExtractQuicktime(pype.api.Extractor): instance.data["files"] = list() instance.data["files"].append(movieFile) - # ftrackStrings = fStrings.annotationData() - # nData = ftrackStrings.niceData - # nData['version'] = instance.context.data('version') - # fFrame = int(pm.playbackOptions( q = True, minTime = True)) - # eFrame = int(pm.playbackOptions( q = True, maxTime = True)) - # nData['frame'] = [(str("{0:05d}".format(f))) for f in range(fFrame, eFrame + 1)] - # soundOfst = int(float(nData['oFStart'])) - int(float(nData['handle'])) - fFrame - # soundFile = mu.giveMePublishedAudio() - # self.log.info("SOUND offset %s" % str(soundOfst)) - # self.log.info("SOUND source video to %s" % str(soundFile)) - # ann = dHUD.draftAnnotate() - # if soundFile: - # ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData, soundFile = soundFile, soundOffset = soundOfst) - # else: - # ann.addAnotation(seqFls = fls, outputMoviePth = movieFullPth, annotateDataArr = nData) - - # for f in fls: - # os.remove(f) - - # playblast = (ann.expPth).replace("\\","/") - @contextlib.contextmanager def maintained_time(): From e4e96a98f42153b3e64cf328374770919b8a9d88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Feb 2019 19:03:10 +0100 Subject: [PATCH 08/46] copied copyrights cleanup --- pype/ftrack/actions/action_create_cust_attrs.py | 2 -- pype/ftrack/actions/action_job_killer.py | 2 -- pype/ftrack/actions/action_test.py | 2 -- pype/ftrack/lib/ftrack_app_handler.py | 2 -- 4 files changed, 8 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 8e926fb313..6fc9916ced 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -1,5 +1,3 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack import os import sys import argparse diff --git a/pype/ftrack/actions/action_job_killer.py b/pype/ftrack/actions/action_job_killer.py index 184053ed47..9ded00cd37 100644 --- a/pype/ftrack/actions/action_job_killer.py +++ b/pype/ftrack/actions/action_job_killer.py @@ -1,5 +1,3 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack import sys import argparse import logging diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index 31bcd4f518..83c0759406 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -1,5 +1,3 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack import sys import argparse import logging diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 32ccb96ba6..804a0b9c17 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -1,5 +1,3 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2017 ftrack import os import sys import platform From 647226d366b2902d1a189d4f067d40daf49d9762 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Feb 2019 19:04:32 +0100 Subject: [PATCH 09/46] initial commit of integrators to ftrack and avalon --- .../publish/integrate_avalon_instances.py | 89 ++++++++++++ .../publish/integrate_ftrack_instances.py | 127 ++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 pype/plugins/premiere/publish/integrate_avalon_instances.py create mode 100644 pype/plugins/premiere/publish/integrate_ftrack_instances.py diff --git a/pype/plugins/premiere/publish/integrate_avalon_instances.py b/pype/plugins/premiere/publish/integrate_avalon_instances.py new file mode 100644 index 0000000000..f3c4c05a2b --- /dev/null +++ b/pype/plugins/premiere/publish/integrate_avalon_instances.py @@ -0,0 +1,89 @@ +import pyblish.api +from bson.objectid import ObjectId +from pype.ftrack import get_project_config +from avalon import schema +from avalon.tools.libraryloader.io_nonsingleton import DbConnector + + +class IntegrateInstancesToAvalon(pyblish.api.InstancePlugin): + """ + Create entities in ftrack based on collected data from premiere + + """ + + order = pyblish.api.IntegratorOrder + 0.48 + label = 'Integrate To Avalon' + families = [] + + exclude = [] + + def process(self, instance): + for ex in self.exclude: + if ex in instance.data['families']: + return + + self.log.debug('instance {}'.format(instance)) + + self.db = DbConnector() + # TODO implement how to get right data + all_instances_data = {} + self.import_to_ftrack(all_instances_data) + + def import_to_avalon(self, input_data, parent=None): + for name in input_data: + entity_data = input_data[name] + entity_type = entity_data['entity_type'] + + # # Data + data = {} + # CUSTOM ATTRIBUTES + custom_attributes = entity_data.get('custom_attributes', []) + for key in custom_attributes: + data[key] = custom_attributes[key] + + # TASKS + data['entityType'] = entity_type + + if entity_type.lower() == 'project': + self.db.activate_project(name) + entity = self.db.find_one({'type': 'project'}) + if entity is None: + # TODO better exception + raise Exception + self.av_project = entity + + elif self.av_project is None or parent is None: + # TODO better exception + raise Exception + else: + tasks = entity_data.get('tasks', []) + data['tasks'] = tasks + data['visualParent'] = parent['_id'] + try: + entity = self.db.find_one({'type': 'asset', 'name': name}) + except Exception: + entity = None + + # Create entity if not exists + if entity is None: + entity = self.create_avalon_entity(name, entity_data) + + if entity_type.lower() == 'project': + self.db.update_many( + {'_id': self.av_project['_id']}, + {'$set': { + 'data': data + }}) + else: + self.db.update_many( + {'_id': entity['_id']}, + {'$set': { + 'data': data, + 'parent': ObjectId(self.av_project['_id']) + }}) + + if 'childs' in entity_data: + self.import_to_ftrack(entity_data['childs'], entity) + + def create_avalon_entity(self, name, data): + pass diff --git a/pype/plugins/premiere/publish/integrate_ftrack_instances.py b/pype/plugins/premiere/publish/integrate_ftrack_instances.py new file mode 100644 index 0000000000..155a8d2f8b --- /dev/null +++ b/pype/plugins/premiere/publish/integrate_ftrack_instances.py @@ -0,0 +1,127 @@ +import pyblish.api + + +class IntegrateInstancesToFtrack(pyblish.api.InstancePlugin): + """ + Create entities in ftrack based on collected data from premiere + + """ + + order = pyblish.api.IntegratorOrder + 0.48 + label = 'Integrate To Ftrack' + families = ["ftrack"] + + exclude = [] + + def process(self, instance): + for ex in self.exclude: + if ex in instance.data['families']: + return + + self.log.debug('instance {}'.format(instance)) + + self.ft_project = None + self.session = instance.context.data["ftrackSession"] + + # TODO implement how to get right data + all_instances_data = {} + self.import_to_ftrack(all_instances_data) + + def import_to_ftrack(self, input_data, parent=None): + for entity_name in input_data: + entity_data = input_data[entity_name] + entity_type = entity_data['entity_type'] + + if entity_type.lower() == 'project': + query = 'Project where full_name is "{}"'.format(entity_name) + entity = self.session.query(query).one() + self.ft_project = entity + self.task_types = self.get_all_task_types(entity) + + elif self.ft_project is None: + # TODO better exception + raise Exception + elif parent is None: + # TODO better exception + raise Exception + else: + query = '{} where name is "{}" and parent_id is "{}"'.format( + entity_type, entity_name, parent['id'] + ) + try: + entity = self.session.query(query).one() + except Exception: + entity = None + + # Create entity if not exists + if entity is None: + entity = self.create_entity( + name=entity_name, + type=entity_type, + parent=parent + ) + # CUSTOM ATTRIBUTES + custom_attributes = entity_data.get('custom_attributes', []) + for key in custom_attributes: + if key not in entity['custom_attributes']: + # TODO better exception + raise Exception + entity['custom_attributes'][key] = custom_attributes[key] + self.session.commit() + + # TASKS + tasks = entity_data.get('tasks', []) + existing_tasks = [] + tasks_to_create = [] + for child in entity['children']: + if child.entity_type.lower() == 'task': + existing_tasks.append(child['type']['name']) + + for task in tasks: + if task in existing_tasks: + print("Task {} already exists".format(task)) + continue + tasks_to_create.append(task) + + for task in tasks_to_create: + self.create_task( + name=task, + task_type=task, + parent=entity + ) + self.session.commit() + + if 'childs' in entity_data: + self.import_to_ftrack(entity_data['childs'], entity) + + def get_all_task_types(self, project): + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] + + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type + + return tasks + + def create_task(self, name, task_type, parent): + task = self.session.create('Task', { + 'name': name, + 'parent': parent + }) + # TODO not secured!!! - check if task_type exists + task['type'] = self.task_types[task_type] + + self.session.commit() + + return task + + def create_entity(self, name, type, parent): + entity = self.session.create(type, { + 'name': name, + 'parent': parent + }) + self.session.commit() + + return entity From bfcd5b1a3a1d880b32cf4ca56c004050eaf62b77 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 4 Feb 2019 19:12:18 +0100 Subject: [PATCH 10/46] task types are synced to project too --- pype/ftrack/lib/avalon_sync.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index cb33c76a30..f97f627768 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -53,7 +53,7 @@ def import_to_avalon( if entity_type in ['Project']: type = 'project' - config = get_project_config(entity) + config = get_config_from_ftproject(entity) schema.validate(config) av_project_code = None @@ -440,16 +440,42 @@ def get_avalon_project(ft_project): return avalon_project -def get_project_config(entity): +def get_config_from_ftproject(entity=None): config = {} config['schema'] = pypelib.get_avalon_project_config_schema() - config['tasks'] = [{'name': ''}] - config['apps'] = get_project_apps(entity) config['template'] = pypelib.get_avalon_project_template() + apps = [] + tasks = [{'name': ''}] + if entity is not None: + apps = get_project_apps(entity) + + tasks = [] + if entity.entity_type.lower() != 'project': + project = entity['project'] + else: + project = entity + for task_type in get_all_task_types_ftproj(project): + tasks.append({'name': task_type}) + + config['apps'] = apps + config['tasks'] = tasks + return config +def get_all_task_types_ftproj(project): + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] + + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type + + return tasks + + def get_project_apps(entity): """ Get apps from project Requirements: From 4fa28d6c0344f71b0bf7de2c036a8425155ac9c2 Mon Sep 17 00:00:00 2001 From: antirotor Date: Tue, 5 Feb 2019 00:58:04 +0100 Subject: [PATCH 11/46] fix: sequences starting on frame != 1 --- .../plugins/maya/publish/extract_quicktime.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pype/plugins/maya/publish/extract_quicktime.py b/pype/plugins/maya/publish/extract_quicktime.py index 33e25822f5..ca0ae30671 100644 --- a/pype/plugins/maya/publish/extract_quicktime.py +++ b/pype/plugins/maya/publish/extract_quicktime.py @@ -107,12 +107,19 @@ class ExtractQuicktime(pype.api.Extractor): full_movie_path = os.path.join(stagingdir, movieFile) self.log.info("output {}".format(full_movie_path)) - out, err = ( - ffmpeg - .input(input_path, framerate=fps) - .output(full_movie_path) - .run(overwrite_output=True) - ) + try: + ( + ffmpeg + .input(input_path, framerate=fps, start_number=int(start)) + .output(full_movie_path) + .run(overwrite_output=True, + capture_stdout=True, + capture_stderr=True) + ) + except ffmpeg.Error as e: + ffmpeg_error = 'ffmpeg error: {}'.format(e.stderr.decode()) + self.log.error(ffmpeg_error) + raise Exception(ffmpeg_error) if "files" not in instance.data: instance.data["files"] = list() From 1cb78c90bc0764e33d46b9ad5f910a1aaca8842e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Feb 2019 12:56:19 +0100 Subject: [PATCH 12/46] added get all object types, not used yet --- pype/ftrack/lib/avalon_sync.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index f97f627768..29b9e4a9b4 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -548,3 +548,18 @@ def get_config_data(): log.warning("{} - {}".format(msg, str(e))) return data + + +def get_all_object_type_array(self, session): + # basic excluded types + excludes = ['bug', 'epic', 'feature', 'information'] + # TODO read from presets which also will be excluded + all_obj_types = set() + for obj in session.query('ObjectType').all(): + name = obj['name'] + if ' ' in name: + name = name.replace(' ', '') + if name.lower() in excludes: + continue + all_obj_types.add(name) + return list(all_obj_types) From dbc52e39a4ecc120f89202b6db61fc5ee8aa85e1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Feb 2019 13:22:11 +0100 Subject: [PATCH 13/46] now creating assets, self.db set to avalon's io --- .../publish/integrate_avalon_instances.py | 104 +++++++++++------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/pype/plugins/premiere/publish/integrate_avalon_instances.py b/pype/plugins/premiere/publish/integrate_avalon_instances.py index f3c4c05a2b..9defb6cb48 100644 --- a/pype/plugins/premiere/publish/integrate_avalon_instances.py +++ b/pype/plugins/premiere/publish/integrate_avalon_instances.py @@ -1,8 +1,5 @@ import pyblish.api -from bson.objectid import ObjectId -from pype.ftrack import get_project_config -from avalon import schema -from avalon.tools.libraryloader.io_nonsingleton import DbConnector +from avalon import io class IntegrateInstancesToAvalon(pyblish.api.InstancePlugin): @@ -24,66 +21,91 @@ class IntegrateInstancesToAvalon(pyblish.api.InstancePlugin): self.log.debug('instance {}'.format(instance)) - self.db = DbConnector() + self.db = io + self.db.install() # TODO implement how to get right data all_instances_data = {} - self.import_to_ftrack(all_instances_data) + self.import_to_avalon(all_instances_data) def import_to_avalon(self, input_data, parent=None): for name in input_data: entity_data = input_data[name] entity_type = entity_data['entity_type'] - # # Data data = {} - # CUSTOM ATTRIBUTES - custom_attributes = entity_data.get('custom_attributes', []) - for key in custom_attributes: - data[key] = custom_attributes[key] - - # TASKS - data['entityType'] = entity_type - + # Process project if entity_type.lower() == 'project': - self.db.activate_project(name) + # TODO: this should be already set if io...? + if self.db.Session['AVALON_PROJECT'] is None: + self.db.Session['AVALON_PROJECT'] = name + entity = self.db.find_one({'type': 'project'}) if entity is None: - # TODO better exception + # TODO: better exception raise Exception - self.av_project = entity + # get data from already existing project + for key, value in entity.get('data', {}).items(): + data[key] = value + + self.av_project = entity + # Raise error if project or parent are not set elif self.av_project is None or parent is None: # TODO better exception raise Exception + # Else process assset else: + entity = self.db.find_one({'type': 'asset', 'name': name}) + # Create entity if doesn't exist + if entity is None: + if self.av_project['_id'] == parent['_id']: + silo = None + elif parent['silo'] is None: + silo = parent['name'] + else: + silo = parent['silo'] + entity = self.create_avalon_asset(name, silo) + # Else get data from already existing + else: + for key, value in entity.get('data', {}).items(): + data[key] = value + + data['entityType'] = entity_type + # TASKS tasks = entity_data.get('tasks', []) data['tasks'] = tasks - data['visualParent'] = parent['_id'] - try: - entity = self.db.find_one({'type': 'asset', 'name': name}) - except Exception: - entity = None + parents = [] + visualParent = None + # do not store project's id as visualParent (silo asset) + if self.av_project['_id'] != parent['_id']: + visualParent = parent['_id'] + parents.extend(parent['data']['parents']) + parents.append(parent['name']) + data['visualParent'] = visualParent + data['parents'] = parents - # Create entity if not exists - if entity is None: - entity = self.create_avalon_entity(name, entity_data) + # CUSTOM ATTRIBUTES + for key, value in entity_data.get('custom_attributes', {}).items(): + data[key] = value - if entity_type.lower() == 'project': - self.db.update_many( - {'_id': self.av_project['_id']}, - {'$set': { - 'data': data - }}) - else: - self.db.update_many( - {'_id': entity['_id']}, - {'$set': { - 'data': data, - 'parent': ObjectId(self.av_project['_id']) - }}) + # Update entity data with input data + self.db.update_many( + {'_id': entity['_id']}, + {'$set': { + 'data': data, + }}) if 'childs' in entity_data: self.import_to_ftrack(entity_data['childs'], entity) - def create_avalon_entity(self, name, data): - pass + def create_avalon_asset(self, name, silo): + item = { + 'schema': 'avalon-core:asset-2.0', + 'name': name, + 'silo': silo, + 'parent': self.av_project['_id'], + 'type': 'asset' + } + entity_id = self.db.insert_one(item).inserted_id + + return self.db.find_one({'_id': entity_id}) From 64f39193f58069c6967815400f1c61b445169259 Mon Sep 17 00:00:00 2001 From: antirotor Date: Tue, 5 Feb 2019 17:01:15 +0100 Subject: [PATCH 14/46] add ffmpeg validator, error handling --- .../publish/validate_ffmpeg_installed.py | 28 +++++++++++++++++ .../plugins/maya/publish/extract_quicktime.py | 30 ++++++++++--------- 2 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 pype/plugins/global/publish/validate_ffmpeg_installed.py diff --git a/pype/plugins/global/publish/validate_ffmpeg_installed.py b/pype/plugins/global/publish/validate_ffmpeg_installed.py new file mode 100644 index 0000000000..250da0afd9 --- /dev/null +++ b/pype/plugins/global/publish/validate_ffmpeg_installed.py @@ -0,0 +1,28 @@ +import pyblish.api +import os +import subprocess + + +class ValidateFfmpegInstallef(pyblish.api.Validator): + """Validate availability of ffmpeg tool in PATH""" + + order = pyblish.api.ValidatorOrder + label = 'Validate ffmpeg installation' + families = ['review'] + optional = True + + def is_tool(self, name): + try: + devnull = open(os.devnull, "w") + subprocess.Popen( + [name], stdout=devnull, stderr=devnull + ).communicate() + except OSError as e: + if e.errno == os.errno.ENOENT: + return False + return True + + def process(self, instance): + if self.is_tool('ffmpeg') is False: + self.log.error("ffmpeg not found in PATH") + raise RuntimeError('ffmpeg not installed.') diff --git a/pype/plugins/maya/publish/extract_quicktime.py b/pype/plugins/maya/publish/extract_quicktime.py index ca0ae30671..91bfc9284b 100644 --- a/pype/plugins/maya/publish/extract_quicktime.py +++ b/pype/plugins/maya/publish/extract_quicktime.py @@ -1,4 +1,5 @@ import os +import subprocess import contextlib import capture_gui @@ -6,6 +7,7 @@ import clique import pype.maya.lib as lib import pype.api +import avalon.maya from maya import cmds, mel import pymel.core as pm @@ -106,20 +108,20 @@ class ExtractQuicktime(pype.api.Extractor): movieFile = filename + ".mov" full_movie_path = os.path.join(stagingdir, movieFile) self.log.info("output {}".format(full_movie_path)) - - try: - ( - ffmpeg - .input(input_path, framerate=fps, start_number=int(start)) - .output(full_movie_path) - .run(overwrite_output=True, - capture_stdout=True, - capture_stderr=True) - ) - except ffmpeg.Error as e: - ffmpeg_error = 'ffmpeg error: {}'.format(e.stderr.decode()) - self.log.error(ffmpeg_error) - raise Exception(ffmpeg_error) + with avalon.maya.suspended_refresh(): + try: + ( + ffmpeg + .input(input_path, framerate=fps, start_number=int(start)) + .output(full_movie_path) + .run(overwrite_output=True, + capture_stdout=True, + capture_stderr=True) + ) + except ffmpeg.Error as e: + ffmpeg_error = 'ffmpeg error: {}'.format(e.stderr) + self.log.error(ffmpeg_error) + raise RuntimeError(ffmpeg_error) if "files" not in instance.data: instance.data["files"] = list() From d7777ed8b9b123eef74761567f2a08b50021d6c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Feb 2019 17:44:38 +0100 Subject: [PATCH 15/46] get_workdir_template redone so it won't crash if [avalon] in default.toml --- pype/templates.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/templates.py b/pype/templates.py index cf246defb3..36f22ef8cb 100644 --- a/pype/templates.py +++ b/pype/templates.py @@ -266,10 +266,9 @@ def get_workdir_template(data=None): anatomy = api.Anatomy try: - anatomy = anatomy.format(data or get_context_data()) + work = anatomy.work.format(data or get_context_data()) except Exception as e: log.error("{0} Error in " "get_workdir_template(): {1}".format(__name__, e)) - return os.path.join(anatomy.work.root, - anatomy.work.folder) + return os.path.join(work.root, work.folder) From 67cb3f05d5f9d24063ae00b32411a50961a34527 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Feb 2019 17:45:12 +0100 Subject: [PATCH 16/46] templates are loading from anatomy --- pype/lib.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index f0ffba3085..2178e2b1e5 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -424,14 +424,13 @@ def get_avalon_project_template(): """ template = Templates(type=["anatomy"]) proj_template = {} - # TODO this down should work but it can't be in default.toml: - # - Raises error when App (e.g. Nuke) is started - # proj_template['workfile'] = template.anatomy.avalon.workfile - # proj_template['work'] = template.anatomy.avalon.work - # proj_template['publish'] = template.anatomy.avalon.publish - proj_template['workfile'] = "{asset[name]}_{task[name]}_v{version:0>3}<_{comment}>" - proj_template['work'] = "{root}/{project}/{hierarchy}/{asset}/work/{task}" - proj_template['publish'] = "{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}" + proj_template['workfile'] = template.anatomy.avalon.workfile + proj_template['work'] = template.anatomy.avalon.work + proj_template['publish'] = template.anatomy.avalon.publish + # Old - hardcoded = BackUp + # proj_template['workfile'] = "{asset[name]}_{task[name]}_v{version:0>3}<_{comment}>" + # proj_template['work'] = "{root}/{project}/{hierarchy}/{asset}/work/{task}" + # proj_template['publish'] = "{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}" return proj_template From 3c0550029bc8fa8892f0b59bb0475591fcbe0b41 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 5 Feb 2019 17:58:57 +0100 Subject: [PATCH 17/46] return commits of different branch --- .../actions/action_create_cust_attrs.py | 2 + pype/ftrack/actions/action_job_killer.py | 2 + pype/ftrack/actions/action_test.py | 2 + pype/ftrack/lib/avalon_sync.py | 49 +------ pype/ftrack/lib/ftrack_app_handler.py | 2 + .../publish/integrate_avalon_instances.py | 111 --------------- .../publish/integrate_ftrack_instances.py | 127 ------------------ 7 files changed, 12 insertions(+), 283 deletions(-) delete mode 100644 pype/plugins/premiere/publish/integrate_avalon_instances.py delete mode 100644 pype/plugins/premiere/publish/integrate_ftrack_instances.py diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 6fc9916ced..8e926fb313 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -1,3 +1,5 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack import os import sys import argparse diff --git a/pype/ftrack/actions/action_job_killer.py b/pype/ftrack/actions/action_job_killer.py index 9ded00cd37..184053ed47 100644 --- a/pype/ftrack/actions/action_job_killer.py +++ b/pype/ftrack/actions/action_job_killer.py @@ -1,3 +1,5 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack import sys import argparse import logging diff --git a/pype/ftrack/actions/action_test.py b/pype/ftrack/actions/action_test.py index 83c0759406..31bcd4f518 100644 --- a/pype/ftrack/actions/action_test.py +++ b/pype/ftrack/actions/action_test.py @@ -1,3 +1,5 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack import sys import argparse import logging diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 29b9e4a9b4..cb33c76a30 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -53,7 +53,7 @@ def import_to_avalon( if entity_type in ['Project']: type = 'project' - config = get_config_from_ftproject(entity) + config = get_project_config(entity) schema.validate(config) av_project_code = None @@ -440,42 +440,16 @@ def get_avalon_project(ft_project): return avalon_project -def get_config_from_ftproject(entity=None): +def get_project_config(entity): config = {} config['schema'] = pypelib.get_avalon_project_config_schema() + config['tasks'] = [{'name': ''}] + config['apps'] = get_project_apps(entity) config['template'] = pypelib.get_avalon_project_template() - apps = [] - tasks = [{'name': ''}] - if entity is not None: - apps = get_project_apps(entity) - - tasks = [] - if entity.entity_type.lower() != 'project': - project = entity['project'] - else: - project = entity - for task_type in get_all_task_types_ftproj(project): - tasks.append({'name': task_type}) - - config['apps'] = apps - config['tasks'] = tasks - return config -def get_all_task_types_ftproj(project): - tasks = {} - proj_template = project['project_schema'] - temp_task_types = proj_template['_task_type_schema']['types'] - - for type in temp_task_types: - if type['name'] not in tasks: - tasks[type['name']] = type - - return tasks - - def get_project_apps(entity): """ Get apps from project Requirements: @@ -548,18 +522,3 @@ def get_config_data(): log.warning("{} - {}".format(msg, str(e))) return data - - -def get_all_object_type_array(self, session): - # basic excluded types - excludes = ['bug', 'epic', 'feature', 'information'] - # TODO read from presets which also will be excluded - all_obj_types = set() - for obj in session.query('ObjectType').all(): - name = obj['name'] - if ' ' in name: - name = name.replace(' ', '') - if name.lower() in excludes: - continue - all_obj_types.add(name) - return list(all_obj_types) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 804a0b9c17..32ccb96ba6 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -1,3 +1,5 @@ +# :coding: utf-8 +# :copyright: Copyright (c) 2017 ftrack import os import sys import platform diff --git a/pype/plugins/premiere/publish/integrate_avalon_instances.py b/pype/plugins/premiere/publish/integrate_avalon_instances.py deleted file mode 100644 index 9defb6cb48..0000000000 --- a/pype/plugins/premiere/publish/integrate_avalon_instances.py +++ /dev/null @@ -1,111 +0,0 @@ -import pyblish.api -from avalon import io - - -class IntegrateInstancesToAvalon(pyblish.api.InstancePlugin): - """ - Create entities in ftrack based on collected data from premiere - - """ - - order = pyblish.api.IntegratorOrder + 0.48 - label = 'Integrate To Avalon' - families = [] - - exclude = [] - - def process(self, instance): - for ex in self.exclude: - if ex in instance.data['families']: - return - - self.log.debug('instance {}'.format(instance)) - - self.db = io - self.db.install() - # TODO implement how to get right data - all_instances_data = {} - self.import_to_avalon(all_instances_data) - - def import_to_avalon(self, input_data, parent=None): - for name in input_data: - entity_data = input_data[name] - entity_type = entity_data['entity_type'] - - data = {} - # Process project - if entity_type.lower() == 'project': - # TODO: this should be already set if io...? - if self.db.Session['AVALON_PROJECT'] is None: - self.db.Session['AVALON_PROJECT'] = name - - entity = self.db.find_one({'type': 'project'}) - if entity is None: - # TODO: better exception - raise Exception - - # get data from already existing project - for key, value in entity.get('data', {}).items(): - data[key] = value - - self.av_project = entity - # Raise error if project or parent are not set - elif self.av_project is None or parent is None: - # TODO better exception - raise Exception - # Else process assset - else: - entity = self.db.find_one({'type': 'asset', 'name': name}) - # Create entity if doesn't exist - if entity is None: - if self.av_project['_id'] == parent['_id']: - silo = None - elif parent['silo'] is None: - silo = parent['name'] - else: - silo = parent['silo'] - entity = self.create_avalon_asset(name, silo) - # Else get data from already existing - else: - for key, value in entity.get('data', {}).items(): - data[key] = value - - data['entityType'] = entity_type - # TASKS - tasks = entity_data.get('tasks', []) - data['tasks'] = tasks - parents = [] - visualParent = None - # do not store project's id as visualParent (silo asset) - if self.av_project['_id'] != parent['_id']: - visualParent = parent['_id'] - parents.extend(parent['data']['parents']) - parents.append(parent['name']) - data['visualParent'] = visualParent - data['parents'] = parents - - # CUSTOM ATTRIBUTES - for key, value in entity_data.get('custom_attributes', {}).items(): - data[key] = value - - # Update entity data with input data - self.db.update_many( - {'_id': entity['_id']}, - {'$set': { - 'data': data, - }}) - - if 'childs' in entity_data: - self.import_to_ftrack(entity_data['childs'], entity) - - def create_avalon_asset(self, name, silo): - item = { - 'schema': 'avalon-core:asset-2.0', - 'name': name, - 'silo': silo, - 'parent': self.av_project['_id'], - 'type': 'asset' - } - entity_id = self.db.insert_one(item).inserted_id - - return self.db.find_one({'_id': entity_id}) diff --git a/pype/plugins/premiere/publish/integrate_ftrack_instances.py b/pype/plugins/premiere/publish/integrate_ftrack_instances.py deleted file mode 100644 index 155a8d2f8b..0000000000 --- a/pype/plugins/premiere/publish/integrate_ftrack_instances.py +++ /dev/null @@ -1,127 +0,0 @@ -import pyblish.api - - -class IntegrateInstancesToFtrack(pyblish.api.InstancePlugin): - """ - Create entities in ftrack based on collected data from premiere - - """ - - order = pyblish.api.IntegratorOrder + 0.48 - label = 'Integrate To Ftrack' - families = ["ftrack"] - - exclude = [] - - def process(self, instance): - for ex in self.exclude: - if ex in instance.data['families']: - return - - self.log.debug('instance {}'.format(instance)) - - self.ft_project = None - self.session = instance.context.data["ftrackSession"] - - # TODO implement how to get right data - all_instances_data = {} - self.import_to_ftrack(all_instances_data) - - def import_to_ftrack(self, input_data, parent=None): - for entity_name in input_data: - entity_data = input_data[entity_name] - entity_type = entity_data['entity_type'] - - if entity_type.lower() == 'project': - query = 'Project where full_name is "{}"'.format(entity_name) - entity = self.session.query(query).one() - self.ft_project = entity - self.task_types = self.get_all_task_types(entity) - - elif self.ft_project is None: - # TODO better exception - raise Exception - elif parent is None: - # TODO better exception - raise Exception - else: - query = '{} where name is "{}" and parent_id is "{}"'.format( - entity_type, entity_name, parent['id'] - ) - try: - entity = self.session.query(query).one() - except Exception: - entity = None - - # Create entity if not exists - if entity is None: - entity = self.create_entity( - name=entity_name, - type=entity_type, - parent=parent - ) - # CUSTOM ATTRIBUTES - custom_attributes = entity_data.get('custom_attributes', []) - for key in custom_attributes: - if key not in entity['custom_attributes']: - # TODO better exception - raise Exception - entity['custom_attributes'][key] = custom_attributes[key] - self.session.commit() - - # TASKS - tasks = entity_data.get('tasks', []) - existing_tasks = [] - tasks_to_create = [] - for child in entity['children']: - if child.entity_type.lower() == 'task': - existing_tasks.append(child['type']['name']) - - for task in tasks: - if task in existing_tasks: - print("Task {} already exists".format(task)) - continue - tasks_to_create.append(task) - - for task in tasks_to_create: - self.create_task( - name=task, - task_type=task, - parent=entity - ) - self.session.commit() - - if 'childs' in entity_data: - self.import_to_ftrack(entity_data['childs'], entity) - - def get_all_task_types(self, project): - tasks = {} - proj_template = project['project_schema'] - temp_task_types = proj_template['_task_type_schema']['types'] - - for type in temp_task_types: - if type['name'] not in tasks: - tasks[type['name']] = type - - return tasks - - def create_task(self, name, task_type, parent): - task = self.session.create('Task', { - 'name': name, - 'parent': parent - }) - # TODO not secured!!! - check if task_type exists - task['type'] = self.task_types[task_type] - - self.session.commit() - - return task - - def create_entity(self, name, type, parent): - entity = self.session.create(type, { - 'name': name, - 'parent': parent - }) - self.session.commit() - - return entity From 5dffcc459b73da636864e4c357e5783bc736e1c7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 6 Feb 2019 19:46:23 +0100 Subject: [PATCH 18/46] first verision of update tamplates action --- .../ftrack/actions/action_update_templates.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 pype/ftrack/actions/action_update_templates.py diff --git a/pype/ftrack/actions/action_update_templates.py b/pype/ftrack/actions/action_update_templates.py new file mode 100644 index 0000000000..7c881d7a5e --- /dev/null +++ b/pype/ftrack/actions/action_update_templates.py @@ -0,0 +1,109 @@ +import sys +import argparse +import logging + +import ftrack_api +from pype.ftrack import BaseAction +from avalon import io +import pype + +ignore_me = True + + +class UpdateTemplates(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'update.templates' + #: Action label. + label = 'UpdateTemplates' + #: Action description. + description = 'Updates templates in project' + #: priority + priority = 10000 + + def discover(self, session, entities, event): + ''' Validation ''' + discover = False + roleList = ['Pypeclub'] + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + for role in user['user_security_roles']: + if role['security_role']['name'] in roleList: + discover = True + break + + return discover + + def launch(self, session, entities, event): + anatomy = pype.Anatomy + io.install() + for project in io.projects(): + io.Session["AVALON_PROJECT"] = project["name"] + io.update_many( + {'type': 'project'}, + {'$set': { + 'config.template.workfile': anatomy.avalon.workfile, + 'config.template.work': anatomy.avalon.work, + 'config.template.publish': anatomy.avalon.publish, + }} + ) + + io.update_many( + {'type': 'representation'}, + {'$set': { + 'data.template': anatomy.avalon.publish + }} + ) + + return True + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = UpdateTemplates(session) + action_handler.register() + + +def main(arguments=None): + '''Set up logging and register action.''' + if arguments is None: + arguments = [] + + parser = argparse.ArgumentParser() + # Allow setting of logging level from arguments. + loggingLevels = {} + for level in ( + logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, + logging.ERROR, logging.CRITICAL + ): + loggingLevels[logging.getLevelName(level).lower()] = level + + parser.add_argument( + '-v', '--verbosity', + help='Set the logging output verbosity.', + choices=loggingLevels.keys(), + default='info' + ) + namespace = parser.parse_args(arguments) + + # Set up basic logging + logging.basicConfig(level=loggingLevels[namespace.verbosity]) + + session = ftrack_api.Session() + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) From e0de3a1447e202987cedc1cf12a0f1ec9659f599 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 6 Feb 2019 19:46:39 +0100 Subject: [PATCH 19/46] project and projectcode changed to project dict --- pype/plugins/global/publish/integrate_rendered_frames.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index 2ee28797e7..d24536c17f 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -251,9 +251,11 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # for performance reasons. "context": { "root": root, - "project": PROJECT, - "projectcode": project['data']['code'], - 'task': api.Session["AVALON_TASK"], + "project": { + "name": PROJECT, + "code": project['data']['code'] + }, + "task": api.Session["AVALON_TASK"], "silo": asset['silo'], "asset": ASSET, "family": instance.data['family'], From f43b1d8cde8ab9b76c6e78e0958c518957da485e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 6 Feb 2019 19:59:00 +0100 Subject: [PATCH 20/46] hierarchy is set to empty string if no parents --- pype/ftrack/lib/avalon_sync.py | 4 +++- pype/ftrack/lib/ftrack_app_handler.py | 7 ++++--- pype/plugins/global/publish/integrate.py | 10 +++++++--- .../global/publish/integrate_rendered_frames.py | 7 ++++--- pype/templates.py | 8 +++++--- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index cb33c76a30..346bdceeb9 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -406,7 +406,9 @@ def get_data(entity, session, custom_attributes): {'type': 'asset', 'name': parName} )['_id'] - hierarchy = os.path.sep.join(folderStruct) + hierarchy = "" + if len(folderStruct) > 0: + hierarchy = os.path.sep.join(folderStruct) data['visualParent'] = parentId data['parents'] = folderStruct diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 32ccb96ba6..422d3b96dc 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -180,13 +180,14 @@ class AppAction(BaseHandler): os.environ["AVALON_APP_NAME"] = self.identifier anatomy = pype.Anatomy - hierarchy = database[project_name].find_one({ + hierarchy = "" + parents = database[project_name].find_one({ "type": 'asset', "name": entity['parent']['name'] })['data']['parents'] - if hierarchy: - hierarchy = os.path.join(*hierarchy) + if parents: + hierarchy = os.path.join(*parents) data = {"project": {"name": entity['project']['full_name'], "code": entity['project']['name']}, diff --git a/pype/plugins/global/publish/integrate.py b/pype/plugins/global/publish/integrate.py index f7c14d990c..6a9dde90df 100644 --- a/pype/plugins/global/publish/integrate.py +++ b/pype/plugins/global/publish/integrate.py @@ -141,10 +141,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # \|________| # root = api.registered_root() - hierarchy = io.find_one({"type": 'asset', "name": ASSET})['data']['parents'] - if hierarchy: + hierarchy = "" + parents = io.find_one({ + "type": 'asset', + "name": ASSET + })['data']['parents'] + if parents and len(parents) > 0: # hierarchy = os.path.sep.join(hierarchy) - hierarchy = os.path.join(*hierarchy) + hierarchy = os.path.join(*parents) template_data = {"root": root, "project": {"name": PROJECT, diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index 2ee28797e7..05702c47af 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -139,11 +139,12 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # \|________| # root = api.registered_root() - hierarchy = io.find_one({"type": 'asset', "name": ASSET})[ + hierarchy = "" + parents = io.find_one({"type": 'asset', "name": ASSET})[ 'data']['parents'] - if hierarchy: + if parents and len(parents) > 0: # hierarchy = os.path.sep.join(hierarchy) - hierarchy = os.path.join(*hierarchy) + hierarchy = os.path.join(*parents) template_data = {"root": root, "project": {"name": PROJECT, diff --git a/pype/templates.py b/pype/templates.py index cf246defb3..af106396dd 100644 --- a/pype/templates.py +++ b/pype/templates.py @@ -167,14 +167,16 @@ def get_hierarchy(): string: asset hierarchy path """ - hierarchy = io.find_one({ + parents = io.find_one({ "type": 'asset', "name": get_asset()} )['data']['parents'] - if hierarchy: + hierarchy = "" + if parents and len(parents) > 0: # hierarchy = os.path.sep.join(hierarchy) - return os.path.join(*hierarchy).replace("\\", "/") + hierarchy = os.path.join(*parents).replace("\\", "/") + return hierarchy def set_hierarchy(hierarchy): From 8d28c4af448b2d5859a55dba9f2934e7b4488124 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 6 Feb 2019 21:18:56 +0100 Subject: [PATCH 21/46] fix version checker, and other nuke fixes --- pype/api.py | 4 +-- pype/nuke/lib.py | 8 +++--- pype/plugins/global/publish/collect_json.py | 2 +- .../global/publish/collect_scene_version.py | 23 +++++++++++++++ pype/plugins/global/publish/extract_json.py | 1 + .../publish/integrate_rendered_frames.py | 5 +++- .../validate_write_families.py | 22 +++++++++------ pype/plugins/nuke/create/create_write.py | 28 +++++++++---------- pype/plugins/nuke/load/load_sequence.py | 2 +- .../nuke/publish/collect_current_file.py | 2 +- pype/plugins/nuke/publish/collect_writes.py | 6 ++++ ...version.py => increment_script_version.py} | 9 +++--- .../nuke/publish/validate_collection.py | 4 ++- .../nuke/publish/validate_version_match.py | 15 ++++++++++ pype/templates.py | 2 +- 15 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 pype/plugins/global/publish/collect_scene_version.py rename pype/plugins/nuke/publish/{integrate_script_version.py => increment_script_version.py} (72%) create mode 100644 pype/plugins/nuke/publish/validate_version_match.py diff --git a/pype/api.py b/pype/api.py index fd4a1ca1d2..747ad425f8 100644 --- a/pype/api.py +++ b/pype/api.py @@ -33,7 +33,7 @@ from .templates import ( get_asset, get_task, set_avalon_workdir, - get_version_from_workfile, + get_version_from_path, get_workdir_template, set_hierarchy, set_project_code @@ -77,7 +77,7 @@ __all__ = [ "get_asset", "get_task", "set_avalon_workdir", - "get_version_from_workfile", + "get_version_from_path", "get_workdir_template", "modified_environ", "add_tool_to_environment", diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 183e545ddb..f5b385cfe0 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -35,14 +35,14 @@ def writes_version_sync(): for each in nuke.allNodes(): if each.Class() == 'Write': avalon_knob_data = get_avalon_knob_data(each) - # if avalon_knob_data['families'] not in ["render"]: - # log.info(avalon_knob_data['families']) - # continue + if avalon_knob_data['families'] not in ["render"]: + log.info(avalon_knob_data['families']) + continue try: node_file = each['file'].value() log.info("node_file: {}".format(node_file)) - node_version = pype.get_version_from_path(node_file, None) + node_version = pype.get_version_from_path(node_file) log.info("node_version: {}".format(node_version)) node_new_file = node_file.replace(node_version, new_version) diff --git a/pype/plugins/global/publish/collect_json.py b/pype/plugins/global/publish/collect_json.py index 1cf79f4612..ba5fc29d12 100644 --- a/pype/plugins/global/publish/collect_json.py +++ b/pype/plugins/global/publish/collect_json.py @@ -27,7 +27,7 @@ class CollectJSON(pyblish.api.ContextPlugin): return matches[-1:][0][1], re.search(r"\d+", matches[-1:][0]).group() def process(self, context): - current_file = context.data("currentFile") + current_file = context.data.get("currentFile", '') # Skip if current file is not a directory if not os.path.isdir(current_file): return diff --git a/pype/plugins/global/publish/collect_scene_version.py b/pype/plugins/global/publish/collect_scene_version.py new file mode 100644 index 0000000000..06bc8e3a53 --- /dev/null +++ b/pype/plugins/global/publish/collect_scene_version.py @@ -0,0 +1,23 @@ +import os +import pyblish.api +import os +import pype.api as pype + +class CollectSceneVersion(pyblish.api.ContextPlugin): + """Finds version in the filename or passes the one found in the context + Arguments: + version (int, optional): version number of the publish + """ + + order = pyblish.api.CollectorOrder + label = 'Collect Version' + + def process(self, context): + + filename = os.path.basename(context.data.get('currentFile')) + + rootVersion = pype.get_version_from_path(filename) + + context.data['version'] = rootVersion + + self.log.info('Scene Version: %s' % context.data('version')) diff --git a/pype/plugins/global/publish/extract_json.py b/pype/plugins/global/publish/extract_json.py index fa7f95147b..e218776638 100644 --- a/pype/plugins/global/publish/extract_json.py +++ b/pype/plugins/global/publish/extract_json.py @@ -25,6 +25,7 @@ class ExtractJSON(pyblish.api.ContextPlugin): output_data = [] for instance in context: + self.log.debug(instance['data']) data = {} for key, value in instance.data.iteritems(): diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index 8f8766a297..08ddbddd99 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -117,6 +117,9 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "('v{1:03d}')".format(assumed_version, next_version)) + if instance.data.get('version'): + next_version = int(instance.data.get('version')) + self.log.debug("Next version: v{0:03d}".format(next_version)) version_data = self.create_version_data(context, instance) @@ -261,7 +264,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "VERSION": version["name"], + "version": version["name"], "hierarchy": hierarchy, "representation": ext[1:] } diff --git a/pype/plugins/nuke/_publish_unused/validate_write_families.py b/pype/plugins/nuke/_publish_unused/validate_write_families.py index 087fb3be5e..73f710867d 100644 --- a/pype/plugins/nuke/_publish_unused/validate_write_families.py +++ b/pype/plugins/nuke/_publish_unused/validate_write_families.py @@ -25,18 +25,24 @@ class ValidateWriteFamilies(pyblish.api.InstancePlugin): families = ["write"] actions = [pype.nuke.actions.SelectInvalidAction, pype.api.RepairAction] - # @staticmethod - # def get_invalid(instance): - # for f in instance.data["families"]: - # if ".frames" in f: - # return - # - # if not instance.data["files"]: - # return (instance) + @staticmethod + def get_invalid(self, instance): + if not [f for f in instance.data["families"] + if ".frames" in f]: + return + + if not instance.data.get('files'): + return (instance) def process(self, instance): self.log.debug('instance.data["files"]: {}'.format(instance.data['files'])) + invalid = self.get_invalid(self, instance) + + if invalid: + raise ValueError(str("`{}`: Switch `Render` on! " + "> {}".format(__name__, invalid))) + # if any(".frames" in f for f in instance.data["families"]): # if not instance.data["files"]: # raise ValueError("instance {} is set to publish frames\ diff --git a/pype/plugins/nuke/create/create_write.py b/pype/plugins/nuke/create/create_write.py index c1b492ac2e..af7462680e 100644 --- a/pype/plugins/nuke/create/create_write.py +++ b/pype/plugins/nuke/create/create_write.py @@ -25,7 +25,7 @@ class CrateWriteRender(avalon.nuke.Creator): name = "WriteRender" label = "Create Write Render" hosts = ["nuke"] - family = "{}_write".format(preset) + family = "write" families = preset icon = "sign-out" @@ -34,7 +34,7 @@ class CrateWriteRender(avalon.nuke.Creator): data = OrderedDict() - data["family"] = self.family.split("_")[1] + data["family"] = self.family data["families"] = self.families {data.update({k: v}) for k, v in self.data.items() @@ -44,15 +44,15 @@ class CrateWriteRender(avalon.nuke.Creator): def process(self): self.name = self.data["subset"] - family = self.family.split("_")[0] - node = self.family.split("_")[1] + family = self.family + node = 'write' instance = nuke.toNode(self.data["subset"]) if not instance: write_data = { "class": node, - "preset": family, + "preset": self.preset, "avalon": self.data } @@ -68,7 +68,7 @@ class CrateWritePrerender(avalon.nuke.Creator): name = "WritePrerender" label = "Create Write Prerender" hosts = ["nuke"] - family = "{}_write".format(preset) + family = "write" families = preset icon = "sign-out" @@ -89,13 +89,13 @@ class CrateWritePrerender(avalon.nuke.Creator): instance = nuke.toNode(self.data["subset"]) - family = self.family.split("_")[0] - node = self.family.split("_")[1] + family = self.family + node = 'write' if not instance: write_data = { "class": node, - "preset": family, + "preset": self.preset, "avalon": self.data } @@ -111,7 +111,7 @@ class CrateWriteStill(avalon.nuke.Creator): name = "WriteStill" label = "Create Write Still" hosts = ["nuke"] - family = "{}_write".format(preset) + family = "write" families = preset icon = "image" @@ -120,7 +120,7 @@ class CrateWriteStill(avalon.nuke.Creator): data = OrderedDict() - data["family"] = self.family.split("_")[1] + data["family"] = self.family data["families"] = self.families {data.update({k: v}) for k, v in self.data.items() @@ -132,14 +132,14 @@ class CrateWriteStill(avalon.nuke.Creator): instance = nuke.toNode(self.data["subset"]) - family = self.family.split("_")[0] - node = self.family.split("_")[1] + family = self.family + node = 'write' if not instance: write_data = { "frame_range": [nuke.frame(), nuke.frame()], "class": node, - "preset": family, + "preset": self.preset, "avalon": self.data } diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 23575a3f28..a4a591e657 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -76,7 +76,7 @@ class LoadSequence(api.Loader): """Load image sequence into Nuke""" families = ["write", "source"] - representations = ["exr"] + representations = ["exr", "dpx"] label = "Load sequence" order = -10 diff --git a/pype/plugins/nuke/publish/collect_current_file.py b/pype/plugins/nuke/publish/collect_current_file.py index 96ec44d9d6..35a0ef4c2a 100644 --- a/pype/plugins/nuke/publish/collect_current_file.py +++ b/pype/plugins/nuke/publish/collect_current_file.py @@ -4,7 +4,7 @@ import pyblish.api class SelectCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder - 0.5 hosts = ["nuke"] def process(self, context): diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 89f78367a9..df9666b8ca 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -3,6 +3,7 @@ import tempfile import nuke import pyblish.api import logging +import pype.api as pype log = logging.getLogger(__name__) @@ -50,6 +51,11 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): output_dir = os.path.dirname(path) self.log.debug('output dir: {}'.format(output_dir)) + # get version + version = pype.get_version_from_path(path) + instance.data['version'] = version + self.log.debug('Write Version: %s' % instance.data('version')) + # create label name = node.name() # Include start and end render frame in label diff --git a/pype/plugins/nuke/publish/integrate_script_version.py b/pype/plugins/nuke/publish/increment_script_version.py similarity index 72% rename from pype/plugins/nuke/publish/integrate_script_version.py rename to pype/plugins/nuke/publish/increment_script_version.py index 09dfeb4e7d..77eab30a63 100644 --- a/pype/plugins/nuke/publish/integrate_script_version.py +++ b/pype/plugins/nuke/publish/increment_script_version.py @@ -7,14 +7,15 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): """Increment current script version.""" order = pyblish.api.IntegratorOrder + 0.9 - label = "Increment Current Script Version" + label = "Increment Script Version" optional = True hosts = ['nuke'] - families = ["nukescript", "render.local", "render.frames"] def process(self, context): - # return - # + + assert all(result["success"] for result in context.data["results"]), ( + "Atomicity not held, aborting.") + from pype.lib import version_up path = context.data["currentFile"] nuke.scriptSaveAs(version_up(path)) diff --git a/pype/plugins/nuke/publish/validate_collection.py b/pype/plugins/nuke/publish/validate_collection.py index a99e930661..c402927373 100644 --- a/pype/plugins/nuke/publish/validate_collection.py +++ b/pype/plugins/nuke/publish/validate_collection.py @@ -30,9 +30,11 @@ class ValidatePrerenderedFrames(pyblish.api.InstancePlugin): hosts = ["nuke"] actions = [RepairCollectionAction] + def process(self, instance): self.log.debug('instance.data["files"]: {}'.format(instance.data['files'])) - assert instance.data["files"], "No frames have been collected" + + assert instance.data.get('files'), "no frames were collected, you need to render them" collections, remainder = clique.assemble(*instance.data['files']) self.log.info('collections: {}'.format(str(collections))) diff --git a/pype/plugins/nuke/publish/validate_version_match.py b/pype/plugins/nuke/publish/validate_version_match.py new file mode 100644 index 0000000000..64646ea5dc --- /dev/null +++ b/pype/plugins/nuke/publish/validate_version_match.py @@ -0,0 +1,15 @@ +import pyblish.api + + +class ValidateVersionMatch(pyblish.api.InstancePlugin): + """Checks if write version matches workfile version""" + + label = "Validate Version Match" + order = pyblish.api.ValidatorOrder + hosts = ["nuke"] + families = ['render.frames'] + + def process(self, instance): + + assert instance.data['version'] == instance.context.data['version'], "\ + Version in write doesn't match version of the workfile" diff --git a/pype/templates.py b/pype/templates.py index cf246defb3..5fe41f6899 100644 --- a/pype/templates.py +++ b/pype/templates.py @@ -58,7 +58,7 @@ def reset_data_from_templates(): log.info("Data from templates were Unloaded...") -def get_version_from_workfile(file): +def get_version_from_path(file): """ Finds version number in file path string From 11939bdf152f60b943c36b202b100a5761d86591 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 7 Feb 2019 14:33:18 +0100 Subject: [PATCH 22/46] project templates are loaded from templates during sync to avalon --- pype/lib.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index f0ffba3085..368ddad024 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -424,14 +424,9 @@ def get_avalon_project_template(): """ template = Templates(type=["anatomy"]) proj_template = {} - # TODO this down should work but it can't be in default.toml: - # - Raises error when App (e.g. Nuke) is started - # proj_template['workfile'] = template.anatomy.avalon.workfile - # proj_template['work'] = template.anatomy.avalon.work - # proj_template['publish'] = template.anatomy.avalon.publish - proj_template['workfile'] = "{asset[name]}_{task[name]}_v{version:0>3}<_{comment}>" - proj_template['work'] = "{root}/{project}/{hierarchy}/{asset}/work/{task}" - proj_template['publish'] = "{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}" + proj_template['workfile'] = template.anatomy.avalon.workfile + proj_template['work'] = template.anatomy.avalon.work + proj_template['publish'] = template.anatomy.avalon.publish return proj_template From a8745ed130160f61d439b1847aa2809fa7389beb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 7 Feb 2019 15:34:55 +0100 Subject: [PATCH 23/46] VERSION removed and replaced by version --- pype/plugins/ftrack/integrate_ftrack_instances.py | 2 +- pype/plugins/global/publish/collect_assumed_destination.py | 4 ++-- pype/plugins/global/publish/integrate.py | 4 ++-- pype/plugins/global/publish/integrate_rendered_frames.py | 6 +++--- pype/plugins/global/publish/validate_templates.py | 6 ++---- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pype/plugins/ftrack/integrate_ftrack_instances.py b/pype/plugins/ftrack/integrate_ftrack_instances.py index 491428a33d..89246397d4 100644 --- a/pype/plugins/ftrack/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/integrate_ftrack_instances.py @@ -36,7 +36,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): self.log.debug('instance {}'.format(instance)) assumed_data = instance.data["assumedTemplateData"] - assumed_version = assumed_data["VERSION"] + assumed_version = assumed_data["version"] version_number = int(assumed_version) family = instance.data['family'].lower() asset_type = '' diff --git a/pype/plugins/global/publish/collect_assumed_destination.py b/pype/plugins/global/publish/collect_assumed_destination.py index 2343861efc..d5d3d9a846 100644 --- a/pype/plugins/global/publish/collect_assumed_destination.py +++ b/pype/plugins/global/publish/collect_assumed_destination.py @@ -108,7 +108,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): # if there is a subset there ought to be version if version is not None: - version_number += version["name"] + version_number += int(version["name"]) hierarchy = asset['data']['parents'] if hierarchy: @@ -122,7 +122,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): "family": instance.data['family'], "asset": asset_name, "subset": subset_name, - "VERSION": version_number, + "version": version_number, "hierarchy": hierarchy, "representation": "TEMP"} diff --git a/pype/plugins/global/publish/integrate.py b/pype/plugins/global/publish/integrate.py index f7c14d990c..904b6848ae 100644 --- a/pype/plugins/global/publish/integrate.py +++ b/pype/plugins/global/publish/integrate.py @@ -112,7 +112,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.info("Verifying version from assumed destination") assumed_data = instance.data["assumedTemplateData"] - assumed_version = assumed_data["VERSION"] + assumed_version = assumed_data["version"] if assumed_version != next_version: raise AttributeError("Assumed version 'v{0:03d}' does not match" "next version in database " @@ -153,7 +153,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "VERSION": version["name"], + "version": int(version["name"]), "hierarchy": hierarchy} template_publish = project["config"]["template"]["publish"] diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index d24536c17f..9600cad887 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -110,7 +110,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): self.log.info("Verifying version from assumed destination") assumed_data = instance.data["assumedTemplateData"] - assumed_version = assumed_data["VERSION"] + assumed_version = assumed_data["version"] if assumed_version != next_version: raise AttributeError("Assumed version 'v{0:03d}' does not match" "next version in database " @@ -153,7 +153,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "VERSION": version["name"], + "version": int(version["name"]), "hierarchy": hierarchy} # template_publish = project["config"]["template"]["publish"] @@ -260,7 +260,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "VERSION": version["name"], + "version": int(version["name"]), "hierarchy": hierarchy, "representation": ext[1:] } diff --git a/pype/plugins/global/publish/validate_templates.py b/pype/plugins/global/publish/validate_templates.py index f806104bb2..8f8eb45686 100644 --- a/pype/plugins/global/publish/validate_templates.py +++ b/pype/plugins/global/publish/validate_templates.py @@ -19,8 +19,7 @@ class ValidateTemplates(pyblish.api.ContextPlugin): data = { "project": {"name": "D001_projectsx", "code": "prjX"}, "representation": "exr", - "VERSION": 3, - "SUBVERSION": 10, + "version": 3, "task": "animation", "asset": "sh001", "hierarchy": "ep101/sq01/sh010"} @@ -32,8 +31,7 @@ class ValidateTemplates(pyblish.api.ContextPlugin): data = { "project": {"name": "D001_projectsy", "code": "prjY"}, "representation": "abc", - "VERSION": 1, - "SUBVERSION": 5, + "version": 1, "task": "lookdev", "asset": "bob", "hierarchy": "ep101/sq01/bob"} From 1e96f1ad1aa66d36f390a9ad101103890af219e0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 7 Feb 2019 15:35:44 +0100 Subject: [PATCH 24/46] removed unnecessary action from ftrack --- .../ftrack/actions/action_update_templates.py | 109 ------------------ 1 file changed, 109 deletions(-) delete mode 100644 pype/ftrack/actions/action_update_templates.py diff --git a/pype/ftrack/actions/action_update_templates.py b/pype/ftrack/actions/action_update_templates.py deleted file mode 100644 index 7c881d7a5e..0000000000 --- a/pype/ftrack/actions/action_update_templates.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys -import argparse -import logging - -import ftrack_api -from pype.ftrack import BaseAction -from avalon import io -import pype - -ignore_me = True - - -class UpdateTemplates(BaseAction): - '''Edit meta data action.''' - - #: Action identifier. - identifier = 'update.templates' - #: Action label. - label = 'UpdateTemplates' - #: Action description. - description = 'Updates templates in project' - #: priority - priority = 10000 - - def discover(self, session, entities, event): - ''' Validation ''' - discover = False - roleList = ['Pypeclub'] - userId = event['source']['user']['id'] - user = session.query('User where id is ' + userId).one() - - for role in user['user_security_roles']: - if role['security_role']['name'] in roleList: - discover = True - break - - return discover - - def launch(self, session, entities, event): - anatomy = pype.Anatomy - io.install() - for project in io.projects(): - io.Session["AVALON_PROJECT"] = project["name"] - io.update_many( - {'type': 'project'}, - {'$set': { - 'config.template.workfile': anatomy.avalon.workfile, - 'config.template.work': anatomy.avalon.work, - 'config.template.publish': anatomy.avalon.publish, - }} - ) - - io.update_many( - {'type': 'representation'}, - {'$set': { - 'data.template': anatomy.avalon.publish - }} - ) - - return True - - -def register(session, **kw): - '''Register plugin. Called when used as an plugin.''' - - if not isinstance(session, ftrack_api.session.Session): - return - - action_handler = UpdateTemplates(session) - action_handler.register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From ddfde40964db577d758f3351305e3eb2b762fd2e Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 7 Feb 2019 17:01:54 +0100 Subject: [PATCH 25/46] fix standin updating --- pype/plugins/maya/load/load_ass.py | 63 ++++++++++++++++++++- pype/plugins/maya/publish/collect_review.py | 6 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py index 814639a4d9..b27cd20b5b 100644 --- a/pype/plugins/maya/load/load_ass.py +++ b/pype/plugins/maya/load/load_ass.py @@ -1,6 +1,7 @@ from avalon import api import pype.maya.plugin import os +import pymel.core as pm class AssProxyLoader(pype.maya.plugin.ReferenceLoader): @@ -37,7 +38,6 @@ class AssProxyLoader(pype.maya.plugin.ReferenceLoader): # Set attributes proxyShape = pm.ls(nodes, type="mesh")[0] - proxyShape = pm.ls(nodes, type="mesh")[0] proxyShape.aiTranslator.set('procedural') proxyShape.dso.set(path) @@ -51,6 +51,67 @@ class AssProxyLoader(pype.maya.plugin.ReferenceLoader): def switch(self, container, representation): self.update(container, representation) + def update(self, container, representation): + + import os + from maya import cmds + + node = container["objectName"] + + path = api.get_representation_path(representation) + # path = self.fname + proxyPath = os.path.splitext(path)[0] + ".ma" + + # Get reference node from container members + members = cmds.sets(node, query=True, nodesOnly=True) + reference_node = self._get_reference_node(members) + + assert os.path.exists(path), "%s does not exist." % proxyPath + + try: + content = cmds.file(proxyPath, + loadReference=reference_node, + type="mayaAscii", + returnNewNodes=True) + + # Set attributes + proxyShape = pm.ls(content, type="mesh")[0] + + proxyShape.aiTranslator.set('procedural') + proxyShape.dso.set(path) + proxyShape.aiOverrideShaders.set(0) + + except RuntimeError as exc: + # When changing a reference to a file that has load errors the + # command will raise an error even if the file is still loaded + # correctly (e.g. when raising errors on Arnold attributes) + # When the file is loaded and has content, we consider it's fine. + if not cmds.referenceQuery(reference_node, isLoaded=True): + raise + + content = cmds.referenceQuery(reference_node, + nodes=True, + dagPath=True) + if not content: + raise + + self.log.warning("Ignoring file read error:\n%s", exc) + + # Add new nodes of the reference to the container + cmds.sets(content, forceElement=node) + + # Remove any placeHolderList attribute entries from the set that + # are remaining from nodes being removed from the referenced file. + members = cmds.sets(node, query=True) + invalid = [x for x in members if ".placeHolderList" in x] + if invalid: + cmds.sets(invalid, remove=node) + + # Update metadata + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + class AssStandinLoader(api.Loader): """Load .ASS file as standin""" diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 7fb0f92866..6ddb550a99 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -44,11 +44,15 @@ class CollectReviewData(pyblish.api.InstancePlugin): for inst in context: self.log.debug('instance: {}'.format(instance)) if inst.name == reviewable_subset[0]: - inst.data['families'].append('review') + if inst.data.get('families'): + inst.data['families'].append('review') + else: + inst.data['families'] = ['review'] inst.data['review_camera'] = camera self.log.info('adding review family to {}'.format(reviewable_subset)) cmds.setAttr(str(instance) + '.active', 0) inst.data['publish'] = 0 + inst.data['active'] = 0 else: instance.data['subset'] = task + 'Review' instance.data['review_camera'] = camera From 397e041953ea3910114a9635ff2d908b29fc3920 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 7 Feb 2019 18:54:19 +0100 Subject: [PATCH 26/46] remove colorbleed logo --- pype/maya/customize.py | 20 ++++++++++---------- res/icons/colorbleed_logo_36x36.png | Bin 20198 -> 0 bytes 2 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 res/icons/colorbleed_logo_36x36.png diff --git a/pype/maya/customize.py b/pype/maya/customize.py index 46c9ceb652..872942bfd9 100644 --- a/pype/maya/customize.py +++ b/pype/maya/customize.py @@ -134,16 +134,16 @@ def override_toolbox_ui(): parent=parent) controls.append(control) - control = mc.iconTextButton( - "pype_toolbox", - annotation="Colorbleed", - label="Colorbleed", - image=os.path.join(icons, "pype_logo_36x36.png"), - bgc=background_color, - width=icon_size, - height=icon_size, - parent=parent) - controls.append(control) + # control = mc.iconTextButton( + # "pype_toolbox", + # annotation="Kredenc", + # label="Kredenc", + # image=os.path.join(icons, "kredenc_logo.png"), + # bgc=background_color, + # width=icon_size, + # height=icon_size, + # parent=parent) + # controls.append(control) # Add the buttons on the bottom and stack # them above each other with side padding diff --git a/res/icons/colorbleed_logo_36x36.png b/res/icons/colorbleed_logo_36x36.png deleted file mode 100644 index 847a85c2282d4acd024bc8d719877c09660581df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20198 zcmeHPdvH|ebwBs+d-ax(0Aa+k0f*OWclBCH7NQv00XxFf;2L)(ZC1N0*dl2aX)&@h z(>Ter)BMqNCevv%aobGWNvG3s(@duAB!RJ=I@l$lWhP?`#E6H4ct{9@q}9If_IK_+ zv|QkZHtEEF+;2F0@AsYWJLmWP&N<(``s4DoeS1GsX05jpQQ4l|Jul+AfyHdZ?^XKd zH*v8H?|x;JsHBEPpl|)vDx!|t!2<`w2YWx;=?e`udi|lkK;zipFlG~V?HC*O`i=y` zw!Xlj;Lvva-Q(}rZ9)Hb`^&ApuHNBYfy2Swua5-wzrOc?@AV_T4!?azx3z1m69xtY zVXtj$aA0V(b8Nd^w%dt&9(LMo@|5tA?e?AQ&~~tQpKVuYBw%Z8Y;*WrZ62GaqtV^! z@w9lh*_vGLR;R1k+3a#OHFvhSI$N4-xn}RS;;w7N-{1LS&$Bsq=xw(j4u^+3oz7#& zjx`=@ZVZhaa=JS@I-IU1XH$~{GaRF@4TZg9j-k;{WsT(R^aMtIBf;TtFf?RiyWYOg z(eQS=ogEcgZeN4L1xG`pIqtxsbId#JbT_)34_oB-71j(N9T||J`F+m7KwvO16duJo z_e0kW9}a~>qlZI}a9L14(lKJv+gtGcP`rbK4|P2ne&$tBkfZdWD@PB!HXLxi7#Iy5 z9q|R8c@8@(Zau>UosjV2^?YiDbFv5lgcN*kyjzc=iCEGxxxi|qJ)o&BMaL2tM_ zIOshTa1IY0>T)g*7R@YVRp+kIKxhQ80^QAB&W8;bt&%gXbI;Id*gND4?CI&oKw~iI z@AL)Q+PvT$HYTLO-bX0O-L*Wc3A=l1vmp61rPi-(wbz}4>H zD6;jnqAW%(%O~6Zz!&Zx%_|0i@(DHU9T^R9{cX4B3$Spc6;3Q(BiC`K*T-jDw~xys z;O}zYH@Xa~urjwS_+U66kAkfIqUDeFaqMtlNUglGrsP?VHZmIO4}ykN5p}*$Z^HwYKufr5skS*cTiN3>@qU;v633 z^Q5^gw??UW=)zYXwXjH$TbK=1(QBDpgV|BM^x=KeS$N@mpgaqw*st?uc}Kras|s71 z69pmaU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zev zeiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}b zE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#T~)fQ%b#!thVb3yF?^u;R?V(2;Nwo4Z}*G6 zM6YiodgH4^@eHoFh>p65raw>Ai4R6shQ9IpC!Zx+fiJCg9vBnIzv9gqA(O$>Pf!y- zL0DucpEyAJiOEUgu!(|5m6ersdUNTfJ9G2Xl9aHR^nz$H8dGMAC1TL)m#_tvSTdE0 zqh(Ub6s4tff#kHcnv9qKl}g|4rHmfKItXWkP?+*dZ+IE$^*YjtqV@b#Z0G!)@$U0y zPF@@TcxoH6EWM((b^&q+GE-GqF^%POiDWV!i9{$>oQ^4I zGPFM<5OSNGB#{-sZVGxjj{aXmZa^MrdS(Z^7cSh8HqU+VhtF?~fA4p;R@Itr8{@Yp z-c8(^6e=et>+R$3ls$1}USG0MnTf5dGt#E&>YBu=`ua#iL&F{0x^>qZ*49r+M!Fa= zJwcSDJ-TxG6Dp-ffj`3rLzyIzv_wxz5$Yz9UI(xd0Oqf=?`y)c#*i221AInV4ydk`?5SI_aof6d6Q0G zmNtn$J^GdBEZ=!4ik&3ph(u#4>Fd^j=@N>5I7`ycu2SY2;!{nD^Xok)&#diucdE+v zae4e+_02iULiysQ_5O8#JJ7s2x`HZU9UGGy=MMwoKHr-mQIH5*IZy|n|7VQKO_-Rv zZaHykYG>QWzxDO4Ya^dd>FOwXYgz`CN*hQpt{_8o4W(jwn*YT-rOsX>^R)y;DoHxM zVf&9xta|RP%_S4|BMsjkwyud;iF2GCGb3EXEP1A~d4*5t0VV=aQgmVkgEp#&;0nVuyx@@yERe+(i0^lZdf znmB7O{mkF#sA4yd5k2ud88>Vr!{yV2M5XlN5*UIc=g3%HLKW+(DA~Q9OtA=Aq^VlE zhc$d4ltMTp44_!@pz}!C zz*7|977!Z)5HVvIE`ZA$;#`9ol_p0Sy%r%&XGOtSX12`yTNzFLJ;rh^ahGtQQ6`_1 ze2>KiVWX@|0qqi;-<_k({48aRxL*w}8<0>dkswyyJ}BXELWT1wyKmT%0dncX4icNh z1l4mv;NhSb4F-dW-X9?H;P5EhM44OfW*z2sA@6}fl1_u$clPiRb5RaZbUeh_fmFWf*$cOKuU7?mIP=CMmgMZo`*gn z%z&ab&y;3XH~`30G8a#om)Kc`<6XBIxo}RND1*V80w4y(!i{&C7a0%(gXl1US;K=Z zlSz;uft?iMl?Mm*$OuO!%+aM$Cn-D?7HQ_*V#U(c=u@J}Qa-(ush*`d*y;Ed341>x zs(%T2!A9Yfg_)8C4wDMW&*3YBPeGrCV0l3#>^21|5;&-0a1=!}@L=Nwm`NF=+_{?+ zzdmuzES8p-?k$)$Tp-X!wQcwTuywzkO~RZruCz=-KxN@t5cDx71C&8vVV+F}#KNp| znDgM+IOkA;cNfKJy+FdkB2El_Vn%BCuhrH1%VL!|NsE>3CujHsJq{or{sm<|LdBV3 zkby8G3B)9h>wSXxGPpc2%*nIFAY{pcx-aAm8pE7-X&hU;iA$+1KTno#Ia^_&+j>KK zfuy?Tx5uw+Iahn-qs=saMBap0p_ADlW+CS&lMe;#uu(2qk}CzfW)|cc1`>A2KOzP( zhjU#Xl0ym=PKFF3CNLnh^O+s*udQEs&1$vGi?LXgR@GKcU#WQcAL2X#6($&UBrU=O zhXU+o!-achhymuVGJCn4k`GXhgd8qh3k+dCgXP8>Lzi0$X~c*2#rW!pnabzh*|OGz z$Ads3UPq})yi2k5FTD9deDgWNQ-L>5F@z!t;a$OL24Rzd#c*XEK*@auECWYhW)dFx zs9+FzVGx`WtOuibRFEFwX8qgA&wXd(#*ODIN-eh&IIl!0ouMdRKR2u?yY=&iu`gbk z(Mc4`Wj_EB0LaP8%m9ciaxP&!31Kj~kY-tdmpl-@LS3j*R6G|~1_gvSMmn+3eLTKq z{}0@D^R+kl!4?M&*8NU1{+DC>h&=9;llb0KP?&k^PNrgzc3n2i%Iz7XNZ`|)nz0a zrv}Rc)p=g|(>E=D@U>frsE9`a$64g?a>qYfLwzu{i2MBoU(X_Vw{{fD>>$q^Z6_OfA;9Po0>qSg4-; z(6u@pu_9~WST6&UIF~pMKq~O5%vY{7z5=WKq-29oi5O>HVo4-J41a22_`Q3Lr+#Su z@>jdNp8C&i>q;k=qD&tz6XfU?0Lh~m;>3EQTug|o9H&o2H;hl;o?cZSyWUWWKO0g4 zjsmbH{gJCatWMskcIE-^yb^7bE z1RfoHCKNT{$M5~5xQhuANok2Jc+a^L7j-|ocyqIG?uQ4PrjPGyO0_Nv*CS(smlklAd0 zd_ZYDiTORnWHiyGd+E}TuTE|&x%}Sq_S>g=?8z%z>-BSG6~+wle}AQeaME%~b79`j z7?Ts}y2*=~C(m9sw4J0?o_BY&Zo1_DblpremZYVnB^k*73Xp^cVlu_?70HOdYz3W; zt}h9uiRroe^z?XR$-PUB)}xD#xMM&0^8WLu_Y9x}_6acISpwiM(yaJ_VFpmVJOe!GwjW({4PgK(Y{jzYN cNRe@iH~yn<<^DH#@}6h*_I&s0&wu5A0OFGkkpKVy From e1493540c1b44b67ab03ed9f3bf99b39c1c26b5e Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 7 Feb 2019 21:38:29 +0100 Subject: [PATCH 27/46] fix review on local render --- pype/plugins/nuke/publish/collect_families.py | 47 ++++++++++--------- pype/plugins/nuke/publish/collect_writes.py | 1 + .../nuke/publish/extract_render_local.py | 7 +++ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/pype/plugins/nuke/publish/collect_families.py b/pype/plugins/nuke/publish/collect_families.py index 4fd09fe967..08ab90143d 100644 --- a/pype/plugins/nuke/publish/collect_families.py +++ b/pype/plugins/nuke/publish/collect_families.py @@ -2,43 +2,46 @@ import pyblish.api @pyblish.api.log -class CollectInstanceFamilies(pyblish.api.ContextPlugin): +class CollectInstanceFamilies(pyblish.api.InstancePlugin): """Collect families for all instances""" order = pyblish.api.CollectorOrder + 0.2 label = "Collect Families" hosts = ["nuke", "nukeassist"] + families = ['write'] - def process(self, context): - for instance in context.data["instances"]: + def process(self, instance): - if "write" in instance.data["family"]: - node = instance[0] + node = instance[0] - families = [] - if instance.data.get('families'): - families.append(instance.data['families']) + self.log.info('processing {}'.format(node)) - # set for ftrack to accept - # instance.data["families"] = ["ftrack"] + families = [] + if instance.data.get('families'): + families.append(instance.data['families']) - if node["render"].value(): - # dealing with local/farm rendering - if node["render_farm"].value(): - families.append("render.farm") - else: - families.append("render.local") - else: - families.append("render.frames") - # to ignore staging dir op in integrate - instance.data['transfer'] = False + # set for ftrack to accept + # instance.data["families"] = ["ftrack"] + + if node["render"].value(): + # dealing with local/farm rendering + if node["render_farm"].value(): + families.append("render.farm") + else: + families.append("render.local") + else: + families.append("render.frames") + # to ignore staging dir op in integrate + instance.data['transfer'] = False + + families.append('ftrack') - instance.data["families"] = families + instance.data["families"] = families # Sort/grouped by family (preserving local index) - context[:] = sorted(context, key=self.sort_by_family) + instance.context[:] = sorted(instance.context, key=self.sort_by_family) def sort_by_family(self, instance): """Sort by family""" diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index df9666b8ca..7ec2bbc09e 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -89,6 +89,7 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): "colorspace": node["colorspace"].value(), }) + self.log.debug("instance.data: {}".format(instance.data)) self.log.debug("context: {}".format(context)) diff --git a/pype/plugins/nuke/publish/extract_render_local.py b/pype/plugins/nuke/publish/extract_render_local.py index 5b53a42136..1f0a00273f 100644 --- a/pype/plugins/nuke/publish/extract_render_local.py +++ b/pype/plugins/nuke/publish/extract_render_local.py @@ -2,6 +2,7 @@ import pyblish.api import nuke import os import pype +import clique class NukeRenderLocal(pype.api.Extractor): @@ -66,5 +67,11 @@ class NukeRenderLocal(pype.api.Extractor): output_dir )) + collections, remainder = clique.assemble(*instance.data['files']) + self.log.info('collections: {}'.format(str(collections))) + + collection = collections[0] + instance.data['collection'] = collection + self.log.info('Finished render') return From 52a2b375982ee8c6efe4da5ad442c75f792b388b Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 7 Feb 2019 23:51:52 +0100 Subject: [PATCH 28/46] comment out nuke callback that were crashing session --- pype/plugins/ftrack/integrate_ftrack_instances.py | 2 +- pype/plugins/nuke/publish/collect_writes.py | 1 + setup/nuke/nuke_path/menu.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/plugins/ftrack/integrate_ftrack_instances.py b/pype/plugins/ftrack/integrate_ftrack_instances.py index 491428a33d..48850d3460 100644 --- a/pype/plugins/ftrack/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/integrate_ftrack_instances.py @@ -67,7 +67,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): 'frameOut': int(instance.data["startFrame"]), 'frameRate': 25})} } - elif ext in [".jpg"]: + elif ext in [".jpg", ".jpeg"]: component_data = { "name": "thumbnail" # Default component name is "main". } diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 7ec2bbc09e..2a274201bb 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -90,6 +90,7 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): }) + self.log.debug("instance.data: {}".format(instance.data)) self.log.debug("context: {}".format(context)) diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 3613bc99f2..45f44d0d11 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -6,7 +6,7 @@ from pype.api import Logger log = Logger.getLogger(__name__, "nuke") -nuke.addOnScriptSave(writes_version_sync) -nuke.addOnScriptSave(onScriptLoad) +# nuke.addOnScriptSave(writes_version_sync) +# nuke.addOnScriptSave(onScriptLoad) log.info('Automatic syncing of write file knob to script version') From 4462972d52ab060251d9c1477640febbbfd60d6c Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sun, 10 Feb 2019 00:12:48 +0100 Subject: [PATCH 29/46] fixing review collection and minor tweaks --- .../extract_json.py | 0 pype/plugins/maya/publish/collect_model.py | 5 ++- pype/plugins/maya/publish/collect_review.py | 43 ++++++++++++------- .../plugins/maya/publish/extract_quicktime.py | 4 +- .../publish/validate_mesh_non_zero_edge.py | 1 + .../publish/validate_render_image_rule.py | 4 +- 6 files changed, 37 insertions(+), 20 deletions(-) rename pype/plugins/global/{publish => _publish_unused}/extract_json.py (100%) diff --git a/pype/plugins/global/publish/extract_json.py b/pype/plugins/global/_publish_unused/extract_json.py similarity index 100% rename from pype/plugins/global/publish/extract_json.py rename to pype/plugins/global/_publish_unused/extract_json.py diff --git a/pype/plugins/maya/publish/collect_model.py b/pype/plugins/maya/publish/collect_model.py index fa6a0eee1c..831399339b 100644 --- a/pype/plugins/maya/publish/collect_model.py +++ b/pype/plugins/maya/publish/collect_model.py @@ -26,4 +26,7 @@ class CollectModelData(pyblish.api.InstancePlugin): instance.data['endFrame'] = frame # make ftrack publishable - instance.data["families"] = ['ftrack'] + if instance.data.get('families'): + instance.data['families'].append('ftrack') + else: + instance.data['families'] = ['ftrack'] diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 6ddb550a99..9fb054127e 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -4,7 +4,7 @@ import pymel.core as pm import pyblish.api import avalon.api -class CollectReviewData(pyblish.api.InstancePlugin): +class CollectReview(pyblish.api.InstancePlugin): """Collect Review data """ @@ -15,12 +15,9 @@ class CollectReviewData(pyblish.api.InstancePlugin): def process(self, instance): - # make ftrack publishable - instance.data["families"] = ['ftrack'] - context = instance.context + self.log.debug('instance: {}'.format(instance)) task = avalon.api.Session["AVALON_TASK"] - # pseudo code # get cameras members = instance.data['setMembers'] @@ -33,7 +30,7 @@ class CollectReviewData(pyblish.api.InstancePlugin): camera = cameras[0] self.log.debug('camera: {}'.format(camera)) - objectset = context.data['objectsets'] + objectset = instance.context.data['objectsets'] reviewable_subset = None reviewable_subset = list(set(members) & set(objectset)) @@ -41,18 +38,34 @@ class CollectReviewData(pyblish.api.InstancePlugin): assert len(reviewable_subset) <= 1, "Multiple subsets for review" self.log.debug('subset for review: {}'.format(reviewable_subset)) - for inst in context: - self.log.debug('instance: {}'.format(instance)) + i = 0 + for inst in instance.context: + data = instance.context[i].data + if inst.name == reviewable_subset[0]: - if inst.data.get('families'): - inst.data['families'].append('review') + if data.get('families'): + data['families'].append('review') else: - inst.data['families'] = ['review'] - inst.data['review_camera'] = camera - self.log.info('adding review family to {}'.format(reviewable_subset)) + data['families'] = ['review'] + self.log.debug('adding review family to {}'.format(reviewable_subset)) + data['review_camera'] = camera + data["publish"] = False + data['startFrameReview'] = instance.data['startFrame'] + data['endFrameReview'] = instance.data['endFrame'] + data['handles'] = instance.data['handles'] + data['step'] = instance.data['step'] + data['fps'] = instance.data['fps'] cmds.setAttr(str(instance) + '.active', 0) - inst.data['publish'] = 0 - inst.data['active'] = 0 + i += 1 + instance.context[i].data.update(data) + instance.data['remove'] = True else: instance.data['subset'] = task + 'Review' instance.data['review_camera'] = camera + instance.data['startFrameReview'] = instance.data['startFrame'] + instance.data['endFrameReview'] = instance.data['endFrame'] + + # make ftrack publishable + instance.data["families"] = ['ftrack'] + + cmds.setAttr(str(instance) + '.active', 1) diff --git a/pype/plugins/maya/publish/extract_quicktime.py b/pype/plugins/maya/publish/extract_quicktime.py index 91bfc9284b..16c8c08e52 100644 --- a/pype/plugins/maya/publish/extract_quicktime.py +++ b/pype/plugins/maya/publish/extract_quicktime.py @@ -37,8 +37,8 @@ class ExtractQuicktime(pype.api.Extractor): # if start and end frames cannot be determined, get them # from Maya timeline - start = instance.data.get("startFrame") - end = instance.data.get("endFrame") + start = instance.data.get("startFrameReview") + end = instance.data.get("endFrameReview") if start is None: start = cmds.playbackOptions(query=True, animationStartTime=True) if end is None: diff --git a/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py b/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py index 6ade81764b..7d2c126af9 100644 --- a/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py @@ -23,6 +23,7 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): version = (0, 1, 0) label = 'Mesh Edge Length Non Zero' actions = [pype.maya.action.SelectInvalidAction] + optional = True __tolerance = 1e-5 diff --git a/pype/plugins/maya/publish/validate_render_image_rule.py b/pype/plugins/maya/publish/validate_render_image_rule.py index 9a718afc13..377dbfeadc 100644 --- a/pype/plugins/maya/publish/validate_render_image_rule.py +++ b/pype/plugins/maya/publish/validate_render_image_rule.py @@ -9,7 +9,7 @@ def get_file_rule(rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) -class ValidateRenderImageRule(pyblish.api.ContextPlugin): +class ValidateRenderImageRule(pyblish.api.InstancePlugin): """Validates "images" file rule is set to "renders/" """ @@ -19,7 +19,7 @@ class ValidateRenderImageRule(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["renderlayer"] - def process(self, context): + def process(self, instance): assert get_file_rule("images") == "renders", ( "Workspace's `images` file rule must be set to: renders" From 4cae0b06ceea2ab4fde5ba8c70e5d41bd069afbe Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sun, 10 Feb 2019 14:10:57 +0100 Subject: [PATCH 30/46] hotfix/collection problems with assigning review to subsets fixe --- pype/plugins/maya/publish/collect_model.py | 2 +- .../maya/publish/collect_remove_marked.py | 24 +++++++++++++++++++ pype/plugins/maya/publish/collect_review.py | 6 ++++- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 pype/plugins/maya/publish/collect_remove_marked.py diff --git a/pype/plugins/maya/publish/collect_model.py b/pype/plugins/maya/publish/collect_model.py index 831399339b..f8e25a9fc1 100644 --- a/pype/plugins/maya/publish/collect_model.py +++ b/pype/plugins/maya/publish/collect_model.py @@ -15,7 +15,7 @@ class CollectModelData(pyblish.api.InstancePlugin): """ - order = pyblish.api.CollectorOrder + 0.499 + order = pyblish.api.CollectorOrder + 0.4 label = 'Collect Model Data' families = ["model"] diff --git a/pype/plugins/maya/publish/collect_remove_marked.py b/pype/plugins/maya/publish/collect_remove_marked.py new file mode 100644 index 0000000000..c842fe4ed0 --- /dev/null +++ b/pype/plugins/maya/publish/collect_remove_marked.py @@ -0,0 +1,24 @@ +import pyblish.api + + +class CollectRemoveMarked(pyblish.api.ContextPlugin): + """Collect model data + + Ensures always only a single frame is extracted (current frame). + + Note: + This is a workaround so that the `pype.model` family can use the + same pointcache extractor implementation as animation and pointcaches. + This always enforces the "current" frame to be published. + + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = 'Remove Marked Instances' + + def process(self, context): + + # make ftrack publishable + for instance in context: + if instance.data.get('remove'): + context.remove(instance) diff --git a/pype/plugins/maya/publish/collect_review.py b/pype/plugins/maya/publish/collect_review.py index 9fb054127e..b318a8de40 100644 --- a/pype/plugins/maya/publish/collect_review.py +++ b/pype/plugins/maya/publish/collect_review.py @@ -4,6 +4,7 @@ import pymel.core as pm import pyblish.api import avalon.api + class CollectReview(pyblish.api.InstancePlugin): """Collect Review data @@ -40,6 +41,9 @@ class CollectReview(pyblish.api.InstancePlugin): i = 0 for inst in instance.context: + + self.log.debug('processing {}'.format(inst)) + self.log.debug('processing2 {}'.format(instance.context[i])) data = instance.context[i].data if inst.name == reviewable_subset[0]: @@ -56,9 +60,9 @@ class CollectReview(pyblish.api.InstancePlugin): data['step'] = instance.data['step'] data['fps'] = instance.data['fps'] cmds.setAttr(str(instance) + '.active', 0) - i += 1 instance.context[i].data.update(data) instance.data['remove'] = True + i += 1 else: instance.data['subset'] = task + 'Review' instance.data['review_camera'] = camera From 690cc3ffe0fe3557d740c950309bcc9a93afcb8d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Tue, 12 Feb 2019 17:44:09 +0100 Subject: [PATCH 31/46] renderr submission fixe on avalon_deadline --- pype/plugins/global/publish/submit_publish_job.py | 14 +++++++------- pype/plugins/maya/create/create_renderglobals.py | 12 ++++++------ .../maya/publish/validate_deadline_connection.py | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index e4a674dc65..6b6cdfadf2 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -133,14 +133,14 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): def process(self, instance): - # AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", - # "http://localhost:8082") - # assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" - try: - deadline_url = os.environ["DEADLINE_REST_URL"] - except KeyError: - self.log.error("Deadline REST API url not found.") + # try: + # deadline_url = os.environ["DEADLINE_REST_URL"] + # except KeyError: + # self.log.error("Deadline REST API url not found.") # Get a submission job job = instance.data.get("deadlineSubmissionJob") diff --git a/pype/plugins/maya/create/create_renderglobals.py b/pype/plugins/maya/create/create_renderglobals.py index 81850fe830..2ecc6cd0cd 100644 --- a/pype/plugins/maya/create/create_renderglobals.py +++ b/pype/plugins/maya/create/create_renderglobals.py @@ -4,8 +4,8 @@ import pype.maya.lib as lib from avalon.vendor import requests import avalon.maya -from avalon import api - +# from avalon import api +import os class CreateRenderGlobals(avalon.maya.Creator): @@ -21,7 +21,7 @@ class CreateRenderGlobals(avalon.maya.Creator): # Get available Deadline pools try: - deadline_url = os.environ["DEADLINE_REST_URL"] + AVALON_DEADLINE = os.environ["AVALON_DEADLINE"] except KeyError: self.log.error("Deadline REST API url not found.") @@ -34,9 +34,9 @@ class CreateRenderGlobals(avalon.maya.Creator): pools = response.json() # We don't need subset or asset attributes - self.data.pop("subset", None) - self.data.pop("asset", None) - self.data.pop("active", None) + # self.data.pop("subset", None) + # self.data.pop("asset", None) + # self.data.pop("active", None) self.data["suspendPublishJob"] = False self.data["extendFrames"] = False diff --git a/pype/plugins/maya/publish/validate_deadline_connection.py b/pype/plugins/maya/publish/validate_deadline_connection.py index 9052088d58..66ae32f024 100644 --- a/pype/plugins/maya/publish/validate_deadline_connection.py +++ b/pype/plugins/maya/publish/validate_deadline_connection.py @@ -3,7 +3,7 @@ import pyblish.api import avalon.api as api from avalon.vendor import requests from pype.plugin import contextplugin_should_run - +import os class ValidateDeadlineConnection(pyblish.api.ContextPlugin): """Validate Deadline Web Service is running""" @@ -20,7 +20,7 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): return try: - deadline_url = os.environ["DEADLINE_REST_URL"] + AVALON_DEADLINE = os.environ["AVALON_DEADLINE"] except KeyError: self.log.error("Deadline REST API url not found.") From 326cb966b4b66f3fd27a625fa0877ed823e935b2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Feb 2019 10:08:46 +0100 Subject: [PATCH 32/46] first version, name of assets must be written --- .../actions/action_remove_avalon_asset.py | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 pype/ftrack/actions/action_remove_avalon_asset.py diff --git a/pype/ftrack/actions/action_remove_avalon_asset.py b/pype/ftrack/actions/action_remove_avalon_asset.py new file mode 100644 index 0000000000..d4a0f7969b --- /dev/null +++ b/pype/ftrack/actions/action_remove_avalon_asset.py @@ -0,0 +1,189 @@ +import sys +import logging +import argparse +import ftrack_api +from pype.ftrack import BaseAction +from avalon.tools.libraryloader.io_nonsingleton import DbConnector + + +class AssetsRemover(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'remove.assets' + #: Action label. + label = 'Assets remover' + #: Action description. + description = 'Removes assets from Ftrack and Avalon db with all childs' + #: Db + db = DbConnector() + + def discover(self, session, entities, event): + ''' Validation ''' + selection = event["data"].get("selection", None) + if selection is None: + return False + + valid = ["show", "task"] + entityType = selection[0].get("entityType", "") + if entityType.lower() not in valid: + return False + + discover = False + roleList = ['Pypeclub', 'Administrator'] + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + for role in user['user_security_roles']: + if role['security_role']['name'] in roleList: + discover = True + break + + return discover + + def interface(self, session, entities, event): + if not event['data'].get('values', {}): + title = 'Enter Asset names to delete' + + items = [] + for i in range(15): + + item = { + 'label': 'Asset {}'.format(i+1), + 'name': 'asset_{}'.format(i+1), + 'type': 'text', + 'value': '' + } + items.append(item) + + return { + 'items': items, + 'title': title + } + + def launch(self, session, entities, event): + entity = entities[0] + if entity.entity_type.lower() != 'Project': + project = entity['project'] + else: + project = entity + + if 'values' not in event['data']: + return + + values = event['data']['values'] + if len(values) <= 0: + return { + 'success': True, + 'message': 'No Assets to delete!' + } + + asset_names = [] + + for k, v in values.items(): + if v.replace(' ', '') != '': + asset_names.append(v) + + self.db.install() + self.db.Session['AVALON_PROJECT'] = project["full_name"] + + assets = self.find_assets(asset_names) + + all_ids = [] + for asset in assets: + all_ids.append(asset['_id']) + all_ids.extend(self.find_child(asset)) + + if len(all_ids) == 0: + self.db.uninstall() + return { + 'success': True, + 'message': 'None of assets' + } + + or_subquery = [] + for id in all_ids: + or_subquery.append({'_id': id}) + delete_query = {'$or': or_subquery} + self.db.delete_many(delete_query) + + self.db.uninstall() + return { + 'success': True, + 'message': 'All assets were deleted!' + } + + def find_child(self, entity): + output = [] + id = entity['_id'] + visuals = [x for x in self.db.find({'data.visualParent': id})] + assert len(visuals) == 0, 'This asset has another asset as child' + childs = self.db.find({'parent': id}) + for child in childs: + output.append(child['_id']) + output.extend(self.find_child(child)) + return output + + def find_assets(self, asset_names): + assets = [] + for name in asset_names: + entity = self.db.find_one({ + 'type': 'asset', + 'name': name + }) + if entity is not None and entity not in assets: + assets.append(entity) + return assets + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + # Validate that session is an instance of ftrack_api.Session. If not, + # assume that register is being called from an old or incompatible API and + # return without doing anything. + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = AssetsRemover(session) + action_handler.register() + + +def main(arguments=None): + '''Set up logging and register action.''' + if arguments is None: + arguments = [] + + parser = argparse.ArgumentParser() + # Allow setting of logging level from arguments. + loggingLevels = {} + for level in ( + logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, + logging.ERROR, logging.CRITICAL + ): + loggingLevels[logging.getLevelName(level).lower()] = level + + parser.add_argument( + '-v', '--verbosity', + help='Set the logging output verbosity.', + choices=loggingLevels.keys(), + default='info' + ) + namespace = parser.parse_args(arguments) + + # Set up basic logging + logging.basicConfig(level=loggingLevels[namespace.verbosity]) + + session = ftrack_api.Session() + + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) From cd8d6aba17168962dea5c163d42f7adb4ce3fe6d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Feb 2019 11:17:39 +0100 Subject: [PATCH 33/46] renamed action --- ...ion_remove_avalon_asset.py => action_delete_asset_byname.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename pype/ftrack/actions/{action_remove_avalon_asset.py => action_delete_asset_byname.py} (99%) diff --git a/pype/ftrack/actions/action_remove_avalon_asset.py b/pype/ftrack/actions/action_delete_asset_byname.py similarity index 99% rename from pype/ftrack/actions/action_remove_avalon_asset.py rename to pype/ftrack/actions/action_delete_asset_byname.py index d4a0f7969b..ab5eeeac2c 100644 --- a/pype/ftrack/actions/action_remove_avalon_asset.py +++ b/pype/ftrack/actions/action_delete_asset_byname.py @@ -12,7 +12,7 @@ class AssetsRemover(BaseAction): #: Action identifier. identifier = 'remove.assets' #: Action label. - label = 'Assets remover' + label = 'Delete Assets by Name' #: Action description. description = 'Removes assets from Ftrack and Avalon db with all childs' #: Db From aa2f4e1aa017f7e5557b347bca4639c108f58f8a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 13 Feb 2019 11:19:30 +0100 Subject: [PATCH 34/46] action runable on entityType task needs verification to delete --- pype/ftrack/actions/action_delete_asset.py | 192 +++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 pype/ftrack/actions/action_delete_asset.py diff --git a/pype/ftrack/actions/action_delete_asset.py b/pype/ftrack/actions/action_delete_asset.py new file mode 100644 index 0000000000..93dc1b3824 --- /dev/null +++ b/pype/ftrack/actions/action_delete_asset.py @@ -0,0 +1,192 @@ +import sys +import logging +import random +import string +import argparse +import ftrack_api +from pype.ftrack import BaseAction +from avalon.tools.libraryloader.io_nonsingleton import DbConnector + + +class DeleteEntity(BaseAction): + '''Edit meta data action.''' + + #: Action identifier. + identifier = 'delete.entity' + #: Action label. + label = 'Delete entity' + #: Action description. + description = 'Removes assets from Ftrack and Avalon db with all childs' + icon = "https://www.iconsdb.com/icons/preview/white/full-trash-xxl.png" + #: Db + db = DbConnector() + + def discover(self, session, entities, event): + ''' Validation ''' + selection = event["data"].get("selection", None) + if selection is None or len(selection) > 1: + return False + + valid = ["task"] + entityType = selection[0].get("entityType", "") + if entityType.lower() not in valid: + return False + + discover = False + roleList = ['Pypeclub', 'Administrator'] + userId = event['source']['user']['id'] + user = session.query('User where id is ' + userId).one() + + for role in user['user_security_roles']: + if role['security_role']['name'] in roleList: + discover = True + break + + return discover + + def interface(self, session, entities, event): + if not event['data'].get('values', {}): + entity = entities[0] + title = 'Going to delete "{}"'.format(entity['name']) + + items = [] + item = { + 'label': 'Enter "DELETE" to confirm', + 'name': 'key', + 'type': 'text', + 'value': '' + } + items.append(item) + + return { + 'items': items, + 'title': title + } + + def launch(self, session, entities, event): + if 'values' not in event['data']: + return + + values = event['data']['values'] + if len(values) <= 0: + return { + 'success': True, + 'message': 'No Assets to delete!' + } + elif values.get('key', '').lower() != 'delete': + return { + 'success': False, + 'message': 'Entered key does not match' + } + entity = entities[0] + project = entity['project'] + + self.db.install() + self.db.Session['AVALON_PROJECT'] = project["full_name"] + + av_entity = self.db.find_one({ + 'type': 'asset', + 'name': entity['name'] + }) + + if av_entity is not None: + all_ids = [] + all_ids.append(av_entity['_id']) + all_ids.extend(self.find_child(av_entity)) + + if len(all_ids) == 0: + self.db.uninstall() + return { + 'success': True, + 'message': 'None of assets' + } + + or_subquery = [] + for id in all_ids: + or_subquery.append({'_id': id}) + delete_query = {'$or': or_subquery} + self.db.delete_many(delete_query) + + session.delete(entity) + session.commit() + self.db.uninstall() + + return { + 'success': True, + 'message': 'All assets were deleted!' + } + + def find_child(self, entity): + output = [] + id = entity['_id'] + visuals = [x for x in self.db.find({'data.visualParent': id})] + assert len(visuals) == 0, 'This asset has another asset as child' + childs = self.db.find({'parent': id}) + for child in childs: + output.append(child['_id']) + output.extend(self.find_child(child)) + return output + + def find_assets(self, asset_names): + assets = [] + for name in asset_names: + entity = self.db.find_one({ + 'type': 'asset', + 'name': name + }) + if entity is not None and entity not in assets: + assets.append(entity) + return assets + + +def register(session, **kw): + '''Register plugin. Called when used as an plugin.''' + + # Validate that session is an instance of ftrack_api.Session. If not, + # assume that register is being called from an old or incompatible API and + # return without doing anything. + if not isinstance(session, ftrack_api.session.Session): + return + + action_handler = DeleteEntity(session) + action_handler.register() + + +def main(arguments=None): + '''Set up logging and register action.''' + if arguments is None: + arguments = [] + + parser = argparse.ArgumentParser() + # Allow setting of logging level from arguments. + loggingLevels = {} + for level in ( + logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, + logging.ERROR, logging.CRITICAL + ): + loggingLevels[logging.getLevelName(level).lower()] = level + + parser.add_argument( + '-v', '--verbosity', + help='Set the logging output verbosity.', + choices=loggingLevels.keys(), + default='info' + ) + namespace = parser.parse_args(arguments) + + # Set up basic logging + logging.basicConfig(level=loggingLevels[namespace.verbosity]) + + session = ftrack_api.Session() + + register(session) + + # Wait for events + logging.info( + 'Registered actions and listening for events. Use Ctrl-C to abort.' + ) + session.event_hub.wait() + + +if __name__ == '__main__': + raise SystemExit(main(sys.argv[1:])) From fb81acfc8ff8da86f9b96dbe402302db2a2ad300 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Feb 2019 16:22:29 -0100 Subject: [PATCH 35/46] moving ftrack plugins into publish folder --- pype/plugins/ftrack/{ => publish}/collect_ftrack_api.py | 0 pype/plugins/ftrack/{ => publish}/integrate_ftrack_api.py | 0 pype/plugins/ftrack/{ => publish}/integrate_ftrack_instances.py | 0 pype/plugins/ftrack/{ => publish}/integrate_ftrack_review.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename pype/plugins/ftrack/{ => publish}/collect_ftrack_api.py (100%) rename pype/plugins/ftrack/{ => publish}/integrate_ftrack_api.py (100%) rename pype/plugins/ftrack/{ => publish}/integrate_ftrack_instances.py (100%) rename pype/plugins/ftrack/{ => publish}/integrate_ftrack_review.py (100%) diff --git a/pype/plugins/ftrack/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py similarity index 100% rename from pype/plugins/ftrack/collect_ftrack_api.py rename to pype/plugins/ftrack/publish/collect_ftrack_api.py diff --git a/pype/plugins/ftrack/integrate_ftrack_api.py b/pype/plugins/ftrack/publish/integrate_ftrack_api.py similarity index 100% rename from pype/plugins/ftrack/integrate_ftrack_api.py rename to pype/plugins/ftrack/publish/integrate_ftrack_api.py diff --git a/pype/plugins/ftrack/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py similarity index 100% rename from pype/plugins/ftrack/integrate_ftrack_instances.py rename to pype/plugins/ftrack/publish/integrate_ftrack_instances.py diff --git a/pype/plugins/ftrack/integrate_ftrack_review.py b/pype/plugins/ftrack/publish/integrate_ftrack_review.py similarity index 100% rename from pype/plugins/ftrack/integrate_ftrack_review.py rename to pype/plugins/ftrack/publish/integrate_ftrack_review.py From e1b16637281beb99a3f67768346675ed8911339f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 13 Feb 2019 16:37:46 -0100 Subject: [PATCH 36/46] fixing importing module ftrack_api for different python versions --- pype/plugins/ftrack/publish/collect_ftrack_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/plugins/ftrack/publish/collect_ftrack_api.py index 7afa4d50a0..f5e1cfd950 100644 --- a/pype/plugins/ftrack/publish/collect_ftrack_api.py +++ b/pype/plugins/ftrack/publish/collect_ftrack_api.py @@ -1,8 +1,11 @@ import os - -import ftrack_api_old as ftrack_api import pyblish.api +try: + import ftrack_api_old as ftrack_api +except Exception: + import ftrack_api + class CollectFtrackApi(pyblish.api.ContextPlugin): """ Collects an ftrack session and the current task id. """ From 36e3ab48299ddee9a3a5a686e0ed529fe8180e9d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Thu, 14 Feb 2019 08:54:22 +0100 Subject: [PATCH 37/46] fix environment cleanup when sending to farm --- pype/maya/__init__.py | 11 +- .../publish/integrate_rendered_frames.py | 2 +- .../global/publish/submit_publish_job.py | 2 +- pype/plugins/maya/publish/submit_deadline.py | 323 ------------------ .../maya/publish/submit_maya_deadline.py | 83 ++++- 5 files changed, 81 insertions(+), 340 deletions(-) delete mode 100644 pype/plugins/maya/publish/submit_deadline.py diff --git a/pype/maya/__init__.py b/pype/maya/__init__.py index 68af530bcd..6b971c8bca 100644 --- a/pype/maya/__init__.py +++ b/pype/maya/__init__.py @@ -107,6 +107,9 @@ def on_init(_): # Force load objExport plug-in (requested by artists) cmds.loadPlugin("objExport", quiet=True) + # Force load objExport plug-in (requested by artists) + cmds.loadPlugin("spore", quiet=True) + from .customize import ( override_component_mask_commands, override_toolbox_ui @@ -131,8 +134,8 @@ def on_save(_): avalon.logger.info("Running callback on save..") - # Update current task for the current scene - update_task_from_path(cmds.file(query=True, sceneName=True)) + # # Update current task for the current scene + # update_task_from_path(cmds.file(query=True, sceneName=True)) # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) @@ -146,8 +149,8 @@ def on_open(_): from avalon.vendor.Qt import QtWidgets from ..widgets import popup - # Update current task for the current scene - update_task_from_path(cmds.file(query=True, sceneName=True)) + # # Update current task for the current scene + # update_task_from_path(cmds.file(query=True, sceneName=True)) # Validate FPS after update_task_from_path to # ensure it is using correct FPS for the asset diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index 48881d25a1..3d4f753ce9 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -265,7 +265,7 @@ class IntegrateFrames(pyblish.api.InstancePlugin): "asset": ASSET, "family": instance.data['family'], "subset": subset["name"], - "version": version["name"], + "VERSION": version["name"], "hierarchy": hierarchy, "representation": ext[1:] } diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 6b6cdfadf2..8895e3626a 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -326,7 +326,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): self.log.info("Submitting..") self.log.info(json.dumps(payload, indent=4, sort_keys=True)) - url = "{}/api/jobs".format(deadline_url) + url = "{}/api/jobs".format(AVALON_DEADLINE) response = requests.post(url, json=payload) if not response.ok: raise Exception(response.text) diff --git a/pype/plugins/maya/publish/submit_deadline.py b/pype/plugins/maya/publish/submit_deadline.py deleted file mode 100644 index 5247311c97..0000000000 --- a/pype/plugins/maya/publish/submit_deadline.py +++ /dev/null @@ -1,323 +0,0 @@ -import os -import json -import getpass - -from maya import cmds - -from avalon import api -from avalon.vendor import requests - -import pyblish.api - -import pype.maya.lib as lib - - -def get_renderer_variables(renderlayer=None): - """Retrieve the extension which has been set in the VRay settings - - Will return None if the current renderer is not VRay - For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which - start with `rs`. Use the actual node name, do NOT use the `nice name` - - Args: - renderlayer (str): the node name of the renderlayer. - - Returns: - dict - """ - - renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - - padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) - - filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] - - if renderer == "vray": - # Maya's renderSettings function does not return V-Ray file extension - # so we get the extension from vraySettings - extension = cmds.getAttr("vraySettings.imageFormatStr") - - # When V-Ray image format has not been switched once from default .png - # the getAttr command above returns None. As such we explicitly set - # it to `.png` - if extension is None: - extension = "png" - - filename_prefix = "/_/" - else: - # Get the extension, getAttr defaultRenderGlobals.imageFormat - # returns an index number. - filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = "//" - - return {"ext": extension, - "filename_prefix": filename_prefix, - "padding": padding, - "filename_0": filename_0} - - -def preview_fname(folder, scene, layer, padding, ext): - """Return output file path with #### for padding. - - Deadline requires the path to be formatted with # in place of numbers. - For example `/path/to/render.####.png` - - Args: - folder (str): The root output folder (image path) - scene (str): The scene name - layer (str): The layer name to be rendered - padding (int): The padding length - ext(str): The output file extension - - Returns: - str - - """ - - # Following hardcoded "/_/" - output = "{scene}/{layer}/{layer}.{number}.{ext}".format( - scene=scene, - layer=layer, - number="#" * padding, - ext=ext - ) - - return os.path.join(folder, output) - - -class MayaSubmitDeadline(pyblish.api.InstancePlugin): - """Submit available render layers to Deadline - - Renders are submitted to a Deadline Web Service as - supplied via the environment variable DEADLINE_REST_URL - - """ - - label = "Submit to Deadline" - order = pyblish.api.IntegratorOrder + 0.1 - hosts = ["maya"] - families = ["renderlayer"] - - def process(self, instance): - - self.log.debug('Starting deadline submitter') - - try: - deadline_url = os.environ["DEADLINE_REST_URL"] - except KeyError: - self.log.error("Deadline REST API url not found.") - - # AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", - # "http://localhost:8082") - # assert AVALON_DEADLINE, "Requires AVALON_DEADLINE - - context = instance.context - - filepath = None - - allInstances = [] - for result in context.data["results"]: - if (result["instance"] is not None and - result["instance"] not in allInstances): - allInstances.append(result["instance"]) - - for inst in allInstances: - print(inst) - if inst.data['family'] == 'scene': - filepath = inst.data['destination_list'][0] - - if not filepath: - filepath = context.data["currentFile"] - - self.log.debug(filepath) - - workspace = context.data["workspaceDir"] - filename = os.path.basename(filepath) - comment = context.data.get("comment", "") - scene = os.path.splitext(filename)[0] - dirname = os.path.join(workspace, "renders") - renderlayer = instance.data['setMembers'] # rs_beauty - renderlayer_name = instance.data['subset'] # beauty - renderlayer_globals = instance.data["renderGlobals"] - legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] - deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "%s - %s" % (filename, instance.name) - - # Get the variables depending on the renderer - render_variables = get_renderer_variables(renderlayer) - output_filename_0 = preview_fname(folder=dirname, - scene=scene, - layer=renderlayer_name, - padding=render_variables["padding"], - ext=render_variables["ext"]) - - try: - # Ensure render folder exists - os.makedirs(dirname) - except OSError: - pass - - # Documentation for keys available at: - # https://docs.thinkboxsoftware.com - # /products/deadline/8.0/1_User%20Manual/manual - # /manual-submission.html#job-info-file-options - payload = { - "JobInfo": { - # Top-level group name - "BatchName": filename, - - # Job name, as seen in Monitor - "Name": jobname, - - # Arbitrary username, for visualisation in Monitor - "UserName": deadline_user, - - "Plugin": instance.data.get("mayaRenderPlugin", "MayaBatch"), - "Frames": "{start}-{end}x{step}".format( - start=int(instance.data["startFrame"]), - end=int(instance.data["endFrame"]), - step=int(instance.data["byFrameStep"]), - ), - - "Comment": comment, - - # Optional, enable double-click to preview rendered - # frames from Deadline Monitor - "OutputFilename0": output_filename_0.replace("\\", "/"), - }, - "PluginInfo": { - # Input - "SceneFile": filepath, - - # Output directory and filename - "OutputFilePath": dirname.replace("\\", "/"), - "OutputFilePrefix": render_variables["filename_prefix"], - - # Mandatory for Deadline - "Version": cmds.about(version=True), - - # Only render layers are considered renderable in this pipeline - "UsingRenderLayers": True, - - # Use legacy Render Layer system - "UseLegacyRenderLayers": legacy_layers, - - # Render only this layer - "RenderLayer": renderlayer, - - # Determine which renderer to use from the file itself - "Renderer": instance.data["renderer"], - - # Resolve relative references - "ProjectPath": workspace, - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Include critical environment variables with submission - keys = [ - # This will trigger `userSetup.py` on the slave - # such that proper initialisation happens the same - # way as it does on a local machine. - # TODO(marcus): This won't work if the slaves don't - # have accesss to these paths, such as if slaves are - # running Linux and the submitter is on Windows. - "PYTHONPATH", - "PATH", - - "MTOA_EXTENSIONS_PATH", - "MTOA_EXTENSIONS", - "DYLD_LIBRARY_PATH", - "MAYA_RENDER_DESC_PATH", - "MAYA_MODULE_PATH", - "ARNOLD_PLUGIN_PATH", - "AVALON_SCHEMA", - - # todo: This is a temporary fix for yeti variables - "PEREGRINEL_LICENSE", - "REDSHIFT_MAYAEXTENSIONSPATH", - "REDSHIFT_DISABLEOUTPUTLOCKFILES" - "VRAY_FOR_MAYA2018_PLUGINS_X64", - "VRAY_PLUGINS_X64", - "VRAY_USE_THREAD_AFFINITY", - "MAYA_MODULE_PATH", - "TOOL_ENV" - ] - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **api.Session) - - for path in os.environ: - if path.lower().startswith('pype_'): - environment[path] = os.environ[path] - - environment["PATH"] = os.environ["PATH"] - - clean_pythonpath = '' - for path in environment['PYTHONPATH'].split(os.pathsep): - try: - path.decode('UTF-8', 'strict') - clean_pythonpath += path + os.pathsep - except UnicodeDecodeError: - self.log.debug('path contains non UTF characters') - environment['PYTHONPATH'] = clean_pythonpath - - clean_path = '' - for path in environment['PATH'].split(os.pathsep): - clean_path += os.path.normpath(path) + os.pathsep - - environment['PATH'] = clean_path - - for path in environment: - environment[path] = environment[path].replace( - os.path.normpath(environment['PYPE_STUDIO_CORE_MOUNT']), - environment['PYPE_STUDIO_CORE']) - - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - # Include optional render globals - render_globals = instance.data.get("renderGlobals", {}) - payload["JobInfo"].update(render_globals) - - plugin = payload["JobInfo"]["Plugin"] - self.log.info("using render plugin : {}".format(plugin)) - - self.preflight_check(instance) - - self.log.info("Submitting..") - self.log.info(json.dumps(payload, indent=4, sort_keys=True)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - - # Store output dir for unified publisher (filesequence) - instance.data['source'] = filepath - instance.data["outputDir"] = os.path.dirname(output_filename_0) - instance.data["deadlineSubmissionJob"] = response.json() - - def preflight_check(self, instance): - """Ensure the startFrame, endFrame and byFrameStep are integers""" - - for key in ("startFrame", "endFrame", "byFrameStep"): - value = instance.data[key] - - if int(value) == value: - continue - - self.log.warning( - "%f=%d was rounded off to nearest integer" - % (value, int(value)) - ) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 6a6cabdf93..a4c57716cc 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -51,7 +51,7 @@ def get_renderer_variables(renderlayer=None): # returns an index number. filename_base = os.path.basename(filename_0) extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = "/_/" + filename_prefix = "//" return {"ext": extension, "filename_prefix": filename_prefix, @@ -78,7 +78,7 @@ def preview_fname(folder, scene, layer, padding, ext): """ # Following hardcoded "/_/" - output = "{scene}/{scene}_{layer}/{layer}.{number}.{ext}".format( + output = "{scene}/{layer}/{layer}.{number}.{ext}".format( scene=scene, layer=layer, number="#" * padding, @@ -97,9 +97,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): """ label = "Submit to Deadline" - order = pyblish.api.IntegratorOrder + order = pyblish.api.IntegratorOrder + 0.1 hosts = ["maya"] families = ["renderlayer"] + optional = True def process(self, instance): @@ -109,7 +110,25 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context workspace = context.data["workspaceDir"] - filepath = context.data["currentFile"] + + filepath = None + + allInstances = [] + for result in context.data["results"]: + if (result["instance"] is not None and + result["instance"] not in allInstances): + allInstances.append(result["instance"]) + + for inst in allInstances: + print(inst) + if inst.data['family'] == 'scene': + filepath = inst.data['destination_list'][0] + + if not filepath: + filepath = context.data["currentFile"] + + self.log.debug(filepath) + filename = os.path.basename(filepath) comment = context.data.get("comment", "") scene = os.path.splitext(filename)[0] @@ -203,22 +222,64 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # have accesss to these paths, such as if slaves are # running Linux and the submitter is on Windows. "PYTHONPATH", + "PATH", + + "MTOA_EXTENSIONS_PATH", + "MTOA_EXTENSIONS", + "DYLD_LIBRARY_PATH", + "MAYA_RENDER_DESC_PATH", + "MAYA_MODULE_PATH", + "ARNOLD_PLUGIN_PATH", + "AVALON_SCHEMA", # todo: This is a temporary fix for yeti variables "PEREGRINEL_LICENSE", "REDSHIFT_MAYAEXTENSIONSPATH", - "REDSHIFT_DISABLEOUTPUTLOCKFILES", - "VRAY_FOR_MAYA2018_PLUGINS", - "VRAY_PLUGINS", + "REDSHIFT_DISABLEOUTPUTLOCKFILES" + "VRAY_FOR_MAYA2018_PLUGINS_X64", + "VRAY_PLUGINS_X64", "VRAY_USE_THREAD_AFFINITY", - "MAYA_MODULE_PATH" + "MAYA_MODULE_PATH", + "TOOL_ENV" ] environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **api.Session) + self.log.debug("enviro: {}".format(pprint(environment))) + for path in os.environ: + if path.lower().startswith('pype_'): + environment[path] = os.environ[path] - PATHS = os.environ["PATH"].split(";") - environment["PATH"] = ";".join([p for p in PATHS - if p.startswith("P:")]) + environment["PATH"] = os.environ["PATH"] + self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) + clean_environment = {} + for key in environment: + clean_path = "" + self.log.debug("key: {}".format(key)) + to_process = environment[key] + if key == "PYPE_STUDIO_CORE_MOUNT": + clean_path = environment[key] + elif "://" in environment[key]: + clean_path = environment[key] + elif os.pathsep not in to_process: + try: + path = environment[key] + path.decode('UTF-8', 'strict') + clean_path = os.path.normpath(path) + except UnicodeDecodeError: + print('path contains non UTF characters') + else: + for path in environment[key].split(os.pathsep): + try: + path.decode('UTF-8', 'strict') + clean_path += os.path.normpath(path) + os.pathsep + except UnicodeDecodeError: + print('path contains non UTF characters') + clean_path = clean_path.replace( + os.path.normpath(environment['PYPE_STUDIO_CORE_MOUNT']), + os.path.normpath(environment['PYPE_STUDIO_CORE'])) + clean_environment[key] = clean_path + + environment = clean_environment payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( From af2430005964da01c6dd632ea30268d278b023f3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Feb 2019 11:46:39 +0100 Subject: [PATCH 38/46] status changes based on presets more specifically --- pype/ftrack/lib/ftrack_app_handler.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 422d3b96dc..68f69bbe7b 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -306,24 +306,26 @@ class AppAction(BaseHandler): # Change status of task to In progress config = get_config_data() - if ( - 'status_on_app_launch' in config and - 'sync_to_avalon' in config and - 'statuses_name_change' in config['sync_to_avalon'] - ): - statuses = config['sync_to_avalon']['statuses_name_change'] - if entity['status']['name'].lower() in statuses: - status_name = config['status_on_app_launch'] + if 'status_update' in config: + statuses = config['status_update'] + actual_status = entity['status']['name'].lower() + next_status_name = None + for key, value in statuses.items(): + if actual_status in value: + next_status_name = key + break + + if next_status_name is not None: try: - query = 'Status where name is "{}"'.format(status_name) + query = 'Status where name is "{}"'.format(next_status_name) status = session.query(query).one() - task['status'] = status + entity['status'] = status session.commit() - except Exception as e: + except Exception: msg = ( 'Status "{}" in config wasn\'t found on Ftrack' - ).format(status_name) + ).format(next_status_name) self.log.warning(msg) # Set origin avalon environments From 90187e94b3632909d81033f3e337bebf7256251e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Feb 2019 11:54:47 +0100 Subject: [PATCH 39/46] added possibility of usage _any_ so from any status will be changed to one specific --- pype/ftrack/lib/ftrack_app_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 68f69bbe7b..6123048701 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -312,7 +312,7 @@ class AppAction(BaseHandler): actual_status = entity['status']['name'].lower() next_status_name = None for key, value in statuses.items(): - if actual_status in value: + if actual_status in value or '_any_' in value: next_status_name = key break From 305262936e6954a7182c0705a4f73070f4a25056 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 14 Feb 2019 21:34:10 +0000 Subject: [PATCH 40/46] added "_ignore_" possibility --- pype/ftrack/lib/ftrack_app_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 6123048701..9aed3d74da 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -313,7 +313,8 @@ class AppAction(BaseHandler): next_status_name = None for key, value in statuses.items(): if actual_status in value or '_any_' in value: - next_status_name = key + if key != '_ignore_': + next_status_name = key break if next_status_name is not None: From b5f2fef6e1548ebebf15d52520d23bf5647c6218 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 15 Feb 2019 09:01:23 +0100 Subject: [PATCH 41/46] hotfix/remove obsolete log --- pype/plugins/maya/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index a4c57716cc..84f83c8e92 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -244,7 +244,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): ] environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **api.Session) - self.log.debug("enviro: {}".format(pprint(environment))) + #self.log.debug("enviro: {}".format(pprint(environment))) for path in os.environ: if path.lower().startswith('pype_'): environment[path] = os.environ[path] From 9f001cb8fdbc60b120b5e5cf4962e1df95dcab6f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 15 Feb 2019 10:42:26 +0100 Subject: [PATCH 42/46] change so project templates are used if templates already exist in project --- pype/ftrack/lib/avalon_sync.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 346bdceeb9..4b058fa3c3 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -114,6 +114,18 @@ def import_to_avalon( output['errors'] = errors return output + else: + # not override existing templates! + templates = av_project['config'].get('template', None) + if templates is not None: + for key, value in config['template'].items(): + if ( + key in templates and + templates[key] is not None and + templates[key] != value + ): + config['template'][key] = templates[key] + projectId = av_project['_id'] data = get_data( From bf7d25fd94f7ce2fd8419048c4e5adc5bf2aa73f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sun, 17 Feb 2019 21:26:29 +0100 Subject: [PATCH 43/46] cleanup conflicts --- pype/lib.py | 7 ------- pype/plugins/global/publish/integrate_rendered_frames.py | 6 ------ 2 files changed, 13 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index e7a1ce058e..368ddad024 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -427,13 +427,6 @@ def get_avalon_project_template(): proj_template['workfile'] = template.anatomy.avalon.workfile proj_template['work'] = template.anatomy.avalon.work proj_template['publish'] = template.anatomy.avalon.publish -<<<<<<< HEAD -======= - # Old - hardcoded = BackUp - # proj_template['workfile'] = "{asset[name]}_{task[name]}_v{version:0>3}<_{comment}>" - # proj_template['work'] = "{root}/{project}/{hierarchy}/{asset}/work/{task}" - # proj_template['publish'] = "{root}/{project}/{hierarchy}/{asset}/publish/{family}/{subset}/v{version}/{projectcode}_{asset}_{subset}_v{version}.{representation}" ->>>>>>> develop return proj_template diff --git a/pype/plugins/global/publish/integrate_rendered_frames.py b/pype/plugins/global/publish/integrate_rendered_frames.py index a184de1af9..ae11d33348 100644 --- a/pype/plugins/global/publish/integrate_rendered_frames.py +++ b/pype/plugins/global/publish/integrate_rendered_frames.py @@ -258,17 +258,11 @@ class IntegrateFrames(pyblish.api.InstancePlugin): # for performance reasons. "context": { "root": root, -<<<<<<< HEAD "project": { "name": PROJECT, "code": project['data']['code'] }, "task": api.Session["AVALON_TASK"], -======= - "project": {"name": PROJECT, - "code": project['data']['code']}, - 'task': api.Session["AVALON_TASK"], ->>>>>>> develop "silo": asset['silo'], "asset": ASSET, "family": instance.data['family'], From 3cd832a5ebf5c6d0e40d4b103d19c34878cec026 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Sun, 17 Feb 2019 22:23:20 +0100 Subject: [PATCH 44/46] udpate nuke template filling to match unified templates --- pype/nuke/lib.py | 2 +- pype/templates.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index f5b385cfe0..0f29484d9f 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -70,7 +70,7 @@ def format_anatomy(data): data.update({ "hierarchy": pype.get_hierarchy(), "frame": "#"*padding, - "VERSION": pype.get_version_from_path(file) + "version": pype.get_version_from_path(file) }) # log.info("format_anatomy:anatomy: {}".format(anatomy)) diff --git a/pype/templates.py b/pype/templates.py index 8da54b9b84..d7748145ee 100644 --- a/pype/templates.py +++ b/pype/templates.py @@ -87,7 +87,7 @@ def get_project_code(): string: project code """ - return io.find_one({"type": "project"})["data"]["code"] + return io.find_one({"type": "project"})["data"].get("code", '') def set_project_code(code): From fef49c20ef247a454c3a10b4303b6c01c23ca9bd Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 20 Feb 2019 13:48:28 +0100 Subject: [PATCH 45/46] remove locking of parent group --- pype/plugins/maya/load/load_ass.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pype/plugins/maya/load/load_ass.py b/pype/plugins/maya/load/load_ass.py index b27cd20b5b..13ad85473c 100644 --- a/pype/plugins/maya/load/load_ass.py +++ b/pype/plugins/maya/load/load_ass.py @@ -160,10 +160,6 @@ class AssStandinLoader(api.Loader): # Set the standin filepath standinShape.dso.set(self.fname) - - # Lock parenting of the transform and standin - cmds.lockNode([root, standin], lock=True) - nodes = [root, standin] self[:] = nodes From ed4b8f9bddae37d2f4d4e4760783adb8c3827c5d Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 20 Feb 2019 14:48:42 +0100 Subject: [PATCH 46/46] add look manager to maya side menu --- pype/maya/customize.py | 14 ++++++++++++++ res/icons/Thumbs.db | Bin 0 -> 6144 bytes res/icons/lookmanager.png | Bin 0 -> 2408 bytes 3 files changed, 14 insertions(+) create mode 100644 res/icons/Thumbs.db create mode 100644 res/icons/lookmanager.png diff --git a/pype/maya/customize.py b/pype/maya/customize.py index 872942bfd9..61d7c283d2 100644 --- a/pype/maya/customize.py +++ b/pype/maya/customize.py @@ -78,6 +78,8 @@ def override_toolbox_ui(): import avalon.tools.cbsceneinventory as inventory import avalon.tools.cbloader as loader from avalon.maya.pipeline import launch_workfiles_app + import mayalookassigner + # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" @@ -98,6 +100,18 @@ def override_toolbox_ui(): background_color = (0.267, 0.267, 0.267) controls = [] + control = mc.iconTextButton( + "pype_toolbox_lookmanager", + annotation="Look Manager", + label="Look Manager", + image=os.path.join(icons, "lookmanager.png"), + command=lambda: mayalookassigner.show(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + control = mc.iconTextButton( "pype_toolbox_workfiles", annotation="Work Files", diff --git a/res/icons/Thumbs.db b/res/icons/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..fa56c871f60da645a236c2786a1fdfe9f5693dc2 GIT binary patch literal 6144 zcmeH}dpOkDAIHy_VT^>4YbAGs$-2hOR&FiZ=!Z})CHG6Yvy7Cqp_^5|HA11<PcBN=7 z2YeLz01Flo0^s?t=NNd6vJ^r9aKQ`oei1>C<%a;k%dhbT?ZA7uRy2PoIG99k2%2*| zgcm}9@Ilb`U*rF82dqILETKRKn1BG-hJXl|?avP}cw$y|1@(i!iy!G$Ve1+@Fj&~h zP++;I7T5%Ifis}O+5^^JOZQs99cU~S9pDKlK=YHsszMi9nICOG+3}+nk~T!kyp26{^?pu%gn<<9@3hVw7!L4ELZ=NEx}DId-63Fp`ZmFmFa28-7c z+EBeFeDdi~{s;OM;cycl=113!<_*os-~Ikq!T0=-)etmwf)MyG01zR3|kk%uTi&@q%?OM)mvR3NGlGGrq}4e|{{9ijn2 zPlgsm8-niBCWtOX4?=-_^)2*Nemc+`p}Ft)>ygX<$^`DjwM{-l{YTBD{9^LRzce>B$~h{ zCN3elR#AzhtfH#3Nmq|T{dU`TCZ^wSr@#M&h}oS?qruN4jon75e zdU`p}2VV^RHvId@%dzo^$*Jj?*|~S9E)2jf%lcEK4+*C#ImoQ5-|1Eh+mvVbT9c*`LDx($x$2 zu^5;D^ynKhYohcjTa`=3c`*OtktL<8CUiOjrc7~3qrnlWri_ad78uRHtNzI$BXjrRQ z$*j3qu^j_$HUrCly-qrX8)JY*ELCPhYuyG?$(Z@&UjG^gaW4DQ&GORCszLN};39~L zQ5{><8>@-AzF$ww)^3Bxc*J45=AXTV$r~IDCBP-iSB^7x5iloUjsT|{2)L5vYN-(# zoN$%zvVE2nxzbEKC@rLSCrycAo?T|?bjEkf+T0UKv~4{Wt9ulcb4{BMHdYk$U7Ymo z#IpqF1h&CZa^Wb|-V4;XQzuWAQRO=H_dh>%D_+dHK{mwf-&K{3>pFiDWexS?BL)XB z&IdEzk=ZJ4`-&p`iUXu1ZpvKq8xF<^X&!5AUEfmi>t$k(8L7M~TE&hM$}n+Fh=~>= z{=8kkL3Ge<{AKgnmsJ)8`T@TC2Py17j=Y}qcPMDxAsC-EC;i`9!G8s3($FLTFhc+JhhL`IjMLdO_1@63qLD3G2MRF00FX%wXZr6FsYMU7$q`$ zcG_lZONVgNljhqD?e+ZpjM#JlrX>o~?l^JPyse(Bs3@*yXN-5cKRtBQjplNT^$37r zQo>o^)hS)cRNGw_RU^BH+Dui*p~ZGS(`Jp#Z(_UeJ+`+qZ`pOR;7FiS-}}(s(F~>m z(zEPkEA@*1$$aoXjnPAjp=w()z6Adh0}@~TZ^_rceE(~mw{Cdj>^kv$-fm&MpZP9e z{zBhORKP{*h|?MbXg9pCN5FKo-I&+OYX{wJy%I9(?t4au)R(O8;dgYI%iBKi=)~N~ zrX~x6>7;IUZD|DpvLb5e3xc)GsMqd+{lgLse(rG#-nuPAPClYCBTZ*4d;<>_rn39n zM{dw*eGY-|^qo`ktU_WdJGbwdKRiGlX)Aba+nv)s(3@=FGcR~qytj5Zd1~Ns_|u1@ z%!$yERx|RQNYa&;8%Ui=R}W|~2;S+M{8#Ba@Kk+YA$pi%jr(}MkX@f=Pl2Du`c4D- z8}i zEB?X$;2%C%^+Ru@$+cgCe|-IZTKO*g^8KTCe6iaien-T{AeP0Wc8#5uj-IC^?~tT| z1haxu5J0TAo7iJt$6asbSD!tuoK&G4m~wP=>5xU2AMv3Ucf<^|7lTD%hlMw6-G-q* z9_qf9?75^nI*=aB43F%Vc24>&l4dKaXqNh7XW~zcl5^(*r7I*&l-{TxXOf+5Ml~GN zs74lnX{jn%dGU_d7awG>&0d8Tj`|A*-<&qo&CZ6b7o~%EioTP5 zc~_xUFmv8I(L7(Rx2n2KZNad+Y_B@8&&WS~i8_DFpf6kFZ_LR)>kaghn=q;x0+M_2 z`?1kHfbx*m0zW?ut{|Y*_s0AigW0(2rdDUB$8&ko9kY~kNC6Id9)mgG`-c8*zP@hN z>#GE%)Hw5N`4cAO()YVZXD%JNaj+&L^SI98=Amdxc~Pph)On_+m-3S>TGvQ;KD;4$ z8)vaPuYdMz*Q&J2q0_e?Ty1)tLoAZS{TyT{|I~{qJ!|t$dSSW>0X2^iaH>EJ&*@6! zyc+6I>ph@kXgn96$yJBr><*zbs0uEL@y}ue&$%DD?PH!Ls@z1Qv6I#IDZgsl(|72& kBV1h;Z#MfU+O7Nv+GgGN`$lJ^|FXW7PpGf$7rX=i0f1|-6aWAK literal 0 HcmV?d00001 diff --git a/res/icons/lookmanager.png b/res/icons/lookmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed1d3db8ebaaed66efbf4949ecbd1ee9aed198b GIT binary patch literal 2408 zcmbVO3v3f*953TB*ckE%<1q)vO&(^u-lNxRd#qsB7S@f9v5heVVb{BBd!fC%-QBis zXpomU7$6KI3lYo=(TMUkAP9melO+fX27*Bmc^NMJVu#y!X)xAbqcGG4(dO7;hb{<}c=I8r zr0|g0NFgjvnjwqLh*KugX3KyG47Z|~6~#>mPSGSr69g3f!9Y#n{B(h9LR1Sp+2Nq3 z$ux>qR#qA-O-4xxpt#LuLoos+2m~MywMx{O2qLO!9SkmBWfeiz1WAN+M#d+VYjzk| z+U`PFj?s#0G)y31XoQhb+=%HewE;OchLg*cP@6c%qI`%C^P;8#EFQzkK}nO;p!5vt z*z_X;Ahcd@OyjAxgu^ijRdZJWH=+S~Dq1b7l6ka%SEX`=<=qv)%rxB_nRY5Xqe)7U zB!xN>mEU19|2_h#|s;_oh!@C%bx5N58f)NOT72zh@OoH5{V>pdr?NG18 z3I3`lp#Vn$^dnGEH5{Wc{|V+;+Ak?#2KX$5nE;Qk))kcNK$|>u+G%^y~pXy zS0uj>0uyS%#4O0;c2c;Fq7cG}x0&np(jHOO7?I^YE;|h3V-y4qn2MQwypKRE3}!-j zoU|c6&gw@fD@$T5YauDKuVcMSV$1a^(AT#&1She8v0aE78NoWm`A2is&t06ocsI&iBPQHj9?^5A`C^a2xBG8ti^&+ z4CU7=|DkdRiv#$lf~2d_&dOo}(NRbk*Fo=e5LOJcnJ2g$B#yhOEOLUwZ84i24hpko z;m%A{Zy~S%Y9FLrIQCTqcC81q1UATs0UjJLD6IR_ zRz>Y4fIeOwZF8MA?G89N3G`v?2m%kWLyQ*zwE~W)fmbf|H5lR`kIPY1a_HizH-ame z!SOfFQpN9nnS7BAu5QTAX_{Y%)C}=d5Zt za)iozuj|aUs7*c;=k*SCJP`SSgC{QH>Mpki$U$j|Ia8LTcOSK6m-T(~{IY(h)UpE!=ZK%T_jU~I zQ#x_MpNVHuw)d&+dqVD=G9Wa!c~M|`zfTW(KF`}ZuQsJB{Tzm;1oGDfAM_e@eAVLQ zHI4H(rM`14ZRW9@+LNV+uxn$>y3O7WW5WmZ56^905dZPW#>w%n(YbNqJN2cvyX?J* zB*qnP?Oj)vl(cQ%-Qx?Z&rBAx|0;z~E;*2KSiM%;bbDC*6#R!z5*wQbH(7jf=h=P{lXE&X^)PL=gX||TVJ-p-IcJE%gZ$w7w+7;r?m!^ga zdhI;C@+zKqHQ|mo>F9!=dQohfA_MMlKC5J=$mGI zWL?jl7t%lIe&dzgxGzQwJ#_HyPbb!AeKfjH@!STIe5L?j>HSuQ+qPxp9R4E#z_M Kxi)8&F8CKzT}2}R literal 0 HcmV?d00001