mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge branch 'develop' into feature/PYPE-496-nuke-input-process-baking-mov
This commit is contained in:
commit
02c6ca2fef
14 changed files with 230 additions and 51 deletions
27
changelog.md
27
changelog.md
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ class CreateAssembly(avalon.maya.Creator):
|
|||
name = "assembly"
|
||||
label = "Assembly"
|
||||
family = "assembly"
|
||||
icon = "boxes"
|
||||
icon = "cubes"
|
||||
defaults = ['Main']
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ class CreateLayout(avalon.maya.Creator):
|
|||
name = "layoutMain"
|
||||
label = "Layout"
|
||||
family = "layout"
|
||||
icon = "boxes"
|
||||
icon = "cubes"
|
||||
defaults = ["Main"]
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ class CreateSetDress(avalon.maya.Creator):
|
|||
name = "setdressMain"
|
||||
label = "Set Dress"
|
||||
family = "setdress"
|
||||
icon = "boxes"
|
||||
icon = "cubes"
|
||||
defaults = ["Main", "Anim"]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
25
pype/plugins/nuke/publish/collect_legacy_write.py
Normal file
25
pype/plugins/nuke/publish/collect_legacy_write.py
Normal 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)
|
||||
62
pype/plugins/nuke/publish/validate_write_legacy.py
Normal file
62
pype/plugins/nuke/publish/validate_write_legacy.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue