Merge branch 'develop' into feature/PYPE-496-nuke-input-process-baking-mov

This commit is contained in:
Jakub Jezek 2019-10-04 16:46:03 +02:00
commit 02c6ca2fef
14 changed files with 230 additions and 51 deletions

View file

@ -1,7 +1,32 @@
# Pype changelog #
Welcome to pype changelog
## 2.1 ##
## 2.2.0 ##
_release date: 8 Sept 2019_
**new**:
- _(pype)_ add customisable workflow for creating quicktimes from renders or playblasts
- _(nuke)_ option to choose deadline chunk size on write nodes
- _(nukestudio)_ added option to publish soft effects (subTrackItems) from NukeStudio as subsets including LUT files. these can then be loaded in nuke or NukeStudio
- _(nuke)_ option to build nuke script from previously published latest versions of plate and render subsets.
- _(nuke)_ nuke writes now have deadline tab.
- _(ftrack)_ Prepare Project action can now be used for creating the base folder structure on disk and in ftrack, setting up all the initial project attributes and it automatically prepares `pype_project_config` folder for the given project.
- _(clockify)_ Added support for time tracking in clockify. This currently in addition to ftrack time logs, but does not completely replace them.
- _(pype)_ any attributes in Creator and Loader plugins can now be customised using pype preset system
**changed**:
- nukestudio now uses workio API for workfiles
- _(maya)_ "FIX FPS" prompt in maya now appears in the middle of the screen
- _(muster)_ can now be configured with custom templates
- _(pype)_ global publishing plugins can now be configured using presets as well as host specific ones
**fix**:
- wrong version retrieval from path in certain scenarios
- nuke reset resolution wasn't working in certain scenarios
## 2.1.0 ##
_release date: 6 Aug 2019_
A large cleanup release. Most of the change are under the hood.

View file

@ -35,6 +35,8 @@ def patched_discover(superclass):
plugins = _original_discover(superclass)
# determine host application to use for finding presets
if avalon.registered_host() is None:
return plugins
host = avalon.registered_host().__name__.split(".")[-1]
# map plugin superclass to preset json. Currenly suppoted is load and

View file

@ -39,19 +39,17 @@ SHAPE_ATTRS = {"castsShadows",
"doubleSided",
"opposite"}
RENDER_ATTRS = {"vray":
{
RENDER_ATTRS = {"vray": {
"node": "vraySettings",
"prefix": "fileNamePrefix",
"padding": "fileNamePadding",
"ext": "imageFormatStr"
},
"default":
{
},
"default": {
"node": "defaultRenderGlobals",
"prefix": "imageFilePrefix",
"padding": "extensionPadding"
}
}
}
@ -341,19 +339,6 @@ def undo_chunk():
cmds.undoInfo(closeChunk=True)
@contextlib.contextmanager
def renderlayer(layer):
"""Set the renderlayer during the context"""
original = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True)
try:
cmds.editRenderLayerGlobals(currentRenderLayer=layer)
yield
finally:
cmds.editRenderLayerGlobals(currentRenderLayer=original)
@contextlib.contextmanager
def evaluation(mode="off"):
"""Set the evaluation manager during context.
@ -832,7 +817,8 @@ def is_visible(node,
# Display layers set overrideEnabled and overrideVisibility on members
if cmds.attributeQuery('overrideEnabled', node=node, exists=True):
override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node))
override_visibility = cmds.getAttr('{}.overrideVisibility'.format(node))
override_visibility = cmds.getAttr('{}.overrideVisibility'.format(
node))
if override_enabled and override_visibility:
return False
@ -854,8 +840,8 @@ def extract_alembic(file,
startFrame=None,
endFrame=None,
selection=True,
uvWrite= True,
eulerFilter= True,
uvWrite=True,
eulerFilter=True,
dataFormat="ogawa",
verbose=False,
**kwargs):
@ -1470,8 +1456,8 @@ def apply_shaders(relationships, shadernodes, nodes):
member_uuids = [member["uuid"] for member in data["members"]]
filtered_nodes = list()
for uuid in member_uuids:
filtered_nodes.extend(nodes_by_id[uuid])
for m_uuid in member_uuids:
filtered_nodes.extend(nodes_by_id[m_uuid])
id_shading_engines = shading_engines_by_id[shader_uuid]
if not id_shading_engines:
@ -2110,6 +2096,7 @@ def bake_to_world_space(nodes,
return world_space_nodes
def load_capture_preset(path=None, data=None):
import capture_gui
import capture
@ -2119,14 +2106,14 @@ def load_capture_preset(path=None, data=None):
else:
path = path
preset = capture_gui.lib.load_json(path)
print preset
print(preset)
options = dict()
# CODEC
id = 'Codec'
for key in preset[id]:
options[str(key)]= preset[id][key]
options[str(key)] = preset[id][key]
# GENERIC
id = 'Generic'
@ -2142,7 +2129,6 @@ def load_capture_preset(path=None, data=None):
options['height'] = preset[id]['height']
options['width'] = preset[id]['width']
# DISPLAY OPTIONS
id = 'Display Options'
disp_options = {}
@ -2154,7 +2140,6 @@ def load_capture_preset(path=None, data=None):
options['display_options'] = disp_options
# VIEWPORT OPTIONS
temp_options = {}
id = 'Renderer'
@ -2163,11 +2148,12 @@ def load_capture_preset(path=None, data=None):
temp_options2 = {}
id = 'Viewport Options'
light_options = { 0: "default",
1: 'all',
2: 'selected',
3: 'flat',
4: 'nolights'}
light_options = {
0: "default",
1: 'all',
2: 'selected',
3: 'flat',
4: 'nolights'}
for key in preset[id]:
if key == 'high_quality':
temp_options2['multiSampleEnable'] = True
@ -2190,7 +2176,10 @@ def load_capture_preset(path=None, data=None):
else:
temp_options[str(key)] = preset[id][key]
for key in ['override_viewport_options', 'high_quality', 'alphaCut', "gpuCacheDisplayFilter"]:
for key in ['override_viewport_options',
'high_quality',
'alphaCut',
'gpuCacheDisplayFilter']:
temp_options.pop(key, None)
for key in ['ssaoEnable']:
@ -2199,7 +2188,6 @@ def load_capture_preset(path=None, data=None):
options['viewport_options'] = temp_options
options['viewport2_options'] = temp_options2
# use active sound track
scene = capture.parse_active_scene()
options['sound'] = scene['sound']
@ -2363,31 +2351,51 @@ class shelf():
if item['type'] == 'button':
self.addButon(item['name'], command=item['command'])
if item['type'] == 'menuItem':
self.addMenuItem(item['parent'], item['name'], command=item['command'])
self.addMenuItem(item['parent'],
item['name'],
command=item['command'])
if item['type'] == 'subMenu':
self.addMenuItem(item['parent'], item['name'], command=item['command'])
self.addMenuItem(item['parent'],
item['name'],
command=item['command'])
def addButon(self, label, icon="commandButton.png", command=_null, doubleCommand=_null):
'''Adds a shelf button with the specified label, command, double click command and image.'''
def addButon(self, label, icon="commandButton.png",
command=_null, doubleCommand=_null):
'''
Adds a shelf button with the specified label, command,
double click command and image.
'''
cmds.setParent(self.name)
if icon:
icon = self.iconPath + icon
cmds.shelfButton(width=37, height=37, image=icon, l=label, command=command, dcc=doubleCommand, imageOverlayLabel=label, olb=self.labelBackground, olc=self.labelColour)
cmds.shelfButton(width=37, height=37, image=icon, label=label,
command=command, dcc=doubleCommand,
imageOverlayLabel=label, olb=self.labelBackground,
olc=self.labelColour)
def addMenuItem(self, parent, label, command=_null, icon=""):
'''Adds a shelf button with the specified label, command, double click command and image.'''
'''
Adds a shelf button with the specified label, command,
double click command and image.
'''
if icon:
icon = self.iconPath + icon
return cmds.menuItem(p=parent, l=label, c=command, i="")
return cmds.menuItem(p=parent, label=label, c=command, i="")
def addSubMenu(self, parent, label, icon=None):
'''Adds a sub menu item with the specified label and icon to the specified parent popup menu.'''
'''
Adds a sub menu item with the specified label and icon to
the specified parent popup menu.
'''
if icon:
icon = self.iconPath + icon
return cmds.menuItem(p=parent, l=label, i=icon, subMenu=1)
return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1)
def _cleanOldShelf(self):
'''Checks if the shelf exists and empties it if it does or creates it if it does not.'''
'''
Checks if the shelf exists and empties it if it does
or creates it if it does not.
'''
if cmds.shelfLayout(self.name, ex=1):
if cmds.shelfLayout(self.name, q=1, ca=1):
for each in cmds.shelfLayout(self.name, q=1, ca=1):

View file

@ -440,6 +440,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
Returns:
None
"""
src = os.path.normpath(src)
dst = os.path.normpath(dst)
self.log.debug("Copying file .. {} -> {}".format(src, dst))
dirname = os.path.dirname(dst)

View file

@ -7,5 +7,5 @@ class CreateAssembly(avalon.maya.Creator):
name = "assembly"
label = "Assembly"
family = "assembly"
icon = "boxes"
icon = "cubes"
defaults = ['Main']

View file

@ -7,5 +7,5 @@ class CreateLayout(avalon.maya.Creator):
name = "layoutMain"
label = "Layout"
family = "layout"
icon = "boxes"
icon = "cubes"
defaults = ["Main"]

View file

@ -7,5 +7,5 @@ class CreateSetDress(avalon.maya.Creator):
name = "setdressMain"
label = "Set Dress"
family = "setdress"
icon = "boxes"
icon = "cubes"
defaults = ["Main", "Anim"]

View file

@ -45,7 +45,23 @@ class MayaAsciiLoader(pype.maya.plugin.ReferenceLoader):
cmds.setAttr(groupName + ".useOutlinerColor", 1)
cmds.setAttr(groupName + ".outlinerColor",
c[0], c[1], c[2])
cmds.setAttr(groupName + ".displayHandle", 1)
# get bounding box
bbox = cmds.exactWorldBoundingBox(groupName)
# get pivot position on world space
pivot = cmds.xform(groupName, q=True, sp=True, ws=True)
# center of bounding box
cx = (bbox[0] + bbox[3]) / 2
cy = (bbox[1] + bbox[4]) / 2
cz = (bbox[2] + bbox[5]) / 2
# add pivot position to calculate offset
cx = cx + pivot[0]
cy = cy + pivot[1]
cz = cz + pivot[2]
# set selection handle offset to center of bounding box
cmds.setAttr(groupName + ".selectHandleX", cx)
cmds.setAttr(groupName + ".selectHandleY", cy)
cmds.setAttr(groupName + ".selectHandleZ", cz)
return nodes
def switch(self, container, representation):

View file

@ -1,4 +1,4 @@
from avalon import api
import pype.maya.plugin
import os
from pypeapp import config
@ -58,6 +58,9 @@ class ReferenceLoader(pype.maya.plugin.ReferenceLoader):
for root in roots:
root.setParent(groupNode)
cmds.setAttr(groupName + ".displayHandle", 1)
groupNode
presets = config.get_presets(project=os.environ['AVALON_PROJECT'])
colors = presets['plugins']['maya']['load']['colors']
c = colors.get(family)
@ -67,11 +70,30 @@ class ReferenceLoader(pype.maya.plugin.ReferenceLoader):
self[:] = nodes
cmds.setAttr(groupName + ".displayHandle", 1)
# get bounding box
bbox = cmds.exactWorldBoundingBox(groupName)
# get pivot position on world space
pivot = cmds.xform(groupName, q=True, sp=True, ws=True)
# center of bounding box
cx = (bbox[0] + bbox[3]) / 2
cy = (bbox[1] + bbox[4]) / 2
cz = (bbox[2] + bbox[5]) / 2
# add pivot position to calculate offset
cx = cx + pivot[0]
cy = cy + pivot[1]
cz = cz + pivot[2]
# set selection handle offset to center of bounding box
cmds.setAttr(groupName + ".selectHandleX", cx)
cmds.setAttr(groupName + ".selectHandleY", cy)
cmds.setAttr(groupName + ".selectHandleZ", cz)
return nodes
def switch(self, container, representation):
self.update(container, representation)
# for backwards compatibility
class AbcLoader(ReferenceLoader):
label = "Deprecated loader (don't use)"
@ -79,6 +101,7 @@ class AbcLoader(ReferenceLoader):
representations = ["abc"]
tool_names = []
# for backwards compatibility
class ModelLoader(ReferenceLoader):
label = "Deprecated loader (don't use)"

View file

@ -16,3 +16,9 @@ class ValidateActiveViewer(pyblish.api.ContextPlugin):
assert viewer_process_node, (
"Missing active viewer process! Please click on output write node and push key number 1-9"
)
active_viewer = context.data["ActiveViewer"]
active_input = active_viewer.activeInput()
assert active_input is not None, (
"Missing active viewer input! Please click on output write node and push key number 1-9"
)

View file

@ -14,6 +14,7 @@ class LoadLuts(api.Loader):
order = 0
icon = "cc"
color = style.colors.light
ignore_attr = ["useLifetime"]
def load(self, context, name, namespace, data):
"""
@ -83,6 +84,8 @@ class LoadLuts(api.Loader):
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
if isinstance(v, list) and len(v) > 4:
node[k].setAnimated()
for i, value in enumerate(v):
@ -194,6 +197,8 @@ class LoadLuts(api.Loader):
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
if isinstance(v, list) and len(v) > 3:
node[k].setAnimated()
for i, value in enumerate(v):

View file

@ -14,6 +14,7 @@ class LoadLutsInputProcess(api.Loader):
order = 0
icon = "eye"
color = style.colors.alert
ignore_attr = ["useLifetime"]
def load(self, context, name, namespace, data):
"""
@ -83,6 +84,8 @@ class LoadLutsInputProcess(api.Loader):
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
if isinstance(v, list) and len(v) > 4:
node[k].setAnimated()
for i, value in enumerate(v):
@ -196,6 +199,8 @@ class LoadLutsInputProcess(api.Loader):
for ef_name, ef_val in nodes_order.items():
node = nuke.createNode(ef_val["class"])
for k, v in ef_val["node"].items():
if k in self.ignore_attr:
continue
if isinstance(v, list) and len(v) > 3:
node[k].setAnimated()
for i, value in enumerate(v):

View file

@ -0,0 +1,25 @@
import nuke
import pyblish.api
class CollectWriteLegacy(pyblish.api.ContextPlugin):
"""Collect legacy write nodes."""
order = pyblish.api.CollectorOrder
label = "Collect Write Legacy"
hosts = ["nuke", "nukeassist"]
def process(self, context):
for node in nuke.allNodes():
if node.Class() != "Write":
continue
if "avalon" not in node.knobs().keys():
continue
instance = context.create_instance(
node.name(), family="write.legacy"
)
instance.append(node)

View file

@ -0,0 +1,62 @@
import toml
import os
import nuke
from avalon import api
import pyblish.api
class RepairWriteLegacyAction(pyblish.api.Action):
label = "Repair"
icon = "wrench"
on = "failed"
def process(self, context, plugin):
# Get the errored instances
failed = []
for result in context.data["results"]:
if (result["error"] is not None and result["instance"] is not None
and result["instance"] not in failed):
failed.append(result["instance"])
# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(failed, plugin)
for instance in instances:
data = toml.loads(instance[0]["avalon"].value())
data["xpos"] = instance[0].xpos()
data["ypos"] = instance[0].ypos()
data["input"] = instance[0].input(0)
data["publish"] = instance[0]["publish"].value()
data["render"] = instance[0]["render"].value()
data["render_farm"] = instance[0]["render_farm"].value()
nuke.delete(instance[0])
family = "render{}".format(os.environ["AVALON_TASK"].capitalize())
api.create(data["subset"], data["asset"], family)
node = nuke.toNode(data["subset"])
node.setXYpos(data["xpos"], data["ypos"])
node.setInput(0, data["input"])
node["publish"].setValue(data["publish"])
node["render"].setValue(data["render"])
node["render_farm"].setValue(data["render_farm"])
class ValidateWriteLegacy(pyblish.api.InstancePlugin):
"""Validate legacy write nodes."""
order = pyblish.api.ValidatorOrder
optional = True
families = ["write.legacy"]
label = "Write Legacy"
hosts = ["nuke"]
actions = [RepairWriteLegacyAction]
def process(self, instance):
msg = "Clean up legacy write node \"{}\"".format(instance)
assert False, msg