Merge branch 'develop' into enhancement/3dsmax_review_render_ratio_and_viewport_tweaks

This commit is contained in:
Kayla Man 2023-11-09 21:21:37 +08:00
commit c0fbe2c091
135 changed files with 2925 additions and 812 deletions

View file

@ -74,11 +74,6 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
register_loader_plugin_path(LOAD_PATH)
register_creator_plugin_path(CREATE_PATH)
log.info(PUBLISH_PATH)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)
register_event_callback("application.launched", application_launch)
@ -186,11 +181,6 @@ def application_launch():
check_inventory()
def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle layer visibility on instance toggles."""
instance[0].Visible = new_value
def ls():
"""Yields containers from active AfterEffects document.

View file

@ -32,7 +32,7 @@ class BlendLoader(plugin.AssetLoader):
empties = [obj for obj in objects if obj.type == 'EMPTY']
for empty in empties:
if empty.get(AVALON_PROPERTY):
if empty.get(AVALON_PROPERTY) and empty.parent is None:
return empty
return None
@ -90,6 +90,7 @@ class BlendLoader(plugin.AssetLoader):
members.append(data)
container = self._get_asset_container(data_to.objects)
print(container)
assert container, "No asset group found"
container.name = group_name
@ -100,8 +101,11 @@ class BlendLoader(plugin.AssetLoader):
# Link all the container children to the collection
for obj in container.children_recursive:
print(obj)
bpy.context.scene.collection.objects.link(obj)
print("")
# Remove the library from the blend file
library = bpy.data.libraries.get(bpy.path.basename(libpath))
bpy.data.libraries.remove(library)

View file

@ -31,11 +31,12 @@ class CollectReview(pyblish.api.InstancePlugin):
focal_length = cameras[0].data.lens
# get isolate objects list from meshes instance members .
# get isolate objects list from meshes instance members.
types = {"MESH", "GPENCIL"}
isolate_objects = [
obj
for obj in instance
if isinstance(obj, bpy.types.Object) and obj.type == "MESH"
if isinstance(obj, bpy.types.Object) and obj.type in types
]
if not instance.data.get("remove"):

View file

@ -28,16 +28,22 @@ class ExtractBlend(publish.Extractor):
for obj in instance:
data_blocks.add(obj)
# Pack used images in the blend files.
if obj.type == 'MESH':
for material_slot in obj.material_slots:
mat = material_slot.material
if mat and mat.use_nodes:
tree = mat.node_tree
if tree.type == 'SHADER':
for node in tree.nodes:
if node.bl_idname == 'ShaderNodeTexImage':
if node.image:
node.image.pack()
if obj.type != 'MESH':
continue
for material_slot in obj.material_slots:
mat = material_slot.material
if not(mat and mat.use_nodes):
continue
tree = mat.node_tree
if tree.type != 'SHADER':
continue
for node in tree.nodes:
if node.bl_idname != 'ShaderNodeTexImage':
continue
# Check if image is not packed already
# and pack it if not.
if node.image and node.image.packed_file is None:
node.image.pack()
bpy.data.libraries.write(filepath, data_blocks)

View file

@ -0,0 +1,87 @@
from openpype.pipeline import (
load,
get_representation_path,
)
from openpype.hosts.fusion.api import (
imprint_container,
get_current_comp,
comp_lock_and_undo_chunk
)
from openpype.hosts.fusion.api.lib import get_fusion_module
class FusionLoadUSD(load.LoaderPlugin):
"""Load USD into Fusion
Support for USD was added since Fusion 18.5
"""
families = ["*"]
representations = ["*"]
extensions = {"usd", "usda", "usdz"}
label = "Load USD"
order = -10
icon = "code-fork"
color = "orange"
tool_type = "uLoader"
@classmethod
def apply_settings(cls, project_settings, system_settings):
super(FusionLoadUSD, cls).apply_settings(project_settings,
system_settings)
if cls.enabled:
# Enable only in Fusion 18.5+
fusion = get_fusion_module()
version = fusion.GetVersion()
major = version[1]
minor = version[2]
is_usd_supported = (major, minor) >= (18, 5)
cls.enabled = is_usd_supported
def load(self, context, name, namespace, data):
# Fallback to asset name when namespace is None
if namespace is None:
namespace = context['asset']['name']
# Create the Loader with the filename path set
comp = get_current_comp()
with comp_lock_and_undo_chunk(comp, "Create tool"):
path = self.fname
args = (-32768, -32768)
tool = comp.AddTool(self.tool_type, *args)
tool["Filename"] = path
imprint_container(tool,
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__)
def switch(self, container, representation):
self.update(container, representation)
def update(self, container, representation):
tool = container["_tool"]
assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
comp = tool.Comp()
path = get_representation_path(representation)
with comp_lock_and_undo_chunk(comp, "Update tool"):
tool["Filename"] = path
# Update the imprinted representation
tool.SetData("avalon.representation", str(representation["_id"]))
def remove(self, container):
tool = container["_tool"]
assert tool.ID == self.tool_type, f"Must be {self.tool_type}"
comp = tool.Comp()
with comp_lock_and_undo_chunk(comp, "Remove tool"):
tool.Delete()

View file

@ -173,6 +173,7 @@ def install():
os.remove(filepath)
icon = get_openpype_icon_filepath()
tab_menu_label = os.environ.get("AVALON_LABEL") or "AYON"
# Create context only to get creator plugins, so we don't reset and only
# populate what we need to retrieve the list of creator plugins
@ -197,14 +198,14 @@ def install():
if not network_categories:
continue
key = "openpype_create.{}".format(identifier)
key = "ayon_create.{}".format(identifier)
log.debug(f"Registering {key}")
script = CREATE_SCRIPT.format(identifier=identifier)
data = {
"script": script,
"language": hou.scriptLanguage.Python,
"icon": icon,
"help": "Create OpenPype publish instance for {}".format(
"help": "Create Ayon publish instance for {}".format(
creator.label
),
"help_url": None,
@ -213,7 +214,7 @@ def install():
"cop_viewer_categories": [],
"network_op_type": None,
"viewer_op_type": None,
"locations": ["OpenPype"]
"locations": [tab_menu_label]
}
label = "Create {}".format(creator.label)
tool = hou.shelves.tool(key)

View file

@ -11,20 +11,21 @@ import json
import six
from openpype.lib import StringTemplate
from openpype.client import get_asset_by_name
from openpype.client import get_project, get_asset_by_name
from openpype.settings import get_current_project_settings
from openpype.pipeline import (
Anatomy,
get_current_project_name,
get_current_asset_name,
registered_host
)
from openpype.pipeline.context_tools import (
get_current_context_template_data,
get_current_project_asset
registered_host,
get_current_context,
get_current_host_name,
)
from openpype.pipeline.create import CreateContext
from openpype.pipeline.template_data import get_template_data
from openpype.pipeline.context_tools import get_current_project_asset
from openpype.widgets import popup
from openpype.tools.utils.host_tools import get_tool_by_name
from openpype.pipeline.create import CreateContext
import hou
@ -568,9 +569,9 @@ def get_template_from_value(key, value):
return parm
def get_frame_data(node, handle_start=0, handle_end=0, log=None):
"""Get the frame data: start frame, end frame, steps,
start frame with start handle and end frame with end handle.
def get_frame_data(node, log=None):
"""Get the frame data: `frameStartHandle`, `frameEndHandle`
and `byFrameStep`.
This function uses Houdini node's `trange`, `t1, `t2` and `t3`
parameters as the source of truth for the full inclusive frame
@ -578,20 +579,17 @@ def get_frame_data(node, handle_start=0, handle_end=0, log=None):
range including the handles.
The non-inclusive frame start and frame end without handles
are computed by subtracting the handles from the inclusive
can be computed by subtracting the handles from the inclusive
frame range.
Args:
node (hou.Node): ROP node to retrieve frame range from,
the frame range is assumed to be the frame range
*including* the start and end handles.
handle_start (int): Start handles.
handle_end (int): End handles.
log (logging.Logger): Logger to log to.
Returns:
dict: frame data for start, end, steps,
start with handle and end with handle
dict: frame data for `frameStartHandle`, `frameEndHandle`
and `byFrameStep`.
"""
@ -622,11 +620,6 @@ def get_frame_data(node, handle_start=0, handle_end=0, log=None):
data["frameEndHandle"] = int(node.evalParm("f2"))
data["byFrameStep"] = node.evalParm("f3")
data["handleStart"] = handle_start
data["handleEnd"] = handle_end
data["frameStart"] = data["frameStartHandle"] + data["handleStart"]
data["frameEnd"] = data["frameEndHandle"] - data["handleEnd"]
return data
@ -804,6 +797,45 @@ def get_camera_from_container(container):
return cameras[0]
def get_current_context_template_data_with_asset_data():
"""
TODOs:
Support both 'assetData' and 'folderData' in future.
"""
context = get_current_context()
project_name = context["project_name"]
asset_name = context["asset_name"]
task_name = context["task_name"]
host_name = get_current_host_name()
anatomy = Anatomy(project_name)
project_doc = get_project(project_name)
asset_doc = get_asset_by_name(project_name, asset_name)
# get context specific vars
asset_data = asset_doc["data"]
# compute `frameStartHandle` and `frameEndHandle`
frame_start = asset_data.get("frameStart")
frame_end = asset_data.get("frameEnd")
handle_start = asset_data.get("handleStart")
handle_end = asset_data.get("handleEnd")
if frame_start is not None and handle_start is not None:
asset_data["frameStartHandle"] = frame_start - handle_start
if frame_end is not None and handle_end is not None:
asset_data["frameEndHandle"] = frame_end + handle_end
template_data = get_template_data(
project_doc, asset_doc, task_name, host_name
)
template_data["root"] = anatomy.roots
template_data["assetData"] = asset_data
return template_data
def get_context_var_changes():
"""get context var changes."""
@ -823,7 +855,7 @@ def get_context_var_changes():
return houdini_vars_to_update
# Get Template data
template_data = get_current_context_template_data()
template_data = get_current_context_template_data_with_asset_data()
# Set Houdini Vars
for item in houdini_vars:
@ -978,7 +1010,7 @@ def self_publish():
def add_self_publish_button(node):
"""Adds a self publish button to the rop node."""
label = os.environ.get("AVALON_LABEL") or "OpenPype"
label = os.environ.get("AVALON_LABEL") or "AYON"
button_parm = hou.ButtonParmTemplate(
"ayon_self_publish",

View file

@ -3,7 +3,6 @@
import os
import sys
import logging
import contextlib
import hou # noqa
@ -66,10 +65,6 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
register_event_callback("open", on_open)
register_event_callback("new", on_new)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)
self._has_been_setup = True
# add houdini vendor packages
hou_pythonpath = os.path.join(HOUDINI_HOST_DIR, "vendor")
@ -406,54 +401,3 @@ def _set_context_settings():
lib.reset_framerange()
lib.update_houdini_vars_context()
def on_pyblish_instance_toggled(instance, new_value, old_value):
"""Toggle saver tool passthrough states on instance toggles."""
@contextlib.contextmanager
def main_take(no_update=True):
"""Enter root take during context"""
original_take = hou.takes.currentTake()
original_update_mode = hou.updateModeSetting()
root = hou.takes.rootTake()
has_changed = False
try:
if original_take != root:
has_changed = True
if no_update:
hou.setUpdateMode(hou.updateMode.Manual)
hou.takes.setCurrentTake(root)
yield
finally:
if has_changed:
if no_update:
hou.setUpdateMode(original_update_mode)
hou.takes.setCurrentTake(original_take)
if not instance.data.get("_allowToggleBypass", True):
return
nodes = instance[:]
if not nodes:
return
# Assume instance node is first node
instance_node = nodes[0]
if not hasattr(instance_node, "isBypassed"):
# Likely not a node that can actually be bypassed
log.debug("Can't bypass node: %s", instance_node.path())
return
if instance_node.isBypassed() != (not old_value):
print("%s old bypass state didn't match old instance state, "
"updating anyway.." % instance_node.path())
try:
# Go into the main take, because when in another take changing
# the bypass state of a note cannot be done due to it being locked
# by default.
with main_take(no_update=True):
instance_node.bypass(not new_value)
except hou.PermissionError as exc:
log.warning("%s - %s", instance_node.path(), exc)

View file

@ -7,10 +7,11 @@ from openpype.settings import get_project_settings
from openpype.pipeline import get_current_project_name
from openpype.lib import StringTemplate
from openpype.pipeline.context_tools import get_current_context_template_data
import hou
from .lib import get_current_context_template_data_with_asset_data
log = logging.getLogger("openpype.hosts.houdini.shelves")
@ -23,29 +24,33 @@ def generate_shelves():
# load configuration of houdini shelves
project_name = get_current_project_name()
project_settings = get_project_settings(project_name)
shelves_set_config = project_settings["houdini"]["shelves"]
shelves_configs = project_settings["houdini"]["shelves"]
if not shelves_set_config:
if not shelves_configs:
log.debug("No custom shelves found in project settings.")
return
# Get Template data
template_data = get_current_context_template_data()
template_data = get_current_context_template_data_with_asset_data()
for config in shelves_configs:
selected_option = config["options"]
shelf_set_config = config[selected_option]
for shelf_set_config in shelves_set_config:
shelf_set_filepath = shelf_set_config.get('shelf_set_source_path')
shelf_set_os_filepath = shelf_set_filepath[current_os]
if shelf_set_os_filepath:
shelf_set_os_filepath = get_path_using_template_data(
shelf_set_os_filepath, template_data
)
if not os.path.isfile(shelf_set_os_filepath):
log.error("Shelf path doesn't exist - "
"{}".format(shelf_set_os_filepath))
continue
if shelf_set_filepath:
shelf_set_os_filepath = shelf_set_filepath[current_os]
if shelf_set_os_filepath:
shelf_set_os_filepath = get_path_using_template_data(
shelf_set_os_filepath, template_data
)
if not os.path.isfile(shelf_set_os_filepath):
log.error("Shelf path doesn't exist - "
"{}".format(shelf_set_os_filepath))
continue
hou.shelves.newShelfSet(file_path=shelf_set_os_filepath)
continue
hou.shelves.loadFile(shelf_set_os_filepath)
continue
shelf_set_name = shelf_set_config.get('shelf_set_name')
if not shelf_set_name:

View file

@ -3,6 +3,7 @@
from openpype.hosts.houdini.api import plugin
from openpype.pipeline import CreatedInstance, CreatorError
from openpype.lib import EnumDef
import hou
class CreateBGEO(plugin.HoudiniCreator):
@ -13,7 +14,6 @@ class CreateBGEO(plugin.HoudiniCreator):
icon = "gears"
def create(self, subset_name, instance_data, pre_create_data):
import hou
instance_data.pop("active", None)
@ -90,3 +90,9 @@ class CreateBGEO(plugin.HoudiniCreator):
return attrs + [
EnumDef("bgeo_type", bgeo_enum, label="BGEO Options"),
]
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]

View file

@ -45,6 +45,11 @@ class CreateCompositeSequence(plugin.HoudiniCreator):
instance_node.setParms(parms)
# Manually set f1 & f2 to $FSTART and $FEND respectively
# to match other Houdini nodes default.
instance_node.parm("f1").setExpression("$FSTART")
instance_node.parm("f2").setExpression("$FEND")
# Lock any parameters in this list
to_lock = ["prim_to_detail_pattern"]
self.lock_parameters(instance_node, to_lock)

View file

@ -5,6 +5,7 @@ from openpype.client import (
get_subsets,
)
from openpype.hosts.houdini.api import plugin
import hou
class CreateHDA(plugin.HoudiniCreator):
@ -35,7 +36,6 @@ class CreateHDA(plugin.HoudiniCreator):
def create_instance_node(
self, node_name, parent, node_type="geometry"):
import hou
parent_node = hou.node("/obj")
if self.selected_nodes:
@ -81,3 +81,8 @@ class CreateHDA(plugin.HoudiniCreator):
pre_create_data) # type: plugin.CreatedInstance
return instance
def get_network_categories(self):
return [
hou.objNodeTypeCategory()
]

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating Redshift proxies."""
from openpype.hosts.houdini.api import plugin
from openpype.pipeline import CreatedInstance
import hou
class CreateRedshiftProxy(plugin.HoudiniCreator):
@ -12,7 +12,7 @@ class CreateRedshiftProxy(plugin.HoudiniCreator):
icon = "magic"
def create(self, subset_name, instance_data, pre_create_data):
import hou # noqa
# Remove the active, we are checking the bypass flag of the nodes
instance_data.pop("active", None)
@ -28,7 +28,7 @@ class CreateRedshiftProxy(plugin.HoudiniCreator):
instance = super(CreateRedshiftProxy, self).create(
subset_name,
instance_data,
pre_create_data) # type: CreatedInstance
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
@ -44,3 +44,9 @@ class CreateRedshiftProxy(plugin.HoudiniCreator):
# Lock some Avalon attributes
to_lock = ["family", "id", "prim_to_detail_pattern"]
self.lock_parameters(instance_node, to_lock)
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]

View file

@ -54,6 +54,7 @@ class CreateStaticMesh(plugin.HoudiniCreator):
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.objNodeTypeCategory(),
hou.sopNodeTypeCategory()
]

View file

@ -40,6 +40,7 @@ class CreateVDBCache(plugin.HoudiniCreator):
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.objNodeTypeCategory(),
hou.sopNodeTypeCategory()
]

View file

@ -119,7 +119,8 @@ class ImageLoader(load.LoaderPlugin):
if not parent.children():
parent.destroy()
def _get_file_sequence(self, root):
def _get_file_sequence(self, file_path):
root = os.path.dirname(file_path)
files = sorted(os.listdir(root))
first_fname = files[0]

View file

@ -21,8 +21,8 @@ class CollectArnoldROPRenderProducts(pyblish.api.InstancePlugin):
label = "Arnold ROP Render Products"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.4999
# this plugin runs after CollectFrames
order = pyblish.api.CollectorOrder + 0.11
hosts = ["houdini"]
families = ["arnold_rop"]

View file

@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
"""Collector plugin for frames data on ROP instances."""
import hou # noqa
import pyblish.api
from openpype.lib import BoolDef
from openpype.pipeline import OpenPypePyblishPluginMixin
class CollectAssetHandles(pyblish.api.InstancePlugin,
OpenPypePyblishPluginMixin):
"""Apply asset handles.
If instance does not have:
- frameStart
- frameEnd
- handleStart
- handleEnd
But it does have:
- frameStartHandle
- frameEndHandle
Then we will retrieve the asset's handles to compute
the exclusive frame range and actual handle ranges.
"""
hosts = ["houdini"]
# This specific order value is used so that
# this plugin runs after CollectAnatomyInstanceData
order = pyblish.api.CollectorOrder + 0.499
label = "Collect Asset Handles"
use_asset_handles = True
def process(self, instance):
# Only process instances without already existing handles data
# but that do have frameStartHandle and frameEndHandle defined
# like the data collected from CollectRopFrameRange
if "frameStartHandle" not in instance.data:
return
if "frameEndHandle" not in instance.data:
return
has_existing_data = {
"handleStart",
"handleEnd",
"frameStart",
"frameEnd"
}.issubset(instance.data)
if has_existing_data:
return
attr_values = self.get_attr_values_from_data(instance.data)
if attr_values.get("use_handles", self.use_asset_handles):
asset_data = instance.data["assetEntity"]["data"]
handle_start = asset_data.get("handleStart", 0)
handle_end = asset_data.get("handleEnd", 0)
else:
handle_start = 0
handle_end = 0
frame_start = instance.data["frameStartHandle"] + handle_start
frame_end = instance.data["frameEndHandle"] - handle_end
instance.data.update({
"handleStart": handle_start,
"handleEnd": handle_end,
"frameStart": frame_start,
"frameEnd": frame_end
})
# Log debug message about the collected frame range
if attr_values.get("use_handles", self.use_asset_handles):
self.log.debug(
"Full Frame range with Handles "
"[{frame_start_handle} - {frame_end_handle}]"
.format(
frame_start_handle=instance.data["frameStartHandle"],
frame_end_handle=instance.data["frameEndHandle"]
)
)
else:
self.log.debug(
"Use handles is deactivated for this instance, "
"start and end handles are set to 0."
)
# Log collected frame range to the user
message = "Frame range [{frame_start} - {frame_end}]".format(
frame_start=frame_start,
frame_end=frame_end
)
if handle_start or handle_end:
message += " with handles [{handle_start}]-[{handle_end}]".format(
handle_start=handle_start,
handle_end=handle_end
)
self.log.info(message)
if instance.data.get("byFrameStep", 1.0) != 1.0:
self.log.info(
"Frame steps {}".format(instance.data["byFrameStep"]))
# Add frame range to label if the instance has a frame range.
label = instance.data.get("label", instance.data["name"])
instance.data["label"] = (
"{label} [{frame_start_handle} - {frame_end_handle}]"
.format(
label=label,
frame_start_handle=instance.data["frameStartHandle"],
frame_end_handle=instance.data["frameEndHandle"]
)
)
@classmethod
def get_attribute_defs(cls):
return [
BoolDef("use_handles",
tooltip="Disable this if you want the publisher to"
" ignore start and end handles specified in the"
" asset data for this publish instance",
default=cls.use_asset_handles,
label="Use asset handles")
]

View file

@ -11,7 +11,9 @@ from openpype.hosts.houdini.api import lib
class CollectFrames(pyblish.api.InstancePlugin):
"""Collect all frames which would be saved from the ROP nodes"""
order = pyblish.api.CollectorOrder + 0.01
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.1
label = "Collect Frames"
families = ["vdbcache", "imagesequence", "ass",
"redshiftproxy", "review", "bgeo"]
@ -20,8 +22,8 @@ class CollectFrames(pyblish.api.InstancePlugin):
ropnode = hou.node(instance.data["instance_node"])
start_frame = instance.data.get("frameStart", None)
end_frame = instance.data.get("frameEnd", None)
start_frame = instance.data.get("frameStartHandle", None)
end_frame = instance.data.get("frameEndHandle", None)
output_parm = lib.get_output_parameter(ropnode)
if start_frame is not None:

View file

@ -122,10 +122,6 @@ class CollectInstancesUsdLayered(pyblish.api.ContextPlugin):
instance.data.update(save_data)
instance.data["usdLayer"] = layer
# Don't allow the Pyblish `instanceToggled` we have installed
# to set this node to bypass.
instance.data["_allowToggleBypass"] = False
instances.append(instance)
# Store the collected ROP node dependencies

View file

@ -25,8 +25,8 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
label = "Karma ROP Render Products"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.4999
# this plugin runs after CollectFrames
order = pyblish.api.CollectorOrder + 0.11
hosts = ["houdini"]
families = ["karma_rop"]

View file

@ -25,8 +25,8 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
label = "Mantra ROP Render Products"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.4999
# this plugin runs after CollectFrames
order = pyblish.api.CollectorOrder + 0.11
hosts = ["houdini"]
families = ["mantra_rop"]

View file

@ -25,8 +25,8 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
label = "Redshift ROP Render Products"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.4999
# this plugin runs after CollectFrames
order = pyblish.api.CollectorOrder + 0.11
hosts = ["houdini"]
families = ["redshift_rop"]

View file

@ -6,6 +6,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
"""Collect Review Data."""
label = "Collect Review Data"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.1
hosts = ["houdini"]
families = ["review"]
@ -41,8 +43,8 @@ class CollectHoudiniReviewData(pyblish.api.InstancePlugin):
return
if focal_length_parm.isTimeDependent():
start = instance.data["frameStart"]
end = instance.data["frameEnd"] + 1
start = instance.data["frameStartHandle"]
end = instance.data["frameEndHandle"] + 1
focal_length = [
focal_length_parm.evalAsFloatAtFrame(t)
for t in range(int(start), int(end))

View file

@ -2,22 +2,15 @@
"""Collector plugin for frames data on ROP instances."""
import hou # noqa
import pyblish.api
from openpype.lib import BoolDef
from openpype.hosts.houdini.api import lib
from openpype.pipeline import OpenPypePyblishPluginMixin
class CollectRopFrameRange(pyblish.api.InstancePlugin,
OpenPypePyblishPluginMixin):
class CollectRopFrameRange(pyblish.api.InstancePlugin):
"""Collect all frames which would be saved from the ROP nodes"""
hosts = ["houdini"]
# This specific order value is used so that
# this plugin runs after CollectAnatomyInstanceData
order = pyblish.api.CollectorOrder + 0.499
order = pyblish.api.CollectorOrder
label = "Collect RopNode Frame Range"
use_asset_handles = True
def process(self, instance):
@ -30,78 +23,16 @@ class CollectRopFrameRange(pyblish.api.InstancePlugin,
return
ropnode = hou.node(node_path)
attr_values = self.get_attr_values_from_data(instance.data)
if attr_values.get("use_handles", self.use_asset_handles):
asset_data = instance.data["assetEntity"]["data"]
handle_start = asset_data.get("handleStart", 0)
handle_end = asset_data.get("handleEnd", 0)
else:
handle_start = 0
handle_end = 0
frame_data = lib.get_frame_data(
ropnode, handle_start, handle_end, self.log
ropnode, self.log
)
if not frame_data:
return
# Log debug message about the collected frame range
frame_start = frame_data["frameStart"]
frame_end = frame_data["frameEnd"]
if attr_values.get("use_handles", self.use_asset_handles):
self.log.debug(
"Full Frame range with Handles "
"[{frame_start_handle} - {frame_end_handle}]"
.format(
frame_start_handle=frame_data["frameStartHandle"],
frame_end_handle=frame_data["frameEndHandle"]
)
)
else:
self.log.debug(
"Use handles is deactivated for this instance, "
"start and end handles are set to 0."
)
# Log collected frame range to the user
message = "Frame range [{frame_start} - {frame_end}]".format(
frame_start=frame_start,
frame_end=frame_end
self.log.debug(
"Collected frame_data: {}".format(frame_data)
)
if handle_start or handle_end:
message += " with handles [{handle_start}]-[{handle_end}]".format(
handle_start=handle_start,
handle_end=handle_end
)
self.log.info(message)
if frame_data.get("byFrameStep", 1.0) != 1.0:
self.log.info("Frame steps {}".format(frame_data["byFrameStep"]))
instance.data.update(frame_data)
# Add frame range to label if the instance has a frame range.
label = instance.data.get("label", instance.data["name"])
instance.data["label"] = (
"{label} [{frame_start} - {frame_end}]"
.format(
label=label,
frame_start=frame_start,
frame_end=frame_end
)
)
@classmethod
def get_attribute_defs(cls):
return [
BoolDef("use_handles",
tooltip="Disable this if you want the publisher to"
" ignore start and end handles specified in the"
" asset data for this publish instance",
default=cls.use_asset_handles,
label="Use asset handles")
]

View file

@ -25,8 +25,8 @@ class CollectVrayROPRenderProducts(pyblish.api.InstancePlugin):
label = "VRay ROP Render Products"
# This specific order value is used so that
# this plugin runs after CollectRopFrameRange
order = pyblish.api.CollectorOrder + 0.4999
# this plugin runs after CollectFrames
order = pyblish.api.CollectorOrder + 0.11
hosts = ["houdini"]
families = ["vray_rop"]

View file

@ -56,7 +56,7 @@ class ExtractAss(publish.Extractor):
'ext': ext,
"files": files,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"],
}
instance.data["representations"].append(representation)

View file

@ -47,7 +47,7 @@ class ExtractBGEO(publish.Extractor):
"ext": ext.lstrip("."),
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"]
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"]
}
instance.data["representations"].append(representation)

View file

@ -41,8 +41,8 @@ class ExtractComposite(publish.Extractor):
"ext": ext,
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"],
}
from pprint import pformat

View file

@ -40,9 +40,9 @@ class ExtractFBX(publish.Extractor):
}
# A single frame may also be rendered without start/end frame.
if "frameStart" in instance.data and "frameEnd" in instance.data:
representation["frameStart"] = instance.data["frameStart"]
representation["frameEnd"] = instance.data["frameEnd"]
if "frameStartHandle" in instance.data and "frameEndHandle" in instance.data: # noqa
representation["frameStart"] = instance.data["frameStartHandle"]
representation["frameEnd"] = instance.data["frameEndHandle"]
# set value type for 'representations' key to list
if "representations" not in instance.data:

View file

@ -39,8 +39,8 @@ class ExtractOpenGL(publish.Extractor):
"ext": instance.data["imageFormat"],
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"],
"tags": tags,
"preview": True,
"camera_name": instance.data.get("review_camera")

View file

@ -44,8 +44,8 @@ class ExtractRedshiftProxy(publish.Extractor):
}
# A single frame may also be rendered without start/end frame.
if "frameStart" in instance.data and "frameEnd" in instance.data:
representation["frameStart"] = instance.data["frameStart"]
representation["frameEnd"] = instance.data["frameEnd"]
if "frameStartHandle" in instance.data and "frameEndHandle" in instance.data: # noqa
representation["frameStart"] = instance.data["frameStartHandle"]
representation["frameEnd"] = instance.data["frameEndHandle"]
instance.data["representations"].append(representation)

View file

@ -40,7 +40,7 @@ class ExtractVDBCache(publish.Extractor):
"ext": "vdb",
"files": output,
"stagingDir": staging_dir,
"frameStart": instance.data["frameStart"],
"frameEnd": instance.data["frameEnd"],
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"],
}
instance.data["representations"].append(representation)

View file

@ -57,7 +57,17 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
return
rop_node = hou.node(instance.data["instance_node"])
if instance.data["frameStart"] > instance.data["frameEnd"]:
frame_start = instance.data.get("frameStart")
frame_end = instance.data.get("frameEnd")
if frame_start is None or frame_end is None:
cls.log.debug(
"Skipping frame range validation for "
"instance without frame data: {}".format(rop_node.path())
)
return
if frame_start > frame_end:
cls.log.info(
"The ROP node render range is set to "
"{0[frameStartHandle]} - {0[frameEndHandle]} "
@ -89,7 +99,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin):
.format(instance))
return
created_instance.publish_attributes["CollectRopFrameRange"]["use_handles"] = False # noqa
created_instance.publish_attributes["CollectAssetHandles"]["use_handles"] = False # noqa
create_context.save_changes()
cls.log.debug("use asset handles is turned off for '{}'"

View file

@ -4,7 +4,7 @@
<subMenu id="openpype_menu">
<labelExpression><![CDATA[
import os
return os.environ.get("AVALON_LABEL") or "OpenPype"
return os.environ.get("AVALON_LABEL") or "AYON"
]]></labelExpression>
<actionItem id="asset_name">
<labelExpression><![CDATA[
@ -16,7 +16,7 @@ return label
<separatorItem/>
<scriptItem id="openpype_create">
<scriptItem id="ayon_create">
<label>Create...</label>
<scriptCode><![CDATA[
import hou
@ -26,7 +26,7 @@ host_tools.show_publisher(parent, tab="create")
]]></scriptCode>
</scriptItem>
<scriptItem id="openpype_load">
<scriptItem id="ayon_load">
<label>Load...</label>
<scriptCode><![CDATA[
import hou
@ -46,7 +46,7 @@ host_tools.show_publisher(parent, tab="publish")
]]></scriptCode>
</scriptItem>
<scriptItem id="openpype_manage">
<scriptItem id="ayon_manage">
<label>Manage...</label>
<scriptCode><![CDATA[
import hou

View file

@ -4,11 +4,9 @@ import os
import pyblish.api
from pymxs import runtime as rt
from openpype.pipeline import get_current_asset_name
from openpype.hosts.max.api import colorspace
from openpype.hosts.max.api.lib import get_max_version, get_current_renderer
from openpype.hosts.max.api.lib_renderproducts import RenderProducts
from openpype.client import get_last_version_by_subset_name
class CollectRender(pyblish.api.InstancePlugin):
@ -27,7 +25,6 @@ class CollectRender(pyblish.api.InstancePlugin):
filepath = current_file.replace("\\", "/")
context.data['currentFile'] = current_file
asset = get_current_asset_name()
files_by_aov = RenderProducts().get_beauty(instance.name)
aovs = RenderProducts().get_aovs(instance.name)
@ -49,19 +46,6 @@ class CollectRender(pyblish.api.InstancePlugin):
instance.data["files"].append(files_by_aov)
img_format = RenderProducts().image_format()
project_name = context.data["projectName"]
asset_doc = context.data["assetEntity"]
asset_id = asset_doc["_id"]
version_doc = get_last_version_by_subset_name(project_name,
instance.name,
asset_id)
self.log.debug("version_doc: {0}".format(version_doc))
version_int = 1
if version_doc:
version_int += int(version_doc["name"])
self.log.debug(f"Setting {version_int} to context.")
context.data["version"] = version_int
# OCIO config not support in
# most of the 3dsmax renderers
# so this is currently hard coded
@ -87,7 +71,7 @@ class CollectRender(pyblish.api.InstancePlugin):
renderer = str(renderer_class).split(":")[0]
# also need to get the render dir for conversion
data = {
"asset": asset,
"asset": instance.data["asset"],
"subset": str(instance.name),
"publish": True,
"maxversion": str(get_max_version()),
@ -99,7 +83,6 @@ class CollectRender(pyblish.api.InstancePlugin):
"plugin": "3dsmax",
"frameStart": instance.data["frameStartHandle"],
"frameEnd": instance.data["frameEndHandle"],
"version": version_int,
"farm": True
}
instance.data.update(data)

View file

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
"""Validator for Attributes."""
from pyblish.api import ContextPlugin, ValidatorOrder
from pymxs import runtime as rt
from openpype.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError,
RepairContextAction
)
def has_property(object_name, property_name):
"""Return whether an object has a property with given name"""
return rt.Execute(f'isProperty {object_name} "{property_name}"')
def is_matching_value(object_name, property_name, value):
"""Return whether an existing property matches value `value"""
property_value = rt.Execute(f"{object_name}.{property_name}")
# Wrap property value if value is a string valued attributes
# starting with a `#`
if (
isinstance(value, str) and
value.startswith("#") and
not value.endswith(")")
):
# prefix value with `#`
# not applicable for #() array value type
# and only applicable for enum i.e. #bob, #sally
property_value = f"#{property_value}"
return property_value == value
class ValidateAttributes(OptionalPyblishPluginMixin,
ContextPlugin):
"""Validates attributes in the project setting are consistent
with the nodes from MaxWrapper Class in 3ds max.
E.g. "renderers.current.separateAovFiles",
"renderers.production.PrimaryGIEngine"
Admin(s) need to put the dict below and enable this validator for a check:
{
"renderers.current":{
"separateAovFiles" : True
},
"renderers.production":{
"PrimaryGIEngine": "#RS_GIENGINE_BRUTE_FORCE"
}
....
}
"""
order = ValidatorOrder
hosts = ["max"]
label = "Attributes"
actions = [RepairContextAction]
optional = True
@classmethod
def get_invalid(cls, context):
attributes = (
context.data["project_settings"]["max"]["publish"]
["ValidateAttributes"]["attributes"]
)
if not attributes:
return
invalid = []
for object_name, required_properties in attributes.items():
if not rt.Execute(f"isValidValue {object_name}"):
# Skip checking if the node does not
# exist in MaxWrapper Class
cls.log.debug(f"Unable to find '{object_name}'."
" Skipping validation of attributes.")
continue
for property_name, value in required_properties.items():
if not has_property(object_name, property_name):
cls.log.error(
"Non-existing property: "
f"{object_name}.{property_name}")
invalid.append((object_name, property_name))
if not is_matching_value(object_name, property_name, value):
cls.log.error(
f"Invalid value for: {object_name}.{property_name}"
f" should be: {value}")
invalid.append((object_name, property_name))
return invalid
def process(self, context):
if not self.is_active(context.data):
self.log.debug("Skipping Validate Attributes...")
return
invalid_attributes = self.get_invalid(context)
if invalid_attributes:
bullet_point_invalid_statement = "\n".join(
"- {}".format(invalid) for invalid
in invalid_attributes
)
report = (
"Required Attribute(s) have invalid value(s).\n\n"
f"{bullet_point_invalid_statement}\n\n"
"You can use repair action to fix them if they are not\n"
"unknown property value(s)."
)
raise PublishValidationError(
report, title="Invalid Value(s) for Required Attribute(s)")
@classmethod
def repair(cls, context):
attributes = (
context.data["project_settings"]["max"]["publish"]
["ValidateAttributes"]["attributes"]
)
invalid_attributes = cls.get_invalid(context)
for attrs in invalid_attributes:
prop, attr = attrs
value = attributes[prop][attr]
if isinstance(value, str) and not value.startswith("#"):
attribute_fix = '{}.{}="{}"'.format(
prop, attr, value
)
else:
attribute_fix = "{}.{}={}".format(
prop, attr, value
)
rt.Execute(attribute_fix)

View file

@ -1,7 +1,5 @@
# -*- coding: utf-8 -*-
"""Class for handling Render Settings."""
from maya import cmds # noqa
import maya.mel as mel
import six
import sys
@ -63,6 +61,10 @@ class RenderSettings(object):
def set_default_renderer_settings(self, renderer=None):
"""Set basic settings based on renderer."""
# Not all hosts can import this module.
from maya import cmds
import maya.mel as mel
if not renderer:
renderer = cmds.getAttr(
'defaultRenderGlobals.currentRenderer').lower()

View file

@ -771,7 +771,8 @@ class ReferenceLoader(Loader):
"ma": "mayaAscii",
"mb": "mayaBinary",
"abc": "Alembic",
"fbx": "FBX"
"fbx": "FBX",
"usd": "USD Import"
}.get(representation["name"])
assert file_type, "Unsupported representation: %s" % representation

View file

@ -1,7 +1,9 @@
import os
import difflib
import contextlib
from maya import cmds
import qargparse
from openpype.settings import get_project_settings
import openpype.hosts.maya.api.plugin
@ -128,6 +130,12 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
if not attach_to_root:
group_name = namespace
kwargs = {}
if "file_options" in options:
kwargs["options"] = options["file_options"]
if "file_type" in options:
kwargs["type"] = options["file_type"]
path = self.filepath_from_context(context)
with maintained_selection():
cmds.loadPlugin("AbcImport.mll", quiet=True)
@ -139,7 +147,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
reference=True,
returnNewNodes=True,
groupReference=attach_to_root,
groupName=group_name)
groupName=group_name,
**kwargs)
shapes = cmds.ls(nodes, shapes=True, long=True)
@ -251,3 +260,92 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
else:
self.log.warning("This version of Maya does not support locking of"
" transforms of cameras.")
class MayaUSDReferenceLoader(ReferenceLoader):
"""Reference USD file to native Maya nodes using MayaUSDImport reference"""
families = ["usd"]
representations = ["usd"]
extensions = {"usd", "usda", "usdc"}
options = ReferenceLoader.options + [
qargparse.Boolean(
"readAnimData",
label="Load anim data",
default=True,
help="Load animation data from USD file"
),
qargparse.Boolean(
"useAsAnimationCache",
label="Use as animation cache",
default=True,
help=(
"Imports geometry prims with time-sampled point data using a "
"point-based deformer that references the imported "
"USD file.\n"
"This provides better import and playback performance when "
"importing time-sampled geometry from USD, and should "
"reduce the weight of the resulting Maya scene."
)
),
qargparse.Boolean(
"importInstances",
label="Import instances",
default=True,
help=(
"Import USD instanced geometries as Maya instanced shapes. "
"Will flatten the scene otherwise."
)
),
qargparse.String(
"primPath",
label="Prim Path",
default="/",
help=(
"Name of the USD scope where traversing will begin.\n"
"The prim at the specified primPath (including the prim) will "
"be imported.\n"
"Specifying the pseudo-root (/) means you want "
"to import everything in the file.\n"
"If the passed prim path is empty, it will first try to "
"import the defaultPrim for the rootLayer if it exists.\n"
"Otherwise, it will behave as if the pseudo-root was passed "
"in."
)
)
]
file_type = "USD Import"
def process_reference(self, context, name, namespace, options):
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
def bool_option(key, default):
# Shorthand for getting optional boolean file option from options
value = int(bool(options.get(key, default)))
return "{}={}".format(key, value)
def string_option(key, default):
# Shorthand for getting optional string file option from options
value = str(options.get(key, default))
return "{}={}".format(key, value)
options["file_options"] = ";".join([
string_option("primPath", default="/"),
bool_option("importInstances", default=True),
bool_option("useAsAnimationCache", default=True),
bool_option("readAnimData", default=True),
# TODO: Expose more parameters
# "preferredMaterial=none",
# "importRelativeTextures=Automatic",
# "useCustomFrameRange=0",
# "startTime=0",
# "endTime=0",
# "importUSDZTextures=0"
])
options["file_type"] = self.file_type
return super(MayaUSDReferenceLoader, self).process_reference(
context, name, namespace, options
)

View file

@ -129,9 +129,6 @@ class NukeHost(
register_event_callback("workio.open_file", check_inventory_versions)
register_event_callback("taskChanged", change_context_label)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled)
_install_menu()
# add script menu
@ -402,25 +399,6 @@ def add_shortcuts_from_presets():
log.error(e)
def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle node passthrough states on instance toggles."""
log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
instance, old_value, new_value))
# Whether instances should be passthrough based on new value
with viewer_update_and_undo_stop():
n = instance[0]
try:
n["publish"].value()
except ValueError:
n = add_publish_knob(n)
log.info(" `Publish` knob was added to write node..")
n["publish"].setValue(new_value)
def containerise(node,
name,
namespace,
@ -478,8 +456,6 @@ def parse_container(node):
"""
data = read_avalon_data(node)
# (TODO) Remove key validation when `ls` has re-implemented.
#
# If not all required data return the empty container
required = ["schema", "id", "name",
"namespace", "loader", "representation"]
@ -487,7 +463,10 @@ def parse_container(node):
return
# Store the node's name
data["objectName"] = node["name"].value()
data.update({
"objectName": node.fullName(),
"node": node,
})
return data

View file

@ -537,6 +537,7 @@ class NukeLoader(LoaderPlugin):
node.addKnob(knob)
def clear_members(self, parent_node):
parent_class = parent_node.Class()
members = self.get_members(parent_node)
dependent_nodes = None
@ -549,6 +550,8 @@ class NukeLoader(LoaderPlugin):
break
for member in members:
if member.Class() == parent_class:
continue
self.log.info("removing node: `{}".format(member.name()))
nuke.delete(member)

View file

@ -64,8 +64,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
data_imprint = {
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name
"colorspaceInput": colorspace
}
for k in add_keys:
@ -194,7 +193,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
version_doc = get_version_by_id(project_name, representation["parent"])
# get corresponding node
GN = nuke.toNode(container['objectName'])
GN = container["node"]
file = get_representation_path(representation).replace("\\", "/")
@ -207,10 +206,11 @@ class LoadBackdropNodes(load.LoaderPlugin):
add_keys = ["source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"version": vname,
"colorspaceInput": colorspace,
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -252,6 +252,6 @@ class LoadBackdropNodes(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -48,10 +48,11 @@ class AlembicCameraLoader(load.LoaderPlugin):
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname,
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -111,7 +112,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
project_name = get_current_project_name()
version_doc = get_version_by_id(project_name, representation["parent"])
object_name = container['objectName']
object_name = container["node"]
# get main variables
version_data = version_doc.get("data", {})
@ -124,11 +125,12 @@ class AlembicCameraLoader(load.LoaderPlugin):
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -194,6 +196,6 @@ class AlembicCameraLoader(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -189,8 +189,6 @@ class LoadClip(plugin.NukeLoader):
value_ = value_.replace("\\", "/")
data_imprint[key] = value_
data_imprint["objectName"] = read_name
if add_retime and version_data.get("retime", None):
data_imprint["addRetime"] = True
@ -254,7 +252,7 @@ class LoadClip(plugin.NukeLoader):
is_sequence = len(representation["files"]) > 1
read_node = nuke.toNode(container['objectName'])
read_node = container["node"]
if is_sequence:
representation = self._representation_with_hash_in_frame(
@ -299,9 +297,6 @@ class LoadClip(plugin.NukeLoader):
"Representation id `{}` is failing to load".format(repre_id))
return
read_name = self._get_node_name(representation)
read_node["name"].setValue(read_name)
read_node["file"].setValue(filepath)
# to avoid multiple undo steps for rest of process
@ -356,7 +351,7 @@ class LoadClip(plugin.NukeLoader):
self.set_as_member(read_node)
def remove(self, container):
read_node = nuke.toNode(container['objectName'])
read_node = container["node"]
assert read_node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():

View file

@ -62,11 +62,12 @@ class LoadEffects(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -159,7 +160,7 @@ class LoadEffects(load.LoaderPlugin):
version_doc = get_version_by_id(project_name, representation["parent"])
# get corresponding node
GN = nuke.toNode(container['objectName'])
GN = container["node"]
file = get_representation_path(representation).replace("\\", "/")
name = container['name']
@ -175,12 +176,13 @@ class LoadEffects(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -212,7 +214,7 @@ class LoadEffects(load.LoaderPlugin):
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
for _, 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:
@ -346,6 +348,6 @@ class LoadEffects(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -63,11 +63,12 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -98,7 +99,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
for _, 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:
@ -164,28 +165,26 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
version_doc = get_version_by_id(project_name, representation["parent"])
# get corresponding node
GN = nuke.toNode(container['objectName'])
GN = container["node"]
file = get_representation_path(representation).replace("\\", "/")
name = container['name']
version_data = version_doc.get("data", {})
vname = version_doc.get("name", None)
first = version_data.get("frameStart", None)
last = version_data.get("frameEnd", None)
workfile_first_frame = int(nuke.root()["first_frame"].getValue())
namespace = container['namespace']
colorspace = version_data.get("colorspace", None)
object_name = "{}_{}".format(name, namespace)
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -217,7 +216,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
pre_node = nuke.createNode("Input")
pre_node["name"].setValue("rgb")
for ef_name, ef_val in nodes_order.items():
for _, 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:
@ -251,11 +250,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
output = nuke.createNode("Output")
output.setInput(0, pre_node)
# # try to place it under Viewer1
# if not self.connect_active_viewer(GN):
# nuke.delete(GN)
# return
# get all versions in list
last_version_doc = get_last_version_by_subset_id(
project_name, version_doc["parent"], fields=["_id"]
@ -365,6 +359,6 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -64,11 +64,12 @@ class LoadGizmo(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -111,7 +112,7 @@ class LoadGizmo(load.LoaderPlugin):
version_doc = get_version_by_id(project_name, representation["parent"])
# get corresponding node
group_node = nuke.toNode(container['objectName'])
group_node = container["node"]
file = get_representation_path(representation).replace("\\", "/")
name = container['name']
@ -126,12 +127,13 @@ class LoadGizmo(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -175,6 +177,6 @@ class LoadGizmo(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -66,11 +66,12 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -118,7 +119,7 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
version_doc = get_version_by_id(project_name, representation["parent"])
# get corresponding node
group_node = nuke.toNode(container['objectName'])
group_node = container["node"]
file = get_representation_path(representation).replace("\\", "/")
name = container['name']
@ -133,12 +134,13 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
add_keys = ["frameStart", "frameEnd", "handleStart", "handleEnd",
"source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"colorspaceInput": colorspace
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -256,6 +258,6 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
self.update(container, representation)
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -146,8 +146,6 @@ class LoadImage(load.LoaderPlugin):
data_imprint.update(
{k: context["version"]['data'].get(k, str(None))})
data_imprint.update({"objectName": read_name})
r["tile_color"].setValue(int("0x4ecd25ff", 16))
return containerise(r,
@ -168,7 +166,7 @@ class LoadImage(load.LoaderPlugin):
inputs:
"""
node = nuke.toNode(container["objectName"])
node = container["node"]
frame_number = node["first"].value()
assert node.Class() == "Read", "Must be Read"
@ -237,7 +235,7 @@ class LoadImage(load.LoaderPlugin):
self.log.info("updated to version: {}".format(version_doc.get("name")))
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
assert node.Class() == "Read", "Must be Read"
with viewer_update_and_undo_stop():

View file

@ -46,10 +46,11 @@ class AlembicModelLoader(load.LoaderPlugin):
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
data_imprint = {
"frameStart": first,
"frameEnd": last,
"version": vname
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -114,9 +115,9 @@ class AlembicModelLoader(load.LoaderPlugin):
# Get version from io
project_name = get_current_project_name()
version_doc = get_version_by_id(project_name, representation["parent"])
object_name = container['objectName']
# get corresponding node
model_node = nuke.toNode(object_name)
model_node = container["node"]
# get main variables
version_data = version_doc.get("data", {})
@ -129,11 +130,12 @@ class AlembicModelLoader(load.LoaderPlugin):
# add additional metadata from the version to imprint to Avalon knob
add_keys = ["source", "author", "fps"]
data_imprint = {"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname,
"objectName": object_name}
data_imprint = {
"representation": str(representation["_id"]),
"frameStart": first,
"frameEnd": last,
"version": vname
}
for k in add_keys:
data_imprint.update({k: version_data[k]})
@ -142,7 +144,6 @@ class AlembicModelLoader(load.LoaderPlugin):
file = get_representation_path(representation).replace("\\", "/")
with maintained_selection():
model_node = nuke.toNode(object_name)
model_node['selected'].setValue(True)
# collect input output dependencies
@ -163,8 +164,10 @@ class AlembicModelLoader(load.LoaderPlugin):
ypos = model_node.ypos()
nuke.nodeCopy("%clipboard%")
nuke.delete(model_node)
# paste the node back and set the position
nuke.nodePaste("%clipboard%")
model_node = nuke.toNode(object_name)
model_node = nuke.selectedNode()
model_node.setXYpos(xpos, ypos)
# link to original input nodes

View file

@ -55,7 +55,7 @@ class LoadOcioLookNodes(load.LoaderPlugin):
"""
namespace = namespace or context['asset']['name']
suffix = secrets.token_hex(nbytes=4)
object_name = "{}_{}_{}".format(
node_name = "{}_{}_{}".format(
name, namespace, suffix)
# getting file path
@ -64,7 +64,9 @@ class LoadOcioLookNodes(load.LoaderPlugin):
json_f = self._load_json_data(filepath)
group_node = self._create_group_node(
object_name, filepath, json_f["data"])
filepath, json_f["data"])
# renaming group node
group_node["name"].setValue(node_name)
self._node_version_color(context["version"], group_node)
@ -76,17 +78,14 @@ class LoadOcioLookNodes(load.LoaderPlugin):
name=name,
namespace=namespace,
context=context,
loader=self.__class__.__name__,
data={
"objectName": object_name,
}
loader=self.__class__.__name__
)
def _create_group_node(
self,
object_name,
filepath,
data
data,
group_node=None
):
"""Creates group node with all the nodes inside.
@ -94,9 +93,9 @@ class LoadOcioLookNodes(load.LoaderPlugin):
in between - in case those are needed.
Arguments:
object_name (str): name of the group node
filepath (str): path to json file
data (dict): data from json file
group_node (Optional[nuke.Node]): group node or None
Returns:
nuke.Node: group node with all the nodes inside
@ -117,7 +116,6 @@ class LoadOcioLookNodes(load.LoaderPlugin):
input_node = None
output_node = None
group_node = nuke.toNode(object_name)
if group_node:
# remove all nodes between Input and Output nodes
for node in group_node.nodes():
@ -130,7 +128,6 @@ class LoadOcioLookNodes(load.LoaderPlugin):
else:
group_node = nuke.createNode(
"Group",
"name {}_1".format(object_name),
inpanel=False
)
@ -227,16 +224,16 @@ class LoadOcioLookNodes(load.LoaderPlugin):
project_name = get_current_project_name()
version_doc = get_version_by_id(project_name, representation["parent"])
object_name = container['objectName']
group_node = container["node"]
filepath = get_representation_path(representation)
json_f = self._load_json_data(filepath)
group_node = self._create_group_node(
object_name,
filepath,
json_f["data"]
json_f["data"],
group_node
)
self._node_version_color(version_doc, group_node)

View file

@ -46,8 +46,6 @@ class LinkAsGroup(load.LoaderPlugin):
file = self.filepath_from_context(context).replace("\\", "/")
self.log.info("file: {}\n".format(file))
precomp_name = context["representation"]["context"]["subset"]
self.log.info("versionData: {}\n".format(context["version"]["data"]))
# add additional metadata from the version to imprint to Avalon knob
@ -62,7 +60,6 @@ class LinkAsGroup(load.LoaderPlugin):
}
for k in add_keys:
data_imprint.update({k: context["version"]['data'][k]})
data_imprint.update({"objectName": precomp_name})
# group context is set to precomp, so back up one level.
nuke.endGroup()
@ -118,7 +115,7 @@ class LinkAsGroup(load.LoaderPlugin):
inputs:
"""
node = nuke.toNode(container['objectName'])
node = container["node"]
root = get_representation_path(representation).replace("\\", "/")
@ -159,6 +156,6 @@ class LinkAsGroup(load.LoaderPlugin):
self.log.info("updated to version: {}".format(version_doc.get("name")))
def remove(self, container):
node = nuke.toNode(container['objectName'])
node = container["node"]
with viewer_update_and_undo_stop():
nuke.delete(node)

View file

@ -48,11 +48,6 @@ class PhotoshopHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
pyblish.api.register_plugin_path(PUBLISH_PATH)
register_loader_plugin_path(LOAD_PATH)
register_creator_plugin_path(CREATE_PATH)
log.info(PUBLISH_PATH)
pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)
register_event_callback("application.launched", on_application_launch)
@ -177,11 +172,6 @@ def on_application_launch():
check_inventory()
def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle layer visibility on instance toggles."""
instance[0].Visible = new_value
def ls():
"""Yields containers from active Photoshop document

View file

@ -1,4 +1,4 @@
Updated as of 9 May 2022
Updated as of 26 May 2023
----------------------------
In this package, you will find a brief introduction to the Scripting API for DaVinci Resolve Studio. Apart from this README.txt file, this package contains folders containing the basic import
modules for scripting access (DaVinciResolve.py) and some representative examples.
@ -19,7 +19,7 @@ DaVinci Resolve scripting requires one of the following to be installed (for all
Lua 5.1
Python 2.7 64-bit
Python 3.6 64-bit
Python >= 3.6 64-bit
Using a script
@ -171,6 +171,10 @@ Project
GetRenderResolutions(format, codec) --> [{Resolution}] # Returns list of resolutions applicable for the given render format (string) and render codec (string). Returns full list of resolutions if no argument is provided. Each element in the list is a dictionary with 2 keys "Width" and "Height".
RefreshLUTList() --> Bool # Refreshes LUT List
GetUniqueId() --> string # Returns a unique ID for the project item
InsertAudioToCurrentTrackAtPlayhead(mediaPath, --> Bool # Inserts the media specified by mediaPath (string) with startOffsetInSamples (int) and durationInSamples (int) at the playhead on a selected track on the Fairlight page. Returns True if successful, otherwise False.
startOffsetInSamples, durationInSamples)
LoadBurnInPreset(presetName) --> Bool # Loads user defined data burn in preset for project when supplied presetName (string). Returns true if successful.
ExportCurrentFrameAsStill(filePath) --> Bool # Exports current frame as still to supplied filePath. filePath must end in valid export file format. Returns True if succssful, False otherwise.
MediaStorage
GetMountedVolumeList() --> [paths...] # Returns list of folder paths corresponding to mounted volumes displayed in Resolves Media Storage.
@ -179,6 +183,7 @@ MediaStorage
RevealInStorage(path) --> Bool # Expands and displays given file/folder path in Resolves Media Storage.
AddItemListToMediaPool(item1, item2, ...) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is one or more file/folder paths. Returns a list of the MediaPoolItems created.
AddItemListToMediaPool([items...]) --> [clips...] # Adds specified file/folder paths from Media Storage into current Media Pool folder. Input is an array of file/folder paths. Returns a list of the MediaPoolItems created.
AddItemListToMediaPool([{itemInfo}, ...]) --> [clips...] # Adds list of itemInfos specified as dict of "media", "startFrame" (int), "endFrame" (int) from Media Storage into current Media Pool folder. Returns a list of the MediaPoolItems created.
AddClipMattesToMediaPool(MediaPoolItem, [paths], stereoEye) --> Bool # Adds specified media files as mattes for the specified MediaPoolItem. StereoEye is an optional argument for specifying which eye to add the matte to for stereo clips ("left" or "right"). Returns True if successful.
AddTimelineMattesToMediaPool([paths]) --> [MediaPoolItems] # Adds specified media files as timeline mattes in current media pool folder. Returns a list of created MediaPoolItems.
@ -189,20 +194,22 @@ MediaPool
CreateEmptyTimeline(name) --> Timeline # Adds new timeline with given name.
AppendToTimeline(clip1, clip2, ...) --> [TimelineItem] # Appends specified MediaPoolItem objects in the current timeline. Returns the list of appended timelineItems.
AppendToTimeline([clips]) --> [TimelineItem] # Appends specified MediaPoolItem objects in the current timeline. Returns the list of appended timelineItems.
AppendToTimeline([{clipInfo}, ...]) --> [TimelineItem] # Appends list of clipInfos specified as dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int), (optional) "mediaType" (int; 1 - Video only, 2 - Audio only). Returns the list of appended timelineItems.
AppendToTimeline([{clipInfo}, ...]) --> [TimelineItem] # Appends list of clipInfos specified as dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int), (optional) "mediaType" (int; 1 - Video only, 2 - Audio only), "trackIndex" (int) and "recordFrame" (int). Returns the list of appended timelineItems.
CreateTimelineFromClips(name, clip1, clip2,...) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects.
CreateTimelineFromClips(name, [clips]) --> Timeline # Creates new timeline with specified name, and appends the specified MediaPoolItem objects.
CreateTimelineFromClips(name, [{clipInfo}]) --> Timeline # Creates new timeline with specified name, appending the list of clipInfos specified as a dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int).
ImportTimelineFromFile(filePath, {importOptions}) --> Timeline # Creates timeline based on parameters within given file and optional importOptions dict, with support for the keys:
# "timelineName": string, specifies the name of the timeline to be created
# "importSourceClips": Bool, specifies whether source clips should be imported, True by default
CreateTimelineFromClips(name, [{clipInfo}]) --> Timeline # Creates new timeline with specified name, appending the list of clipInfos specified as a dict of "mediaPoolItem", "startFrame" (int), "endFrame" (int), "recordFrame" (int).
ImportTimelineFromFile(filePath, {importOptions}) --> Timeline # Creates timeline based on parameters within given file (AAF/EDL/XML/FCPXML/DRT/ADL) and optional importOptions dict, with support for the keys:
# "timelineName": string, specifies the name of the timeline to be created. Not valid for DRT import
# "importSourceClips": Bool, specifies whether source clips should be imported, True by default. Not valid for DRT import
# "sourceClipsPath": string, specifies a filesystem path to search for source clips if the media is inaccessible in their original path and if "importSourceClips" is True
# "sourceClipsFolders": List of Media Pool folder objects to search for source clips if the media is not present in current folder and if "importSourceClips" is False
# "sourceClipsFolders": List of Media Pool folder objects to search for source clips if the media is not present in current folder and if "importSourceClips" is False. Not valid for DRT import
# "interlaceProcessing": Bool, specifies whether to enable interlace processing on the imported timeline being created. valid only for AAF import
DeleteTimelines([timeline]) --> Bool # Deletes specified timelines in the media pool.
GetCurrentFolder() --> Folder # Returns currently selected Folder.
SetCurrentFolder(Folder) --> Bool # Sets current folder by given Folder.
DeleteClips([clips]) --> Bool # Deletes specified clips or timeline mattes in the media pool
ImportFolderFromFile(filePath, sourceClipsPath="") --> Bool # Returns true if import from given DRB filePath is successful, false otherwise
# sourceClipsPath is a string that specifies a filesystem path to search for source clips if the media is inaccessible in their original path, empty by default
DeleteFolders([subfolders]) --> Bool # Deletes specified subfolders in the media pool
MoveClips([clips], targetFolder) --> Bool # Moves specified clips to target folder.
MoveFolders([folders], targetFolder) --> Bool # Moves specified folders to target folder.
@ -225,6 +232,7 @@ Folder
GetSubFolderList() --> [folders...] # Returns a list of subfolders in the folder.
GetIsFolderStale() --> bool # Returns true if folder is stale in collaboration mode, false otherwise
GetUniqueId() --> string # Returns a unique ID for the media pool folder
Export(filePath) --> bool # Returns true if export of DRB folder to filePath is successful, false otherwise
MediaPoolItem
GetName() --> string # Returns the clip name.
@ -257,6 +265,8 @@ MediaPoolItem
UnlinkProxyMedia() --> Bool # Unlinks any proxy media associated with clip.
ReplaceClip(filePath) --> Bool # Replaces the underlying asset and metadata of MediaPoolItem with the specified absolute clip path.
GetUniqueId() --> string # Returns a unique ID for the media pool item
TranscribeAudio() --> Bool # Transcribes audio of the MediaPoolItem. Returns True if successful; False otherwise
ClearTranscription() --> Bool # Clears audio transcription of the MediaPoolItem. Returns True if successful; False otherwise.
Timeline
GetName() --> string # Returns the timeline name.
@ -266,6 +276,23 @@ Timeline
SetStartTimecode(timecode) --> Bool # Set the start timecode of the timeline to the string 'timecode'. Returns true when the change is successful, false otherwise.
GetStartTimecode() --> string # Returns the start timecode for the timeline.
GetTrackCount(trackType) --> int # Returns the number of tracks for the given track type ("audio", "video" or "subtitle").
AddTrack(trackType, optionalSubTrackType) --> Bool # Adds track of trackType ("video", "subtitle", "audio"). Second argument optionalSubTrackType is required for "audio"
# optionalSubTrackType can be one of {"mono", "stereo", "5.1", "5.1film", "7.1", "7.1film", "adaptive1", ... , "adaptive24"}
DeleteTrack(trackType, trackIndex) --> Bool # Deletes track of trackType ("video", "subtitle", "audio") and given trackIndex. 1 <= trackIndex <= GetTrackCount(trackType).
SetTrackEnable(trackType, trackIndex, Bool) --> Bool # Enables/Disables track with given trackType and trackIndex
# trackType is one of {"audio", "video", "subtitle"}
# 1 <= trackIndex <= GetTrackCount(trackType).
GetIsTrackEnabled(trackType, trackIndex) --> Bool # Returns True if track with given trackType and trackIndex is enabled and False otherwise.
# trackType is one of {"audio", "video", "subtitle"}
# 1 <= trackIndex <= GetTrackCount(trackType).
SetTrackLock(trackType, trackIndex, Bool) --> Bool # Locks/Unlocks track with given trackType and trackIndex
# trackType is one of {"audio", "video", "subtitle"}
# 1 <= trackIndex <= GetTrackCount(trackType).
GetIsTrackLocked(trackType, trackIndex) --> Bool # Returns True if track with given trackType and trackIndex is locked and False otherwise.
# trackType is one of {"audio", "video", "subtitle"}
# 1 <= trackIndex <= GetTrackCount(trackType).
DeleteClips([timelineItems], Bool) --> Bool # Deletes specified TimelineItems from the timeline, performing ripple delete if the second argument is True. Second argument is optional (The default for this is False)
SetClipsLinked([timelineItems], Bool) --> Bool # Links or unlinks the specified TimelineItems depending on second argument.
GetItemListInTrack(trackType, index) --> [items...] # Returns a list of timeline items on that track (based on trackType and index). 1 <= index <= GetTrackCount(trackType).
AddMarker(frameId, color, name, note, duration, --> Bool # Creates a new marker at given frameId position and with given marker information. 'customData' is optional and helps to attach user specific data to the marker.
customData)
@ -301,7 +328,7 @@ Timeline
# "sourceClipsFolders": string, list of Media Pool folder objects to search for source clips if the media is not present in current folder
Export(fileName, exportType, exportSubtype) --> Bool # Exports timeline to 'fileName' as per input exportType & exportSubtype format.
# Refer to section "Looking up timeline exports properties" for information on the parameters.
# Refer to section "Looking up timeline export properties" for information on the parameters.
GetSetting(settingName) --> string # Returns value of timeline setting (indicated by settingName : string). Check the section below for more information.
SetSetting(settingName, settingValue) --> Bool # Sets timeline setting (indicated by settingName : string) to the value (settingValue : string). Check the section below for more information.
InsertGeneratorIntoTimeline(generatorName) --> TimelineItem # Inserts a generator (indicated by generatorName : string) into the timeline.
@ -313,6 +340,8 @@ Timeline
GrabStill() --> galleryStill # Grabs still from the current video clip. Returns a GalleryStill object.
GrabAllStills(stillFrameSource) --> [galleryStill] # Grabs stills from all the clips of the timeline at 'stillFrameSource' (1 - First frame, 2 - Middle frame). Returns the list of GalleryStill objects.
GetUniqueId() --> string # Returns a unique ID for the timeline
CreateSubtitlesFromAudio() --> Bool # Creates subtitles from audio for the timeline. Returns True on success, False otherwise.
DetectSceneCuts() --> Bool # Detects and makes scene cuts along the timeline. Returns True if successful, False otherwise.
TimelineItem
GetName() --> string # Returns the item name.
@ -362,6 +391,7 @@ TimelineItem
GetStereoLeftFloatingWindowParams() --> {keyframes...} # For the LEFT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values.
GetStereoRightFloatingWindowParams() --> {keyframes...} # For the RIGHT eye -> returns a dict (offset -> dict) of keyframe offsets and respective floating window params. Value at particular offset includes the left, right, top and bottom floating window values.
GetNumNodes() --> int # Returns the number of nodes in the current graph for the timeline item
ApplyArriCdlLut() --> Bool # Applies ARRI CDL and LUT. Returns True if successful, False otherwise.
SetLUT(nodeIndex, lutPath) --> Bool # Sets LUT on the node mapping the node index provided, 1 <= nodeIndex <= total number of nodes.
# The lutPath can be an absolute path, or a relative path (based off custom LUT paths or the master LUT path).
# The operation is successful for valid lut paths that Resolve has already discovered (see Project.RefreshLUTList).
@ -376,8 +406,16 @@ TimelineItem
SelectTakeByIndex(idx) --> Bool # Selects a take by index, 1 <= idx <= number of takes.
FinalizeTake() --> Bool # Finalizes take selection.
CopyGrades([tgtTimelineItems]) --> Bool # Copies the current grade to all the items in tgtTimelineItems list. Returns True on success and False if any error occurred.
SetClipEnabled(Bool) --> Bool # Sets clip enabled based on argument.
GetClipEnabled() --> Bool # Gets clip enabled status.
UpdateSidecar() --> Bool # Updates sidecar file for BRAW clips or RMD file for R3D clips.
GetUniqueId() --> string # Returns a unique ID for the timeline item
LoadBurnInPreset(presetName) --> Bool # Loads user defined data burn in preset for clip when supplied presetName (string). Returns true if successful.
GetNodeLabel(nodeIndex) --> string # Returns the label of the node at nodeIndex.
CreateMagicMask(mode) --> Bool # Returns True if magic mask was created successfully, False otherwise. mode can "F" (forward), "B" (backward), or "BI" (bidirection)
RegenerateMagicMask() --> Bool # Returns True if magic mask was regenerated successfully, False otherwise.
Stabilize() --> Bool # Returns True if stabilization was successful, False otherwise
SmartReframe() --> Bool # Performs Smart Reframe. Returns True if successful, False otherwise.
Gallery
GetAlbumName(galleryStillAlbum) --> string # Returns the name of the GalleryStillAlbum object 'galleryStillAlbum'.
@ -422,9 +460,11 @@ Invoke "Project:SetSetting", "Timeline:SetSetting" or "MediaPoolItem:SetClipProp
ensure the success of the operation. You can troubleshoot the validity of keys and values by setting the desired result from the UI and checking property snapshots before and after the change.
The following Project properties have specifically enumerated values:
"superScale" - the property value is an enumerated integer between 0 and 3 with these meanings: 0=Auto, 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x.
"superScale" - the property value is an enumerated integer between 0 and 4 with these meanings: 0=Auto, 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x.
for super scale multiplier '2x Enhanced', exactly 4 arguments must be passed as outlined below. If less than 4 arguments are passed, it will default to 2x.
Affects:
• x = Project:GetSetting('superScale') and Project:SetSetting('superScale', x)
• for '2x Enhanced' --> Project:SetSetting('superScale', 2, sharpnessValue, noiseReductionValue), where sharpnessValue is a float in the range [0.0, 1.0] and noiseReductionValue is a float in the range [0.0, 1.0]
"timelineFrameRate" - the property value is one of the frame rates available to the user in project settings under "Timeline frame rate" option. Drop Frame can be configured for supported frame rates
by appending the frame rate with "DF", e.g. "29.97 DF" will enable drop frame and "29.97" will disable drop frame
@ -432,9 +472,11 @@ Affects:
• x = Project:GetSetting('timelineFrameRate') and Project:SetSetting('timelineFrameRate', x)
The following Clip properties have specifically enumerated values:
"superScale" - the property value is an enumerated integer between 1 and 3 with these meanings: 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x.
"Super Scale" - the property value is an enumerated integer between 1 and 4 with these meanings: 1=no scaling, and 2, 3 and 4 represent the Super Scale multipliers 2x, 3x and 4x.
for super scale multiplier '2x Enhanced', exactly 4 arguments must be passed as outlined below. If less than 4 arguments are passed, it will default to 2x.
Affects:
• x = MediaPoolItem:GetClipProperty('Super Scale') and MediaPoolItem:SetClipProperty('Super Scale', x)
• for '2x Enhanced' --> MediaPoolItem:SetClipProperty('Super Scale', 2, sharpnessValue, noiseReductionValue), where sharpnessValue is a float in the range [0.0, 1.0] and noiseReductionValue is a float in the range [0.0, 1.0]
Looking up Render Settings
@ -478,11 +520,6 @@ exportType can be one of the following constants:
- resolve.EXPORT_DRT
- resolve.EXPORT_EDL
- resolve.EXPORT_FCP_7_XML
- resolve.EXPORT_FCPXML_1_3
- resolve.EXPORT_FCPXML_1_4
- resolve.EXPORT_FCPXML_1_5
- resolve.EXPORT_FCPXML_1_6
- resolve.EXPORT_FCPXML_1_7
- resolve.EXPORT_FCPXML_1_8
- resolve.EXPORT_FCPXML_1_9
- resolve.EXPORT_FCPXML_1_10
@ -492,6 +529,8 @@ exportType can be one of the following constants:
- resolve.EXPORT_TEXT_TAB
- resolve.EXPORT_DOLBY_VISION_VER_2_9
- resolve.EXPORT_DOLBY_VISION_VER_4_0
- resolve.EXPORT_DOLBY_VISION_VER_5_1
- resolve.EXPORT_OTIO
exportSubtype can be one of the following enums:
- resolve.EXPORT_NONE
- resolve.EXPORT_AAF_NEW
@ -504,6 +543,16 @@ When exportType is resolve.EXPORT_AAF, valid exportSubtype values are resolve.EX
When exportType is resolve.EXPORT_EDL, valid exportSubtype values are resolve.EXPORT_CDL, resolve.EXPORT_SDL, resolve.EXPORT_MISSING_CLIPS and resolve.EXPORT_NONE.
Note: Replace 'resolve.' when using the constants above, if a different Resolve class instance name is used.
Unsupported exportType types
---------------------------------
Starting with DaVinci Resolve 18.1, the following export types are not supported:
- resolve.EXPORT_FCPXML_1_3
- resolve.EXPORT_FCPXML_1_4
- resolve.EXPORT_FCPXML_1_5
- resolve.EXPORT_FCPXML_1_6
- resolve.EXPORT_FCPXML_1_7
Looking up Timeline item properties
-----------------------------------
This section covers additional notes for the function "TimelineItem:SetProperty" and "TimelineItem:GetProperty". These functions are used to get and set properties mentioned.

View file

@ -6,7 +6,10 @@ import contextlib
from opentimelineio import opentime
from openpype.lib import Logger
from openpype.pipeline.editorial import is_overlapping_otio_ranges
from openpype.pipeline.editorial import (
is_overlapping_otio_ranges,
frames_to_timecode
)
from ..otio import davinci_export as otio_export
@ -246,18 +249,22 @@ def get_media_pool_item(filepath, root: object = None) -> object:
return None
def create_timeline_item(media_pool_item: object,
timeline: object = None,
source_start: int = None,
source_end: int = None) -> object:
def create_timeline_item(
media_pool_item: object,
timeline: object = None,
timeline_in: int = None,
source_start: int = None,
source_end: int = None,
) -> object:
"""
Add media pool item to current or defined timeline.
Args:
media_pool_item (resolve.MediaPoolItem): resolve's object
timeline (resolve.Timeline)[optional]: resolve's object
source_start (int)[optional]: media source input frame (sequence frame)
source_end (int)[optional]: media source output frame (sequence frame)
timeline (Optional[resolve.Timeline]): resolve's object
timeline_in (Optional[int]): timeline input frame (sequence frame)
source_start (Optional[int]): media source input frame (sequence frame)
source_end (Optional[int]): media source output frame (sequence frame)
Returns:
object: resolve.TimelineItem
@ -269,16 +276,29 @@ def create_timeline_item(media_pool_item: object,
clip_name = _clip_property("File Name")
timeline = timeline or get_current_timeline()
# timing variables
if all([timeline_in, source_start, source_end]):
fps = timeline.GetSetting("timelineFrameRate")
duration = source_end - source_start
timecode_in = frames_to_timecode(timeline_in, fps)
timecode_out = frames_to_timecode(timeline_in + duration, fps)
else:
timecode_in = None
timecode_out = None
# if timeline was used then switch it to current timeline
with maintain_current_timeline(timeline):
# Add input mediaPoolItem to clip data
clip_data = {"mediaPoolItem": media_pool_item}
clip_data = {
"mediaPoolItem": media_pool_item,
}
# add source time range if input was given
if source_start is not None:
clip_data.update({"startFrame": source_start})
if source_end is not None:
clip_data.update({"endFrame": source_end})
if source_start:
clip_data["startFrame"] = source_start
if source_end:
clip_data["endFrame"] = source_end
if timecode_in:
clip_data["recordFrame"] = timecode_in
# add to timeline
media_pool.AppendToTimeline([clip_data])
@ -286,10 +306,15 @@ def create_timeline_item(media_pool_item: object,
output_timeline_item = get_timeline_item(
media_pool_item, timeline)
assert output_timeline_item, AssertionError(
"Track Item with name `{}` doesn't exist on the timeline: `{}`".format(
clip_name, timeline.GetName()
))
assert output_timeline_item, AssertionError((
"Clip name '{}' was't created on the timeline: '{}' \n\n"
"Please check if correct track position is activated, \n"
"or if a clip is not already at the timeline in \n"
"position: '{}' out: '{}'. \n\n"
"Clip data: {}"
).format(
clip_name, timeline.GetName(), timecode_in, timecode_out, clip_data
))
return output_timeline_item
@ -490,7 +515,7 @@ def imprint(timeline_item, data=None):
Arguments:
timeline_item (hiero.core.TrackItem): hiero track item object
data (dict): Any data which needst to be imprinted
data (dict): Any data which needs to be imprinted
Examples:
data = {

View file

@ -306,11 +306,18 @@ class ClipLoader:
self.active_project = lib.get_current_project()
# try to get value from options or evaluate key value for `handles`
self.with_handles = options.get("handles") or bool(
options.get("handles") is True)
self.with_handles = options.get("handles") is True
# try to get value from options or evaluate key value for `load_to`
self.new_timeline = options.get("newTimeline") or bool(
"New timeline" in options.get("load_to", ""))
self.new_timeline = (
options.get("newTimeline") or
options.get("load_to") == "New timeline"
)
# try to get value from options or evaluate key value for `load_how`
self.sequential_load = (
options.get("sequentially") or
options.get("load_how") == "Sequentially in order"
)
assert self._populate_data(), str(
"Cannot Load selected data, look into database "
@ -391,30 +398,70 @@ class ClipLoader:
# create project bin for the media to be imported into
self.active_bin = lib.create_bin(self.data["binPath"])
handle_start = self.data["versionData"].get("handleStart") or 0
handle_end = self.data["versionData"].get("handleEnd") or 0
# create mediaItem in active project bin
# create clip media
media_pool_item = lib.create_media_pool_item(
files,
self.active_bin
)
_clip_property = media_pool_item.GetClipProperty
# get handles
handle_start = self.data["versionData"].get("handleStart")
handle_end = self.data["versionData"].get("handleEnd")
if handle_start is None:
handle_start = int(self.data["assetData"]["handleStart"])
if handle_end is None:
handle_end = int(self.data["assetData"]["handleEnd"])
# check frame duration from versionData or assetData
frame_start = self.data["versionData"].get("frameStart")
if frame_start is None:
frame_start = self.data["assetData"]["frameStart"]
# check frame duration from versionData or assetData
frame_end = self.data["versionData"].get("frameEnd")
if frame_end is None:
frame_end = self.data["assetData"]["frameEnd"]
db_frame_duration = int(frame_end) - int(frame_start) + 1
# get timeline in
timeline_start = self.active_timeline.GetStartFrame()
if self.sequential_load:
# set timeline start frame
timeline_in = int(timeline_start)
else:
# set timeline start frame + original clip in frame
timeline_in = int(
timeline_start + self.data["assetData"]["clipIn"])
source_in = int(_clip_property("Start"))
source_out = int(_clip_property("End"))
source_duration = int(_clip_property("Frames"))
if _clip_property("Type") == "Video":
# check if source duration is shorter than db frame duration
source_with_handles = True
if source_duration < db_frame_duration:
source_with_handles = False
# only exclude handles if source has no handles or
# if user wants to load without handles
if (
not self.with_handles
or not source_with_handles
):
source_in += handle_start
source_out -= handle_end
# include handles
if self.with_handles:
source_in -= handle_start
source_out += handle_end
# make track item from source in bin as item
timeline_item = lib.create_timeline_item(
media_pool_item, self.active_timeline, source_in, source_out)
media_pool_item,
self.active_timeline,
timeline_in,
source_in,
source_out,
)
print("Loading clips: `{}`".format(self.data["clip_name"]))
return timeline_item
@ -455,7 +502,7 @@ class TimelineItemLoader(LoaderPlugin):
"""
options = [
qargparse.Toggle(
qargparse.Boolean(
"handles",
label="Include handles",
default=0,
@ -470,6 +517,16 @@ class TimelineItemLoader(LoaderPlugin):
],
default=0,
help="Where do you want clips to be loaded?"
),
qargparse.Choice(
"load_how",
label="How to load clips",
items=[
"Original timing",
"Sequentially in order"
],
default="Original timing",
help="Would you like to place it at original timing?"
)
]

View file

@ -21,6 +21,7 @@ from aiohttp_json_rpc.protocol import (
)
from aiohttp_json_rpc.exceptions import RpcError
from openpype import AYON_SERVER_ENABLED
from openpype.lib import emit_event
from openpype.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path
@ -834,8 +835,12 @@ class BaseCommunicator:
class QtCommunicator(BaseCommunicator):
label = os.getenv("AVALON_LABEL")
if not label:
label = "AYON" if AYON_SERVER_ENABLED else "OpenPype"
title = "{} Tools".format(label)
menu_definitions = {
"title": "OpenPype Tools",
"title": title,
"menu_items": [
{
"callback": "workfiles_tool",

View file

@ -7,7 +7,7 @@ import requests
import pyblish.api
from openpype.client import get_project, get_asset_by_name
from openpype.client import get_asset_by_name
from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
from openpype.hosts.tvpaint import TVPAINT_ROOT_DIR
from openpype.settings import get_current_project_settings
@ -84,10 +84,6 @@ class TVPaintHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
register_loader_plugin_path(load_dir)
register_creator_plugin_path(create_dir)
registered_callbacks = (
pyblish.api.registered_callbacks().get("instanceToggled") or []
)
register_event_callback("application.launched", self.initial_launch)
register_event_callback("application.exit", self.application_exit)

View file

@ -69,7 +69,6 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
"asset_name": context.data["asset"],
"task_name": context.data["task"]
}
context.data["previous_context"] = current_context
self.log.debug("Current context is: {}".format(current_context))
# Collect context from workfile metadata