add mantra and karma farm publishing for Houdini

This commit is contained in:
Kayla Man 2023-04-11 16:09:56 +08:00
parent 89b87af509
commit 414cd0cce1
11 changed files with 666 additions and 110 deletions

View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
"""Creator plugin to create Karma ROP."""
from openpype.hosts.houdini.api import plugin
from openpype.pipeline import CreatedInstance
from openpype.lib import BoolDef, EnumDef, NumberDef
class CreateKarmaROP(plugin.HoudiniCreator):
"""Karma ROP"""
identifier = "io.openpype.creators.houdini.karma_rop"
label = "Karma ROP"
family = "karma_rop"
icon = "magic"
defaults = ["master"]
def create(self, subset_name, instance_data, pre_create_data):
import hou # noqa
instance_data.pop("active", None)
instance_data.update({"node_type": "karma"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
instance = super(CreateKarmaROP, self).create(
subset_name,
instance_data,
pre_create_data) # type: CreatedInstance
instance_node = hou.node(instance.get("instance_node"))
ext = pre_create_data.get("image_format")
filepath = "{}{}".format(
hou.text.expandString("$HIP/pyblish/"),
"{}.$F4.{}".format(subset_name, ext)
)
checkpoint = "{}{}".format(
hou.text.expandString("$HIP/pyblish/"),
"{}.$F4.checkpoint".format(subset_name)
)
usd_directory = "{}{}".format(
hou.text.expandString("$HIP/pyblish/usd_renders/"),
"{}_$RENDERID".format(subset_name)
)
parms = {
# Render Frame Range
"trange": 1,
# Karma ROP Setting
"picture": filepath,
# Karma Checkpoint Setting
"productName": checkpoint,
# USD Output Directory
"savetodirectory": usd_directory,
}
res_x = pre_create_data.get("res_x")
res_y = pre_create_data.get("res_y")
if self.selected_nodes:
# If camera found in selection
# we will use as render camera
camera = None
for node in self.selected_nodes:
if node.type().name() == "cam":
camera = node.path()
camera_node = hou.node(camera)
has_camera = pre_create_data.get("cam_res")
if has_camera:
res_x = camera_node.evalParm("resx")
res_y = camera_node.evalParm("resy")
if not camera:
self.log.warning("No render camera found in selection")
parms.update({
"camera": camera or "",
"resolutionx": res_x,
"resolutiony": res_y,
})
instance_node.setParms(parms)
# Lock some Avalon attributes
to_lock = ["family", "id"]
self.lock_parameters(instance_node, to_lock)
def get_pre_create_attr_defs(self):
attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
return attrs + [
EnumDef("image_format",
image_format_enum,
default="exr",
label="Image Format Options"),
NumberDef("res_x",
label="width",
default=1920,
decimals=0),
NumberDef("res_y",
label="height",
default=720,
decimals=0),
BoolDef("cam_res",
label="Camera Resolution",
default=False)
]

View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""Creator plugin to create Mantra ROP."""
from openpype.hosts.houdini.api import plugin
from openpype.pipeline import CreatedInstance
from openpype.lib import EnumDef
class CreateMantraROP(plugin.HoudiniCreator):
"""Mantra ROP"""
identifier = "io.openpype.creators.houdini.mantra_rop"
label = "Mantra ROP"
family = "mantra_rop"
icon = "magic"
defaults = ["master"]
def create(self, subset_name, instance_data, pre_create_data):
import hou # noqa
instance_data.pop("active", None)
instance_data.update({"node_type": "ifd"})
# Add chunk size attribute
instance_data["chunkSize"] = 10
instance = super(CreateMantraROP, self).create(
subset_name,
instance_data,
pre_create_data) # type: CreatedInstance
instance_node = hou.node(instance.get("instance_node"))
ext = pre_create_data.get("image_format")
filepath = "{}{}".format(
hou.text.expandString("$HIP/pyblish/"),
"{}.$F4.{}".format(subset_name, ext)
)
parms = {
# Render Frame Range
"trange": 1,
# Mantra ROP Setting
"vm_picture": filepath,
}
if self.selected_nodes:
# If camera found in selection
# we will use as render camera
camera = None
for node in self.selected_nodes:
if node.type().name() == "cam":
camera = node.path()
if not camera:
self.log.warning("No render camera found in selection")
parms.update({"camera": camera or ""})
instance_node.setParms(parms)
# Lock some Avalon attributes
to_lock = ["family", "id"]
self.lock_parameters(instance_node, to_lock)
def get_pre_create_attr_defs(self):
attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
image_format_enum = [
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
"rad", "rat", "rta", "sgi", "tga", "tif",
]
return attrs + [
EnumDef("image_format",
image_format_enum,
default="exr",
label="Image Format Options")
]
# Extract Import Plane parameters(Should be in the setting)

View file

@ -0,0 +1,56 @@
import hou
import pyblish.api
class CollectInstanceNodeFrameRange(pyblish.api.InstancePlugin):
"""Collect time range frame data for the instance node."""
order = pyblish.api.CollectorOrder + 0.001
label = "Instance Node Frame Range"
hosts = ["houdini"]
def process(self, instance):
node_path = instance.data.get("instance_node")
node = hou.node(node_path) if node_path else None
if not node_path or not node:
self.log.debug("No instance node found for instance: "
"{}".format(instance))
return
frame_data = self.get_frame_data(node)
if not frame_data:
return
self.log.info("Collected time data: {}".format(frame_data))
instance.data.update(frame_data)
def get_frame_data(self, node):
"""Get the frame data: start frame, end frame and steps
Args:
node(hou.Node)
Returns:
dict
"""
data = {}
if node.parm("trange") is None:
self.log.debug("Node has no 'trange' parameter: "
"{}".format(node.path()))
return data
if node.evalParm("trange") == 0:
# Ignore 'render current frame'
self.log.debug("Node '{}' has 'Render current frame' set. "
"Time range data ignored.".format(node.path()))
return data
data["frameStart"] = node.evalParm("f1")
data["frameEnd"] = node.evalParm("f2")
data["byFrameStep"] = node.evalParm("f3")
return data

View file

@ -116,6 +116,6 @@ class CollectInstances(pyblish.api.ContextPlugin):
data["frameStart"] = node.evalParm("f1")
data["frameEnd"] = node.evalParm("f2")
data["steps"] = node.evalParm("f3")
data["byFrameStep"] = node.evalParm("f3")
return data

View file

@ -0,0 +1,136 @@
import re
import os
import hou
import pyblish.api
def get_top_referenced_parm(parm):
processed = set() # disallow infinite loop
while True:
if parm.path() in processed:
raise RuntimeError("Parameter references result in cycle.")
processed.add(parm.path())
ref = parm.getReferencedParm()
if ref.path() == parm.path():
# It returns itself when it doesn't reference
# another parameter
return ref
else:
parm = ref
def evalParmNoFrame(node, parm, pad_character="#"):
parameter = node.parm(parm)
assert parameter, "Parameter does not exist: %s.%s" % (node, parm)
# If the parameter has a parameter reference, then get that
# parameter instead as otherwise `unexpandedString()` fails.
parameter = get_top_referenced_parm(parameter)
# Substitute out the frame numbering with padded characters
try:
raw = parameter.unexpandedString()
except hou.Error as exc:
print("Failed: %s" % parameter)
raise RuntimeError(exc)
def replace(match):
padding = 1
n = match.group(2)
if n and int(n):
padding = int(n)
return pad_character * padding
expression = re.sub(r"(\$F([0-9]*))", replace, raw)
with hou.ScriptEvalContext(parameter):
return hou.expandStringAtFrame(expression, 0)
class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
"""Collect Karma Render Products
Collects the instance.data["files"] for the multipart render product.
Provides:
instance -> files
"""
label = "Karma ROP Render Products"
order = pyblish.api.CollectorOrder + 0.4
hosts = ["houdini"]
families = ["karma_rop"]
def process(self, instance):
rop = hou.node(instance.data.get("instance_node"))
# Collect chunkSize
chunk_size_parm = rop.parm("chunkSize")
if chunk_size_parm:
chunk_size = int(chunk_size_parm.eval())
instance.data["chunkSize"] = chunk_size
self.log.debug("Chunk Size: %s" % chunk_size)
default_prefix = evalParmNoFrame(rop, "picture")
render_products = []
# Default beauty AOV
beauty_product = self.get_render_product_name(
prefix=default_prefix, suffix=None
)
render_products.append(beauty_product)
files_by_aov = {
"beauty": self.generate_expected_files(instance,
beauty_product)
}
filenames = list(render_products)
instance.data["files"] = filenames
for product in render_products:
self.log.debug("Found render product: %s" % product)
if "expectedFiles" not in instance.data:
instance.data["expectedFiles"] = list()
instance.data["expectedFiles"].append(files_by_aov)
def get_render_product_name(self, prefix, suffix):
if suffix:
# Add ".{suffix}" before the extension
prefix_base, ext = os.path.splitext(prefix)
product_name = prefix_base + "." + suffix + ext
else:
product_name = prefix
return product_name
def generate_expected_files(self, instance, path):
"""Create expected files in instance data"""
dir = os.path.dirname(path)
file = os.path.basename(path)
if "#" in file:
pparts = file.split("#")
padding = "%0{}d".format(len(pparts) - 1)
file = pparts[0] + padding + pparts[-1]
if "%" not in file:
return path
expected_files = []
start = instance.data["frameStart"]
end = instance.data["frameEnd"]
for i in range(int(start), (int(end) + 1)):
expected_files.append(
os.path.join(dir, (file % i)).replace("\\", "/"))
return expected_files

View file

@ -0,0 +1,158 @@
import re
import os
import hou
import pyblish.api
def get_top_referenced_parm(parm):
processed = set() # disallow infinite loop
while True:
if parm.path() in processed:
raise RuntimeError("Parameter references result in cycle.")
processed.add(parm.path())
ref = parm.getReferencedParm()
if ref.path() == parm.path():
# It returns itself when it doesn't reference
# another parameter
return ref
else:
parm = ref
def evalParmNoFrame(node, parm, pad_character="#"):
parameter = node.parm(parm)
assert parameter, "Parameter does not exist: %s.%s" % (node, parm)
# If the parameter has a parameter reference, then get that
# parameter instead as otherwise `unexpandedString()` fails.
parameter = get_top_referenced_parm(parameter)
# Substitute out the frame numbering with padded characters
try:
raw = parameter.unexpandedString()
except hou.Error as exc:
print("Failed: %s" % parameter)
raise RuntimeError(exc)
def replace(match):
padding = 1
n = match.group(2)
if n and int(n):
padding = int(n)
return pad_character * padding
expression = re.sub(r"(\$F([0-9]*))", replace, raw)
with hou.ScriptEvalContext(parameter):
return hou.expandStringAtFrame(expression, 0)
class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
"""Collect Mantra Render Products
Collects the instance.data["files"] for the render products.
Provides:
instance -> files
"""
label = "Mantra ROP Render Products"
order = pyblish.api.CollectorOrder + 0.4
hosts = ["houdini"]
families = ["mantra_rop"]
def process(self, instance):
rop = hou.node(instance.data.get("instance_node"))
# Collect chunkSize
chunk_size_parm = rop.parm("chunkSize")
if chunk_size_parm:
chunk_size = int(chunk_size_parm.eval())
instance.data["chunkSize"] = chunk_size
self.log.debug("Chunk Size: %s" % chunk_size)
default_prefix = evalParmNoFrame(rop, "vm_picture")
render_products = []
# Default beauty AOV
beauty_product = self.get_render_product_name(
prefix=default_prefix, suffix=None
)
render_products.append(beauty_product)
files_by_aov = {
"beauty": self.generate_expected_files(instance,
beauty_product)
}
aov_numbers = rop.evalParm("vm_numaux")
if aov_numbers > 0:
# get the filenames of the AOVs
for i in range(1, aov_numbers + 1):
var = rop.evalParm("vm_variable_plane%d" % i)
if var:
aov_name = "vm_filename_plane%d" % i
has_aov_path = rop.evalParm(aov_name)
if has_aov_path:
aov_prefix = evalParmNoFrame(rop, aov_name)
aov_product = self.get_render_product_name(
prefix=aov_prefix, suffix=None
)
render_products.append(aov_product)
files_by_aov[var] = self.generate_expected_files(instance,
aov_product)
for product in render_products:
self.log.debug("Found render product: %s" % product)
filenames = list(render_products)
instance.data["files"] = filenames
# For now by default do NOT try to publish the rendered output
instance.data["publishJobState"] = "Suspended"
instance.data["attachTo"] = [] # stub required data
if "expectedFiles" not in instance.data:
instance.data["expectedFiles"] = list()
instance.data["expectedFiles"].append(files_by_aov)
self.log.debug("expectedFiles: %s" % files_by_aov)
def get_render_product_name(self, prefix, suffix):
if suffix:
# Add ".{suffix}" before the extension
prefix_base, ext = os.path.splitext(prefix)
product_name = prefix_base + "." + suffix + ext
else:
product_name = prefix
return product_name
def generate_expected_files(self, instance, path):
"""Create expected files in instance data"""
dir = os.path.dirname(path)
file = os.path.basename(path)
if "#" in file:
pparts = file.split("#")
padding = "%0{}d".format(len(pparts) - 1)
file = pparts[0] + padding + pparts[-1]
if "%" not in file:
return path
expected_files = []
start = instance.data["frameStart"]
end = instance.data["frameEnd"]
for i in range(int(start), (int(end) + 1)):
expected_files.append(
os.path.join(dir, (file % i)).replace("\\", "/"))
return expected_files

View file

@ -87,6 +87,10 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
prefix=default_prefix, suffix=beauty_suffix
)
render_products.append(beauty_product)
files_by_aov = {
"beauty": self.generate_expected_files(instance,
beauty_product)
}
num_aovs = rop.evalParm("RS_aov")
for index in range(num_aovs):
@ -104,11 +108,21 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
render_products.append(aov_product)
files_by_aov[aov_suffix] = self.generate_expected_files(instance,
aov_product)
for product in render_products:
self.log.debug("Found render product: %s" % product)
filenames = list(render_products)
instance.data["files"] = filenames
filenames = list(render_products)
instance.data["files"] = filenames
# For now by default do NOT try to publish the rendered output
instance.data["publishJobState"] = "Suspended"
instance.data["attachTo"] = [] # stub required data
if "expectedFiles" not in instance.data:
instance.data["expectedFiles"] = list()
instance.data["expectedFiles"].append(files_by_aov)
def get_render_product_name(self, prefix, suffix):
"""Return the output filename using the AOV prefix and suffix"""
@ -133,3 +147,26 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
product_name = prefix
return product_name
def generate_expected_files(self, instance, path):
"""Create expected files in instance data"""
dir = os.path.dirname(path)
file = os.path.basename(path)
if "#" in file:
pparts = file.split("#")
padding = "%0{}d".format(len(pparts) - 1)
file = pparts[0] + padding + pparts[-1]
if "%" not in file:
return path
expected_files = []
start = instance.data["frameStart"]
end = instance.data["frameEnd"]
for i in range(int(start), (int(end) + 1)):
expected_files.append(
os.path.join(dir, (file % i)).replace("\\", "/"))
return expected_files

View file

@ -14,7 +14,12 @@ class IncrementCurrentFile(pyblish.api.ContextPlugin):
label = "Increment current file"
order = pyblish.api.IntegratorOrder + 9.0
hosts = ["houdini"]
families = ["workfile"]
families = ["workfile",
"redshift_rop",
"arnold_rop",
"mantra_rop",
"karma_rop",
"usdrender"]
optional = True
def process(self, context):

View file

@ -663,7 +663,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin):
# test if there is instance of workfile waiting
# to be published.
assert i.data["publish"] is True, (
assert i.data.get("publish", True) is True, (
"Workfile (scene) must be published along")
return i

View file

@ -1,19 +1,27 @@
import hou
import os
import json
import attr
import getpass
from datetime import datetime
import requests
import pyblish.api
# import hou ???
from openpype.pipeline import legacy_io
from openpype.tests.lib import is_in_tests
from openpype_modules.deadline import abstract_submit_deadline
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
from openpype.lib import is_running_from_build
class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
@attr.s
class DeadlinePluginInfo():
SceneFile = attr.ib(default=None)
OutputDriver = attr.ib(default=None)
Version = attr.ib(default=None)
IgnoreInputs = attr.ib(default=True)
class HoudiniSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
"""Submit Solaris USD Render ROPs to Deadline.
Renders are submitted to a Deadline Web Service as
@ -30,83 +38,55 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
order = pyblish.api.IntegratorOrder
hosts = ["houdini"]
families = ["usdrender",
"redshift_rop"]
"redshift_rop",
"mantra_rop",
"karma_rop"]
targets = ["local"]
use_published = True
def process(self, instance):
def get_job_info(self):
job_info = DeadlineJobInfo(Plugin="Houdini")
instance = self._instance
context = instance.context
code = context.data["code"]
filepath = context.data["currentFile"]
filename = os.path.basename(filepath)
comment = context.data.get("comment", "")
deadline_user = context.data.get("deadlineUser", getpass.getuser())
jobname = "%s - %s" % (filename, instance.name)
# Support code prefix label for batch name
batch_name = filename
if code:
batch_name = "{0} - {1}".format(code, batch_name)
job_info.Name = "%s - %s" % (filename, instance.name)
job_info.BatchName = filename
job_info.Plugin = "Houdini"
job_info.UserName = context.data.get(
"deadlineUser", getpass.getuser())
if is_in_tests():
batch_name += datetime.now().strftime("%d%m%Y%H%M%S")
job_info.BatchName += datetime.now().strftime("%d%m%Y%H%M%S")
# Output driver to render
driver = instance[0]
# StartFrame to EndFrame by byFrameStep
# Deadline requires integers in frame range
frames = "{start}-{end}x{step}".format(
start=int(instance.data["frameStart"]),
end=int(instance.data["frameEnd"]),
step=int(instance.data["byFrameStep"]),
)
job_info.Frames = frames
# 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": batch_name,
job_info.Pool = instance.data.get("primaryPool")
job_info.SecondaryPool = instance.data.get("secondaryPool")
job_info.ChunkSize = instance.data.get("chunkSize", 10)
job_info.Comment = context.data.get("comment")
# Job name, as seen in Monitor
"Name": jobname,
# Arbitrary username, for visualisation in Monitor
"UserName": deadline_user,
"Plugin": "Houdini",
"Pool": instance.data.get("primaryPool"),
"secondaryPool": instance.data.get("secondaryPool"),
"Frames": frames,
"ChunkSize": instance.data.get("chunkSize", 10),
"Comment": comment
},
"PluginInfo": {
# Input
"SceneFile": filepath,
"OutputDriver": driver.path(),
# Mandatory for Deadline
# Houdini version without patch number
"Version": hou.applicationVersionString().rsplit(".", 1)[0],
"IgnoreInputs": True
},
# Mandatory for Deadline, may be empty
"AuxFiles": []
}
# Include critical environment variables with submission + api.Session
keys = [
# Submit along the current Avalon tool setup that we launched
# this application with so the Render Slave can build its own
# similar environment using it, e.g. "maya2018;vray4.x;yeti3.1.9"
"AVALON_TOOLS"
"FTRACK_API_KEY",
"FTRACK_API_USER",
"FTRACK_SERVER",
"OPENPYPE_SG_USER",
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_APP_NAME",
"OPENPYPE_DEV",
"OPENPYPE_LOG_NO_COLORS",
"OPENPYPE_VERSION"
]
# Add OpenPype version if we are running from build.
@ -114,61 +94,50 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
keys.append("OPENPYPE_VERSION")
# Add mongo url if it's enabled
if context.data.get("deadlinePassMongoUrl"):
if self._instance.context.data.get("deadlinePassMongoUrl"):
keys.append("OPENPYPE_MONGO")
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
for key in keys:
value = environment.get(key)
if value:
job_info.EnvironmentKeyValue[key] = value
payload["JobInfo"].update({
"EnvironmentKeyValue%d" % index: "{key}={value}".format(
key=key,
value=environment[key]
) for index, key in enumerate(environment)
})
# to recognize job from PYPE for turning Event On/Off
job_info.EnvironmentKeyValue["OPENPYPE_RENDER_JOB"] = "1"
# Include OutputFilename entries
# The first entry also enables double-click to preview rendered
# frames from Deadline Monitor
output_data = {}
for i, filepath in enumerate(instance.data["files"]):
dirname = os.path.dirname(filepath)
fname = os.path.basename(filepath)
output_data["OutputDirectory%d" % i] = dirname.replace("\\", "/")
output_data["OutputFilename%d" % i] = fname
job_info.OutputDirectory += dirname.replace("\\", "/")
job_info.OutputFilename += fname
# For now ensure destination folder exists otherwise HUSK
# will fail to render the output image. This is supposedly fixed
# in new production builds of Houdini
# TODO Remove this workaround with Houdini 18.0.391+
if not os.path.exists(dirname):
self.log.info("Ensuring output directory exists: %s" %
dirname)
os.makedirs(dirname)
return job_info
payload["JobInfo"].update(output_data)
def get_plugin_info(self):
self.submit(instance, payload)
instance = self._instance
context = instance.context
def submit(self, instance, payload):
# Output driver to render
driver = hou.node(instance.data["instance_node"])
hou_major_minor = hou.applicationVersionString().rsplit(".", 1)[0]
AVALON_DEADLINE = legacy_io.Session.get("AVALON_DEADLINE",
"http://localhost:8082")
assert AVALON_DEADLINE, "Requires AVALON_DEADLINE"
plugin_info = DeadlinePluginInfo(
SceneFile=context.data["currentFile"],
OutputDriver=driver.path(),
Version=hou_major_minor,
IgnoreInputs=True
)
plugin = payload["JobInfo"]["Plugin"]
self.log.info("Using Render Plugin : {}".format(plugin))
return attr.asdict(plugin_info)
self.log.info("Submitting..")
self.log.debug(json.dumps(payload, indent=4, sort_keys=True))
# E.g. http://192.168.0.1:8082/api/jobs
url = "{}/api/jobs".format(AVALON_DEADLINE)
response = requests.post(url, json=payload)
if not response.ok:
raise Exception(response.text)
def process(self, instance):
super(HoudiniSubmitDeadline, self).process(instance)
# TODO: Avoid the need for this logic here, needed for submit publish
# Store output dir for unified publisher (filesequence)
output_dir = os.path.dirname(instance.data["files"][0])
instance.data["outputDir"] = output_dir
instance.data["deadlineSubmissionJob"] = response.json()
instance.data["toBeRenderedOn"] = "deadline"

View file

@ -122,7 +122,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"celaction", "aftereffects", "harmony"]
families = ["render.farm", "prerender.farm",
"renderlayer", "imagesequence", "maxrender", "vrayscene"]
"renderlayer", "imagesequence",
"maxrender", "vrayscene",
"mantra_rop", "karma_rop"]
aov_filter = {"maya": [r".*([Bb]eauty).*"],
"aftereffects": [r".*"], # for everything from AE
@ -140,7 +142,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
"FTRACK_SERVER",
"AVALON_APP_NAME",
"OPENPYPE_USERNAME",
"OPENPYPE_SG_USER",
"OPENPYPE_VERSION",
"OPENPYPE_SG_USER"
]
# Add OpenPype version if we are running from build.