Merge branch 'develop' into 3.0/poetry
|
|
@ -1,60 +0,0 @@
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
import bpy
|
||||
|
||||
from pype import PLUGINS_DIR
|
||||
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "blender", "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "blender", "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "blender", "create")
|
||||
|
||||
ORIGINAL_EXCEPTHOOK = sys.excepthook
|
||||
|
||||
|
||||
def pype_excepthook_handler(*args):
|
||||
traceback.print_exception(*args)
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Blender configuration for Avalon."""
|
||||
sys.excepthook = pype_excepthook_handler
|
||||
pyblish.register_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
avalon.on("new", on_new)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall Blender configuration for Avalon."""
|
||||
sys.excepthook = ORIGINAL_EXCEPTHOOK
|
||||
pyblish.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
|
||||
def set_start_end_frames():
|
||||
from avalon import io
|
||||
|
||||
asset_name = io.Session["AVALON_ASSET"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
|
||||
bpy.context.scene.frame_start = asset_doc["data"]["frameStart"]
|
||||
bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"]
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
def on_open(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
63
pype/hosts/blender/api/__init__.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import bpy
|
||||
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
import pype.hosts.blender
|
||||
|
||||
HOST_DIR = os.path.dirname(os.path.abspath(pype.hosts.blender.__file__))
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
ORIGINAL_EXCEPTHOOK = sys.excepthook
|
||||
|
||||
|
||||
def pype_excepthook_handler(*args):
|
||||
traceback.print_exception(*args)
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Blender configuration for Avalon."""
|
||||
sys.excepthook = pype_excepthook_handler
|
||||
pyblish.register_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
avalon.on("new", on_new)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall Blender configuration for Avalon."""
|
||||
sys.excepthook = ORIGINAL_EXCEPTHOOK
|
||||
pyblish.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
|
||||
def set_start_end_frames():
|
||||
from avalon import io
|
||||
|
||||
asset_name = io.Session["AVALON_ASSET"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
|
||||
bpy.context.scene.frame_start = asset_doc["data"]["frameStart"]
|
||||
bpy.context.scene.frame_end = asset_doc["data"]["frameEnd"]
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
def on_open(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
|
@ -2,7 +2,7 @@ import bpy
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from ...action import get_errored_instances_from_context
|
||||
from pype.api import get_errored_instances_from_context
|
||||
|
||||
|
||||
class SelectInvalidAction(pyblish.api.Action):
|
||||
0
pype/hosts/blender/plugins/create/__init__.py
Normal file
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
from avalon.blender import Creator, lib
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateAction(Creator):
|
||||
|
|
@ -19,7 +19,7 @@ class CreateAction(Creator):
|
|||
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api, blender
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateAnimation(blender.Creator):
|
||||
|
|
@ -17,7 +17,7 @@ class CreateAnimation(blender.Creator):
|
|||
def process(self):
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
from avalon.blender import Creator, lib
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateCamera(Creator):
|
||||
|
|
@ -19,7 +19,7 @@ class CreateCamera(Creator):
|
|||
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
from avalon.blender import Creator, lib
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateLayout(Creator):
|
||||
|
|
@ -19,7 +19,7 @@ class CreateLayout(Creator):
|
|||
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
from avalon.blender import Creator, lib
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateModel(Creator):
|
||||
|
|
@ -19,7 +19,7 @@ class CreateModel(Creator):
|
|||
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
from avalon.blender import Creator, lib
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
class CreateRig(Creator):
|
||||
|
|
@ -19,7 +19,7 @@ class CreateRig(Creator):
|
|||
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api, blender
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
class CreateSetDress(blender.Creator):
|
||||
"""A grouped package of loaded content"""
|
||||
|
|
@ -15,7 +15,7 @@ class CreateSetDress(blender.Creator):
|
|||
def process(self):
|
||||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
name = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
0
pype/hosts/blender/plugins/load/__init__.py
Normal file
|
|
@ -7,12 +7,12 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
logger = logging.getLogger("pype").getChild("blender").getChild("load_action")
|
||||
|
||||
|
||||
class BlendActionLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class BlendActionLoader(pype.hosts.blender.api.plugin.AssetLoader):
|
||||
"""Load action from a .blend file.
|
||||
|
||||
Warning:
|
||||
|
|
@ -42,8 +42,8 @@ class BlendActionLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
libpath = self.fname
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.plugin.asset_name(
|
||||
lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.api.plugin.asset_name(
|
||||
asset, subset, namespace
|
||||
)
|
||||
|
||||
|
|
@ -149,7 +149,7 @@ class BlendActionLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
assert libpath.is_file(), (
|
||||
f"The file doesn't exist: {libpath}"
|
||||
)
|
||||
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
|
||||
assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, (
|
||||
f"Unsupported file: {libpath}"
|
||||
)
|
||||
|
||||
|
|
@ -7,14 +7,14 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
|
||||
logger = logging.getLogger("pype").getChild(
|
||||
"blender").getChild("load_animation")
|
||||
|
||||
|
||||
class BlendAnimationLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class BlendAnimationLoader(pype.hosts.blender.api.plugin.AssetLoader):
|
||||
"""Load animations from a .blend file.
|
||||
|
||||
Warning:
|
||||
|
|
@ -105,8 +105,8 @@ class BlendAnimationLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
libpath = self.fname
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.plugin.asset_name(
|
||||
lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.api.plugin.asset_name(
|
||||
asset, subset, namespace
|
||||
)
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ class BlendAnimationLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
assert libpath.is_file(), (
|
||||
f"The file doesn't exist: {libpath}"
|
||||
)
|
||||
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
|
||||
assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, (
|
||||
f"Unsupported file: {libpath}"
|
||||
)
|
||||
|
||||
|
|
@ -7,12 +7,12 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
logger = logging.getLogger("pype").getChild("blender").getChild("load_camera")
|
||||
|
||||
|
||||
class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader):
|
||||
class BlendCameraLoader(pype.hosts.blender.api.plugin.AssetLoader):
|
||||
"""Load a camera from a .blend file.
|
||||
|
||||
Warning:
|
||||
|
|
@ -92,8 +92,8 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
libpath = self.fname
|
||||
asset = context["asset"]["name"]
|
||||
subset = context["subset"]["name"]
|
||||
lib_container = pype.hosts.blender.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.plugin.asset_name(
|
||||
lib_container = pype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
container_name = pype.hosts.blender.api.plugin.asset_name(
|
||||
asset, subset, namespace
|
||||
)
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ class BlendCameraLoader(pype.hosts.blender.plugin.AssetLoader):
|
|||
assert libpath.is_file(), (
|
||||
f"The file doesn't exist: {libpath}"
|
||||
)
|
||||
assert extension in pype.hosts.blender.plugin.VALID_EXTENSIONS, (
|
||||
assert extension in pype.hosts.blender.api.plugin.VALID_EXTENSIONS, (
|
||||
f"Unsupported file: {libpath}"
|
||||
)
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender, pipeline
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
import pype.hosts.blender.api.plugin as plugin
|
||||
|
||||
|
||||
class BlendLayoutLoader(plugin.AssetLoader):
|
||||
|
|
@ -7,7 +7,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
import pype.hosts.blender.api.plugin as plugin
|
||||
|
||||
|
||||
class BlendModelLoader(plugin.AssetLoader):
|
||||
|
|
@ -7,7 +7,7 @@ from typing import Dict, List, Optional
|
|||
|
||||
from avalon import api, blender
|
||||
import bpy
|
||||
import pype.hosts.blender.plugin as plugin
|
||||
import pype.hosts.blender.api.plugin as plugin
|
||||
|
||||
|
||||
class BlendRigLoader(plugin.AssetLoader):
|
||||
0
pype/hosts/blender/plugins/publish/__init__.py
Normal file
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
|
||||
import pype.api
|
||||
import pype.hosts.blender.plugin
|
||||
import pype.hosts.blender.api.plugin
|
||||
|
||||
import bpy
|
||||
|
||||
|
|
@ -61,7 +61,8 @@ class ExtractABC(pype.api.Extractor):
|
|||
except:
|
||||
continue
|
||||
|
||||
new_context = pype.hosts.blender.plugin.create_blender_context(active=selected[0], selected=selected)
|
||||
new_context = pype.hosts.blender.api.plugin.create_blender_context(
|
||||
active=selected[0], selected=selected)
|
||||
|
||||
# We set the scale of the scene for the export
|
||||
scene.unit_settings.scale_length = 0.01
|
||||
|
|
@ -6,6 +6,7 @@ import pyblish.api
|
|||
|
||||
import bpy
|
||||
|
||||
|
||||
class ExtractSetDress(pype.api.Extractor):
|
||||
"""Extract setdress."""
|
||||
|
||||
|
|
@ -21,17 +22,20 @@ class ExtractSetDress(pype.api.Extractor):
|
|||
json_data = []
|
||||
|
||||
for i in instance.context:
|
||||
collection = i.data.get('name')
|
||||
collection = i.data.get("name")
|
||||
container = None
|
||||
for obj in bpy.data.collections[collection].objects:
|
||||
if obj.type == 'ARMATURE':
|
||||
container_name = obj.get('avalon').get('container_name')
|
||||
if obj.type == "ARMATURE":
|
||||
container_name = obj.get("avalon").get("container_name")
|
||||
container = bpy.data.collections[container_name]
|
||||
if container:
|
||||
json_dict = {}
|
||||
json_dict['subset'] = i.data.get('subset')
|
||||
json_dict['container'] = container.name
|
||||
json_dict['instance_name'] = container.get('avalon').get('instance_name')
|
||||
json_dict = {
|
||||
"subset": i.data.get("subset"),
|
||||
"container": container.name,
|
||||
}
|
||||
json_dict["instance_name"] = container.get("avalon").get(
|
||||
"instance_name"
|
||||
)
|
||||
json_data.append(json_dict)
|
||||
|
||||
if "representations" not in instance.data:
|
||||
|
|
@ -44,13 +48,14 @@ class ExtractSetDress(pype.api.Extractor):
|
|||
json.dump(json_data, fp=file, indent=2)
|
||||
|
||||
json_representation = {
|
||||
'name': 'json',
|
||||
'ext': 'json',
|
||||
'files': json_filename,
|
||||
"name": "json",
|
||||
"ext": "json",
|
||||
"files": json_filename,
|
||||
"stagingDir": stagingdir,
|
||||
}
|
||||
instance.data["representations"].append(json_representation)
|
||||
|
||||
self.log.info("Extracted instance '{}' to: {}".format(
|
||||
instance.name, json_representation))
|
||||
|
||||
self.log.info(
|
||||
"Extracted instance '{}' to: {}".format(instance.name,
|
||||
json_representation)
|
||||
)
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
import os
|
||||
import avalon.blender.workio
|
||||
|
||||
import pype.api
|
||||
|
||||
|
||||
class ExtractBlend(pype.api.Extractor):
|
||||
"""Extract a blend file."""
|
||||
|
||||
label = "Extract Blend"
|
||||
hosts = ["blender"]
|
||||
families = ["model", "camera", "rig", "action", "layout", "animation"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
self.log.info("Performing extraction..")
|
||||
|
||||
# Just save the file to a temporary location. At least for now it's no
|
||||
# problem to have (possibly) extra stuff in the file.
|
||||
avalon.blender.workio.save_file(filepath, copy=True)
|
||||
#
|
||||
# # Store reference for integration
|
||||
# if "files" not in instance.data:
|
||||
# instance.data["files"] = list()
|
||||
#
|
||||
# # instance.data["files"].append(filename)
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
representation = {
|
||||
'name': 'blend',
|
||||
'ext': 'blend',
|
||||
'files': filename,
|
||||
"stagingDir": stagingdir,
|
||||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
self.log.info("Extracted instance '%s' to: %s",
|
||||
instance.name, representation)
|
||||
import os
|
||||
import avalon.blender.workio
|
||||
|
||||
import pype.api
|
||||
|
||||
|
||||
class ExtractBlend(pype.api.Extractor):
|
||||
"""Extract a blend file."""
|
||||
|
||||
label = "Extract Blend"
|
||||
hosts = ["blender"]
|
||||
families = ["model", "camera", "rig", "action", "layout", "animation"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
# Define extract output file path
|
||||
|
||||
stagingdir = self.staging_dir(instance)
|
||||
filename = f"{instance.name}.blend"
|
||||
filepath = os.path.join(stagingdir, filename)
|
||||
|
||||
# Perform extraction
|
||||
self.log.info("Performing extraction..")
|
||||
|
||||
# Just save the file to a temporary location. At least for now it's no
|
||||
# problem to have (possibly) extra stuff in the file.
|
||||
avalon.blender.workio.save_file(filepath, copy=True)
|
||||
#
|
||||
# # Store reference for integration
|
||||
# if "files" not in instance.data:
|
||||
# instance.data["files"] = list()
|
||||
#
|
||||
# # instance.data["files"].append(filename)
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
representation = {
|
||||
'name': 'blend',
|
||||
'ext': 'blend',
|
||||
'files': filename,
|
||||
"stagingDir": stagingdir,
|
||||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
self.log.info("Extracted instance '%s' to: %s",
|
||||
instance.name, representation)
|
||||
|
|
@ -3,7 +3,7 @@ from typing import List
|
|||
import bpy
|
||||
|
||||
import pyblish.api
|
||||
import pype.hosts.blender.action
|
||||
import pype.hosts.blender.api.action
|
||||
|
||||
|
||||
class ValidateMeshHasUvs(pyblish.api.InstancePlugin):
|
||||
|
|
@ -14,7 +14,7 @@ class ValidateMeshHasUvs(pyblish.api.InstancePlugin):
|
|||
families = ["model"]
|
||||
category = "geometry"
|
||||
label = "Mesh Has UV's"
|
||||
actions = [pype.hosts.blender.action.SelectInvalidAction]
|
||||
actions = [pype.hosts.blender.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -3,7 +3,7 @@ from typing import List
|
|||
import bpy
|
||||
|
||||
import pyblish.api
|
||||
import pype.hosts.blender.action
|
||||
import pype.hosts.blender.api.action
|
||||
|
||||
|
||||
class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
||||
|
|
@ -13,7 +13,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
|||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
label = "Mesh No Negative Scale"
|
||||
actions = [pype.hosts.blender.action.SelectInvalidAction]
|
||||
actions = [pype.hosts.blender.api.action.SelectInvalidAction]
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance) -> List:
|
||||
3
pype/hosts/blender/startup/init.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from pype.hosts.blender import api
|
||||
|
||||
api.install()
|
||||
|
|
@ -7,10 +7,7 @@ from pype.hosts.celaction import api as celaction
|
|||
|
||||
class CelactionPrelaunchHook(PreLaunchHook):
|
||||
"""
|
||||
This hook will check if current workfile path has Unreal
|
||||
project inside. IF not, it initialize it and finally it pass
|
||||
path to the project by environment variable to Unreal launcher
|
||||
shell script.
|
||||
Bootstrap celacion with pype
|
||||
"""
|
||||
workfile_ext = "scn"
|
||||
app_groups = ["celaction"]
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
import os
|
||||
from pype.api import Logger
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
from pype import PLUGINS_DIR
|
||||
|
||||
from .workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root
|
||||
)
|
||||
|
||||
from .menu import (
|
||||
install as menu_install,
|
||||
_update_menu_task_label
|
||||
)
|
||||
|
||||
from .events import register_hiero_events
|
||||
|
||||
__all__ = [
|
||||
# Workfiles API
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root",
|
||||
]
|
||||
|
||||
# get logger
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
||||
''' Creating all important host related variables '''
|
||||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||
|
||||
# plugin root path
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "hiero", "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "hiero", "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "hiero", "create")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "hiero", "inventory")
|
||||
|
||||
# registering particular pyblish gui but `lite` is recomended!!
|
||||
if os.getenv("PYBLISH_GUI", None):
|
||||
pyblish.register_gui(os.getenv("PYBLISH_GUI", None))
|
||||
|
||||
|
||||
def install():
|
||||
"""
|
||||
Installing Hiero integration for avalon
|
||||
|
||||
Args:
|
||||
config (obj): avalon config module `pype` in our case, it is not
|
||||
used but required by avalon.api.install()
|
||||
|
||||
"""
|
||||
|
||||
# adding all events
|
||||
_register_events()
|
||||
|
||||
log.info("Registering Hiero plug-ins..")
|
||||
pyblish.register_host("hiero")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
|
||||
|
||||
# Disable all families except for the ones we explicitly want to see
|
||||
family_states = [
|
||||
"write",
|
||||
"review",
|
||||
"plate"
|
||||
]
|
||||
|
||||
avalon.data["familiesStateDefault"] = False
|
||||
avalon.data["familiesStateToggled"] = family_states
|
||||
|
||||
# install menu
|
||||
menu_install()
|
||||
|
||||
# register hiero events
|
||||
register_hiero_events()
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""
|
||||
Uninstalling Hiero integration for avalon
|
||||
|
||||
"""
|
||||
log.info("Deregistering Hiero plug-ins..")
|
||||
pyblish.deregister_host("hiero")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
|
||||
|
||||
def _register_events():
|
||||
"""
|
||||
Adding all callbacks.
|
||||
"""
|
||||
|
||||
# if task changed then change notext of hiero
|
||||
avalon.on("taskChanged", _update_menu_task_label)
|
||||
log.info("Installed event callback for 'taskChanged'..")
|
||||
|
||||
|
||||
def ls():
|
||||
"""List available containers.
|
||||
|
||||
This function is used by the Container Manager in Nuke. You'll
|
||||
need to implement a for-loop that then *yields* one Container at
|
||||
a time.
|
||||
|
||||
See the `container.json` schema for details on how it should look,
|
||||
and the Maya equivalent, which is in `avalon.maya.pipeline`
|
||||
"""
|
||||
# TODO: listing all availabe containers form sequence
|
||||
return
|
||||
101
pype/hosts/hiero/api/__init__.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
from .workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root
|
||||
)
|
||||
|
||||
from .pipeline import (
|
||||
launch_workfiles_app,
|
||||
ls,
|
||||
install,
|
||||
uninstall,
|
||||
reload_config,
|
||||
containerise,
|
||||
publish,
|
||||
maintained_selection,
|
||||
parse_container,
|
||||
update_container,
|
||||
reset_selection
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
get_track_items,
|
||||
get_current_project,
|
||||
get_current_sequence,
|
||||
get_current_track,
|
||||
get_track_item_pype_tag,
|
||||
set_track_item_pype_tag,
|
||||
get_track_item_pype_data,
|
||||
set_publish_attribute,
|
||||
get_publish_attribute,
|
||||
imprint,
|
||||
get_selected_track_items,
|
||||
set_selected_track_items,
|
||||
create_nuke_workfile_clips,
|
||||
create_bin,
|
||||
apply_colorspace_project,
|
||||
apply_colorspace_clips,
|
||||
is_overlapping,
|
||||
get_sequence_pattern_and_padding
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
CreatorWidget,
|
||||
Creator,
|
||||
PublishClip,
|
||||
SequenceLoader,
|
||||
ClipLoader
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# avalon pipeline module
|
||||
"launch_workfiles_app",
|
||||
"ls",
|
||||
"install",
|
||||
"uninstall",
|
||||
"reload_config",
|
||||
"containerise",
|
||||
"publish",
|
||||
"maintained_selection",
|
||||
"parse_container",
|
||||
"update_container",
|
||||
"reset_selection",
|
||||
|
||||
# Workfiles API
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root",
|
||||
|
||||
# Lib functions
|
||||
"get_track_items",
|
||||
"get_current_project",
|
||||
"get_current_sequence",
|
||||
"get_current_track",
|
||||
"get_track_item_pype_tag",
|
||||
"set_track_item_pype_tag",
|
||||
"get_track_item_pype_data",
|
||||
"set_publish_attribute",
|
||||
"get_publish_attribute",
|
||||
"imprint",
|
||||
"get_selected_track_items",
|
||||
"set_selected_track_items",
|
||||
"create_nuke_workfile_clips",
|
||||
"create_bin",
|
||||
"is_overlapping",
|
||||
"apply_colorspace_project",
|
||||
"apply_colorspace_clips",
|
||||
"get_sequence_pattern_and_padding",
|
||||
|
||||
# plugins
|
||||
"CreatorWidget",
|
||||
"Creator",
|
||||
"PublishClip",
|
||||
"SequenceLoader",
|
||||
"ClipLoader"
|
||||
]
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import os
|
||||
import hiero.core.events
|
||||
import avalon.api as avalon
|
||||
from pype.api import Logger
|
||||
from .lib import sync_avalon_data_to_workfile, launch_workfiles_app
|
||||
from .tags import add_tags_from_presets
|
||||
from .tags import add_tags_to_workfile
|
||||
from .menu import update_menu_task_label
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ def afterNewProjectCreated(event):
|
|||
sync_avalon_data_to_workfile()
|
||||
|
||||
# add tags from preset
|
||||
add_tags_from_presets()
|
||||
add_tags_to_workfile()
|
||||
|
||||
# Workfiles.
|
||||
if int(os.environ.get("WORKFILES_STARTUP", "0")):
|
||||
|
|
@ -48,7 +50,7 @@ def afterProjectLoad(event):
|
|||
sync_avalon_data_to_workfile()
|
||||
|
||||
# add tags from preset
|
||||
add_tags_from_presets()
|
||||
add_tags_to_workfile()
|
||||
|
||||
|
||||
def beforeProjectClosed(event):
|
||||
|
|
@ -77,7 +79,7 @@ def register_hiero_events():
|
|||
"kAfterNewProjectCreated, kBeforeProjectLoad, kAfterProjectLoad, "
|
||||
"kBeforeProjectSave, kAfterProjectSave, kBeforeProjectClose, "
|
||||
"kAfterProjectClose, kShutdown, kStartup"
|
||||
)
|
||||
)
|
||||
|
||||
# hiero.core.events.registerInterest(
|
||||
# "kBeforeNewProjectCreated", beforeNewProjectCreated)
|
||||
|
|
@ -105,3 +107,13 @@ def register_hiero_events():
|
|||
# workfiles
|
||||
hiero.core.events.registerEventType("kStartWorkfiles")
|
||||
hiero.core.events.registerInterest("kStartWorkfiles", launch_workfiles_app)
|
||||
|
||||
|
||||
def register_events():
|
||||
"""
|
||||
Adding all callbacks.
|
||||
"""
|
||||
|
||||
# if task changed then change notext of hiero
|
||||
avalon.on("taskChanged", update_menu_task_label)
|
||||
log.info("Installed event callback for 'taskChanged'..")
|
||||
895
pype/hosts/hiero/api/lib.py
Normal file
|
|
@ -0,0 +1,895 @@
|
|||
"""
|
||||
Host specific functions where host api is connected
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import ast
|
||||
import hiero
|
||||
import avalon.api as avalon
|
||||
import avalon.io
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from pype.api import (Logger, Anatomy, config)
|
||||
from . import tags
|
||||
import shutil
|
||||
from compiler.ast import flatten
|
||||
|
||||
try:
|
||||
from PySide.QtCore import QFile, QTextStream
|
||||
from PySide.QtXml import QDomDocument
|
||||
except ImportError:
|
||||
from PySide2.QtCore import QFile, QTextStream
|
||||
from PySide2.QtXml import QDomDocument
|
||||
|
||||
# from opentimelineio import opentime
|
||||
# from pprint import pformat
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._has_been_setup = False
|
||||
self._has_menu = False
|
||||
self._registered_gui = None
|
||||
self.pype_tag_name = "Pype Data"
|
||||
self.default_sequence_name = "PypeSequence"
|
||||
self.default_bin_name = "PypeBin"
|
||||
|
||||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||
|
||||
|
||||
def get_current_project(remove_untitled=False):
|
||||
projects = flatten(hiero.core.projects())
|
||||
if not remove_untitled:
|
||||
return next(iter(projects))
|
||||
|
||||
# if remove_untitled
|
||||
for proj in projects:
|
||||
if "Untitled" in proj.name():
|
||||
proj.close()
|
||||
else:
|
||||
return proj
|
||||
|
||||
|
||||
def get_current_sequence(name=None, new=False):
|
||||
"""
|
||||
Get current sequence in context of active project.
|
||||
|
||||
Args:
|
||||
name (str)[optional]: name of sequence we want to return
|
||||
new (bool)[optional]: if we want to create new one
|
||||
|
||||
Returns:
|
||||
hiero.core.Sequence: the sequence object
|
||||
"""
|
||||
sequence = None
|
||||
project = get_current_project()
|
||||
root_bin = project.clipsBin()
|
||||
|
||||
if new:
|
||||
# create new
|
||||
name = name or self.default_sequence_name
|
||||
sequence = hiero.core.Sequence(name)
|
||||
root_bin.addItem(hiero.core.BinItem(sequence))
|
||||
elif name:
|
||||
# look for sequence by name
|
||||
sequences = project.sequences()
|
||||
for _sequence in sequences:
|
||||
if _sequence.name() == name:
|
||||
sequence = _sequence
|
||||
if not sequence:
|
||||
# if nothing found create new with input name
|
||||
sequence = get_current_sequence(name, True)
|
||||
elif not name and not new:
|
||||
# if name is none and new is False then return current open sequence
|
||||
sequence = hiero.ui.activeSequence()
|
||||
|
||||
return sequence
|
||||
|
||||
|
||||
def get_current_track(sequence, name, audio=False):
|
||||
"""
|
||||
Get current track in context of active project.
|
||||
|
||||
Creates new if none is found.
|
||||
|
||||
Args:
|
||||
sequence (hiero.core.Sequence): hiero sequene object
|
||||
name (str): name of track we want to return
|
||||
audio (bool)[optional]: switch to AudioTrack
|
||||
|
||||
Returns:
|
||||
hiero.core.Track: the track object
|
||||
"""
|
||||
tracks = sequence.videoTracks()
|
||||
|
||||
if audio:
|
||||
tracks = sequence.audioTracks()
|
||||
|
||||
# get track by name
|
||||
track = None
|
||||
for _track in tracks:
|
||||
if _track.name() in name:
|
||||
track = _track
|
||||
|
||||
if not track:
|
||||
if not audio:
|
||||
track = hiero.core.VideoTrack(name)
|
||||
else:
|
||||
track = hiero.core.AudioTrack(name)
|
||||
sequence.addTrack(track)
|
||||
|
||||
return track
|
||||
|
||||
|
||||
def get_track_items(
|
||||
selected=False,
|
||||
sequence_name=None,
|
||||
track_item_name=None,
|
||||
track_name=None,
|
||||
track_type=None,
|
||||
check_enabled=True,
|
||||
check_locked=True,
|
||||
check_tagged=False):
|
||||
"""Get all available current timeline track items.
|
||||
|
||||
Attribute:
|
||||
selected (bool)[optional]: return only selected items on timeline
|
||||
sequence_name (str)[optional]: return only clips from input sequence
|
||||
track_item_name (str)[optional]: return only item with input name
|
||||
track_name (str)[optional]: return only items from track name
|
||||
track_type (str)[optional]: return only items of given type
|
||||
(`audio` or `video`) default is `video`
|
||||
check_enabled (bool)[optional]: ignore disabled if True
|
||||
check_locked (bool)[optional]: ignore locked if True
|
||||
|
||||
Return:
|
||||
list or hiero.core.TrackItem: list of track items or single track item
|
||||
"""
|
||||
return_list = list()
|
||||
track_items = list()
|
||||
|
||||
# get selected track items or all in active sequence
|
||||
if selected:
|
||||
selected_items = list(hiero.selection)
|
||||
for item in selected_items:
|
||||
if track_name and track_name in item.parent().name():
|
||||
# filter only items fitting input track name
|
||||
track_items.append(item)
|
||||
elif not track_name:
|
||||
# or add all if no track_name was defined
|
||||
track_items.append(item)
|
||||
else:
|
||||
sequence = get_current_sequence(name=sequence_name)
|
||||
# get all available tracks from sequence
|
||||
tracks = list(sequence.audioTracks()) + list(sequence.videoTracks())
|
||||
# loop all tracks
|
||||
for track in tracks:
|
||||
if check_locked and track.isLocked():
|
||||
continue
|
||||
if check_enabled and not track.isEnabled():
|
||||
continue
|
||||
# and all items in track
|
||||
for item in track.items():
|
||||
if check_tagged and not item.tags():
|
||||
continue
|
||||
|
||||
# check if track item is enabled
|
||||
if check_enabled:
|
||||
if not item.isEnabled():
|
||||
continue
|
||||
if track_item_name:
|
||||
if item.name() in track_item_name:
|
||||
return item
|
||||
# make sure only track items with correct track names are added
|
||||
if track_name and track_name in track.name():
|
||||
# filter out only defined track_name items
|
||||
track_items.append(item)
|
||||
elif not track_name:
|
||||
# or add all if no track_name is defined
|
||||
track_items.append(item)
|
||||
|
||||
# filter out only track items with defined track_type
|
||||
for track_item in track_items:
|
||||
if track_type and track_type == "video" and isinstance(
|
||||
track_item.parent(), hiero.core.VideoTrack):
|
||||
# only video track items are allowed
|
||||
return_list.append(track_item)
|
||||
elif track_type and track_type == "audio" and isinstance(
|
||||
track_item.parent(), hiero.core.AudioTrack):
|
||||
# only audio track items are allowed
|
||||
return_list.append(track_item)
|
||||
elif not track_type:
|
||||
# add all if no track_type is defined
|
||||
return_list.append(track_item)
|
||||
|
||||
return return_list
|
||||
|
||||
|
||||
def get_track_item_pype_tag(track_item):
|
||||
"""
|
||||
Get pype track item tag created by creator or loader plugin.
|
||||
|
||||
Attributes:
|
||||
trackItem (hiero.core.TrackItem): hiero object
|
||||
|
||||
Returns:
|
||||
hiero.core.Tag: hierarchy, orig clip attributes
|
||||
"""
|
||||
# get all tags from track item
|
||||
_tags = track_item.tags()
|
||||
if not _tags:
|
||||
return None
|
||||
for tag in _tags:
|
||||
# return only correct tag defined by global name
|
||||
if tag.name() in self.pype_tag_name:
|
||||
return tag
|
||||
|
||||
|
||||
def set_track_item_pype_tag(track_item, data=None):
|
||||
"""
|
||||
Set pype track item tag to input track_item.
|
||||
|
||||
Attributes:
|
||||
trackItem (hiero.core.TrackItem): hiero object
|
||||
|
||||
Returns:
|
||||
hiero.core.Tag
|
||||
"""
|
||||
data = data or dict()
|
||||
|
||||
# basic Tag's attribute
|
||||
tag_data = {
|
||||
"editable": "0",
|
||||
"note": "Pype data holder",
|
||||
"icon": "pype_icon.png",
|
||||
"metadata": {k: v for k, v in data.items()}
|
||||
}
|
||||
# get available pype tag if any
|
||||
_tag = get_track_item_pype_tag(track_item)
|
||||
|
||||
if _tag:
|
||||
# it not tag then create one
|
||||
tag = tags.update_tag(_tag, tag_data)
|
||||
else:
|
||||
# if pype tag available then update with input data
|
||||
tag = tags.create_tag(self.pype_tag_name, tag_data)
|
||||
# add it to the input track item
|
||||
track_item.addTag(tag)
|
||||
|
||||
return tag
|
||||
|
||||
|
||||
def get_track_item_pype_data(track_item):
|
||||
"""
|
||||
Get track item's pype tag data.
|
||||
|
||||
Attributes:
|
||||
trackItem (hiero.core.TrackItem): hiero object
|
||||
|
||||
Returns:
|
||||
dict: data found on pype tag
|
||||
"""
|
||||
data = dict()
|
||||
# get pype data tag from track item
|
||||
tag = get_track_item_pype_tag(track_item)
|
||||
|
||||
if not tag:
|
||||
return None
|
||||
|
||||
# get tag metadata attribut
|
||||
tag_data = tag.metadata()
|
||||
# convert tag metadata to normal keys names and values to correct types
|
||||
for k, v in dict(tag_data).items():
|
||||
key = k.replace("tag.", "")
|
||||
|
||||
try:
|
||||
# capture exceptions which are related to strings only
|
||||
value = ast.literal_eval(v)
|
||||
except (ValueError, SyntaxError):
|
||||
value = v
|
||||
|
||||
data.update({key: value})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def imprint(track_item, data=None):
|
||||
"""
|
||||
Adding `Avalon data` into a hiero track item tag.
|
||||
|
||||
Also including publish attribute into tag.
|
||||
|
||||
Arguments:
|
||||
track_item (hiero.core.TrackItem): hiero track item object
|
||||
data (dict): Any data which needst to be imprinted
|
||||
|
||||
Examples:
|
||||
data = {
|
||||
'asset': 'sq020sh0280',
|
||||
'family': 'render',
|
||||
'subset': 'subsetMain'
|
||||
}
|
||||
"""
|
||||
data = data or {}
|
||||
|
||||
tag = set_track_item_pype_tag(track_item, data)
|
||||
|
||||
# add publish attribute
|
||||
set_publish_attribute(tag, True)
|
||||
|
||||
|
||||
def set_publish_attribute(tag, value):
|
||||
""" Set Publish attribute in input Tag object
|
||||
|
||||
Attribute:
|
||||
tag (hiero.core.Tag): a tag object
|
||||
value (bool): True or False
|
||||
"""
|
||||
tag_data = tag.metadata()
|
||||
# set data to the publish attribute
|
||||
tag_data.setValue("tag.publish", str(value))
|
||||
|
||||
|
||||
def get_publish_attribute(tag):
|
||||
""" Get Publish attribute from input Tag object
|
||||
|
||||
Attribute:
|
||||
tag (hiero.core.Tag): a tag object
|
||||
value (bool): True or False
|
||||
"""
|
||||
tag_data = tag.metadata()
|
||||
# get data to the publish attribute
|
||||
value = tag_data.value("tag.publish")
|
||||
# return value converted to bool value. Atring is stored in tag.
|
||||
return ast.literal_eval(value)
|
||||
|
||||
|
||||
def sync_avalon_data_to_workfile():
|
||||
# import session to get project dir
|
||||
project_name = avalon.Session["AVALON_PROJECT"]
|
||||
|
||||
anatomy = Anatomy(project_name)
|
||||
work_template = anatomy.templates["work"]["path"]
|
||||
work_root = anatomy.root_value_for_template(work_template)
|
||||
active_project_root = (
|
||||
os.path.join(work_root, project_name)
|
||||
).replace("\\", "/")
|
||||
# getting project
|
||||
project = get_current_project()
|
||||
|
||||
if "Tag Presets" in project.name():
|
||||
return
|
||||
|
||||
log.debug("Synchronizing Pype metadata to project: {}".format(
|
||||
project.name()))
|
||||
|
||||
# set project root with backward compatibility
|
||||
try:
|
||||
project.setProjectDirectory(active_project_root)
|
||||
except Exception:
|
||||
# old way of seting it
|
||||
project.setProjectRoot(active_project_root)
|
||||
|
||||
# get project data from avalon db
|
||||
project_doc = avalon.io.find_one({"type": "project"})
|
||||
project_data = project_doc["data"]
|
||||
|
||||
log.debug("project_data: {}".format(project_data))
|
||||
|
||||
# get format and fps property from avalon db on project
|
||||
width = project_data["resolutionWidth"]
|
||||
height = project_data["resolutionHeight"]
|
||||
pixel_aspect = project_data["pixelAspect"]
|
||||
fps = project_data['fps']
|
||||
format_name = project_data['code']
|
||||
|
||||
# create new format in hiero project
|
||||
format = hiero.core.Format(width, height, pixel_aspect, format_name)
|
||||
project.setOutputFormat(format)
|
||||
|
||||
# set fps to hiero project
|
||||
project.setFramerate(fps)
|
||||
|
||||
# TODO: add auto colorspace set from project drop
|
||||
log.info("Project property has been synchronised with Avalon db")
|
||||
|
||||
|
||||
def launch_workfiles_app(event):
|
||||
"""
|
||||
Event for launching workfiles after hiero start
|
||||
|
||||
Args:
|
||||
event (obj): required but unused
|
||||
"""
|
||||
from . import launch_workfiles_app
|
||||
launch_workfiles_app()
|
||||
|
||||
|
||||
def setup(console=False, port=None, menu=True):
|
||||
"""Setup integration
|
||||
|
||||
Registers Pyblish for Hiero plug-ins and appends an item to the File-menu
|
||||
|
||||
Arguments:
|
||||
console (bool): Display console with GUI
|
||||
port (int, optional): Port from which to start looking for an
|
||||
available port to connect with Pyblish QML, default
|
||||
provided by Pyblish Integration.
|
||||
menu (bool, optional): Display file menu in Hiero.
|
||||
"""
|
||||
|
||||
if self._has_been_setup:
|
||||
teardown()
|
||||
|
||||
add_submission()
|
||||
|
||||
if menu:
|
||||
add_to_filemenu()
|
||||
self._has_menu = True
|
||||
|
||||
self._has_been_setup = True
|
||||
log.debug("pyblish: Loaded successfully.")
|
||||
|
||||
|
||||
def teardown():
|
||||
"""Remove integration"""
|
||||
if not self._has_been_setup:
|
||||
return
|
||||
|
||||
if self._has_menu:
|
||||
remove_from_filemenu()
|
||||
self._has_menu = False
|
||||
|
||||
self._has_been_setup = False
|
||||
log.debug("pyblish: Integration torn down successfully")
|
||||
|
||||
|
||||
def remove_from_filemenu():
|
||||
raise NotImplementedError("Implement me please.")
|
||||
|
||||
|
||||
def add_to_filemenu():
|
||||
PublishAction()
|
||||
|
||||
|
||||
class PyblishSubmission(hiero.exporters.FnSubmission.Submission):
|
||||
|
||||
def __init__(self):
|
||||
hiero.exporters.FnSubmission.Submission.__init__(self)
|
||||
|
||||
def addToQueue(self):
|
||||
from . import publish
|
||||
# Add submission to Hiero module for retrieval in plugins.
|
||||
hiero.submission = self
|
||||
publish()
|
||||
|
||||
|
||||
def add_submission():
|
||||
registry = hiero.core.taskRegistry
|
||||
registry.addSubmission("Pyblish", PyblishSubmission)
|
||||
|
||||
|
||||
class PublishAction(QtWidgets.QAction):
|
||||
"""
|
||||
Action with is showing as menu item
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
QtWidgets.QAction.__init__(self, "Publish", None)
|
||||
self.triggered.connect(self.publish)
|
||||
|
||||
for interest in ["kShowContextMenu/kTimeline",
|
||||
"kShowContextMenukBin",
|
||||
"kShowContextMenu/kSpreadsheet"]:
|
||||
hiero.core.events.registerInterest(interest, self.eventHandler)
|
||||
|
||||
self.setShortcut("Ctrl+Alt+P")
|
||||
|
||||
def publish(self):
|
||||
from . import publish
|
||||
# Removing "submission" attribute from hiero module, to prevent tasks
|
||||
# from getting picked up when not using the "Export" dialog.
|
||||
if hasattr(hiero, "submission"):
|
||||
del hiero.submission
|
||||
publish()
|
||||
|
||||
def eventHandler(self, event):
|
||||
# Add the Menu to the right-click menu
|
||||
event.menu.addAction(self)
|
||||
|
||||
|
||||
# def CreateNukeWorkfile(nodes=None,
|
||||
# nodes_effects=None,
|
||||
# to_timeline=False,
|
||||
# **kwargs):
|
||||
# ''' Creating nuke workfile with particular version with given nodes
|
||||
# Also it is creating timeline track items as precomps.
|
||||
#
|
||||
# Arguments:
|
||||
# nodes(list of dict): each key in dict is knob order is important
|
||||
# to_timeline(type): will build trackItem with metadata
|
||||
#
|
||||
# Returns:
|
||||
# bool: True if done
|
||||
#
|
||||
# Raises:
|
||||
# Exception: with traceback
|
||||
#
|
||||
# '''
|
||||
# import hiero.core
|
||||
# from avalon.nuke import imprint
|
||||
# from pype.hosts.nuke import (
|
||||
# lib as nklib
|
||||
# )
|
||||
#
|
||||
# # check if the file exists if does then Raise "File exists!"
|
||||
# if os.path.exists(filepath):
|
||||
# raise FileExistsError("File already exists: `{}`".format(filepath))
|
||||
#
|
||||
# # if no representations matching then
|
||||
# # Raise "no representations to be build"
|
||||
# if len(representations) == 0:
|
||||
# raise AttributeError("Missing list of `representations`")
|
||||
#
|
||||
# # check nodes input
|
||||
# if len(nodes) == 0:
|
||||
# log.warning("Missing list of `nodes`")
|
||||
#
|
||||
# # create temp nk file
|
||||
# nuke_script = hiero.core.nuke.ScriptWriter()
|
||||
#
|
||||
# # create root node and save all metadata
|
||||
# root_node = hiero.core.nuke.RootNode()
|
||||
#
|
||||
# anatomy = Anatomy(os.environ["AVALON_PROJECT"])
|
||||
# work_template = anatomy.templates["work"]["path"]
|
||||
# root_path = anatomy.root_value_for_template(work_template)
|
||||
#
|
||||
# nuke_script.addNode(root_node)
|
||||
#
|
||||
# # here to call pype.hosts.nuke.lib.BuildWorkfile
|
||||
# script_builder = nklib.BuildWorkfile(
|
||||
# root_node=root_node,
|
||||
# root_path=root_path,
|
||||
# nodes=nuke_script.getNodes(),
|
||||
# **kwargs
|
||||
# )
|
||||
|
||||
|
||||
def create_nuke_workfile_clips(nuke_workfiles, seq=None):
|
||||
'''
|
||||
nuke_workfiles is list of dictionaries like:
|
||||
[{
|
||||
'path': 'P:/Jakub_testy_pipeline/test_v01.nk',
|
||||
'name': 'test',
|
||||
'handleStart': 15, # added asymetrically to handles
|
||||
'handleEnd': 10, # added asymetrically to handles
|
||||
"clipIn": 16,
|
||||
"frameStart": 991,
|
||||
"frameEnd": 1023,
|
||||
'task': 'Comp-tracking',
|
||||
'work_dir': 'VFX_PR',
|
||||
'shot': '00010'
|
||||
}]
|
||||
'''
|
||||
|
||||
proj = hiero.core.projects()[-1]
|
||||
root = proj.clipsBin()
|
||||
|
||||
if not seq:
|
||||
seq = hiero.core.Sequence('NewSequences')
|
||||
root.addItem(hiero.core.BinItem(seq))
|
||||
# todo will ned to define this better
|
||||
# track = seq[1] # lazy example to get a destination# track
|
||||
clips_lst = []
|
||||
for nk in nuke_workfiles:
|
||||
task_path = '/'.join([nk['work_dir'], nk['shot'], nk['task']])
|
||||
bin = create_bin(task_path, proj)
|
||||
|
||||
if nk['task'] not in seq.videoTracks():
|
||||
track = hiero.core.VideoTrack(nk['task'])
|
||||
seq.addTrack(track)
|
||||
else:
|
||||
track = seq.tracks(nk['task'])
|
||||
|
||||
# create clip media
|
||||
media = hiero.core.MediaSource(nk['path'])
|
||||
media_in = int(media.startTime() or 0)
|
||||
media_duration = int(media.duration() or 0)
|
||||
|
||||
handle_start = nk.get("handleStart")
|
||||
handle_end = nk.get("handleEnd")
|
||||
|
||||
if media_in:
|
||||
source_in = media_in + handle_start
|
||||
else:
|
||||
source_in = nk["frameStart"] + handle_start
|
||||
|
||||
if media_duration:
|
||||
source_out = (media_in + media_duration - 1) - handle_end
|
||||
else:
|
||||
source_out = nk["frameEnd"] - handle_end
|
||||
|
||||
source = hiero.core.Clip(media)
|
||||
|
||||
name = os.path.basename(os.path.splitext(nk['path'])[0])
|
||||
split_name = split_by_client_version(name)[0] or name
|
||||
|
||||
# add to bin as clip item
|
||||
items_in_bin = [b.name() for b in bin.items()]
|
||||
if split_name not in items_in_bin:
|
||||
binItem = hiero.core.BinItem(source)
|
||||
bin.addItem(binItem)
|
||||
|
||||
new_source = [
|
||||
item for item in bin.items() if split_name in item.name()
|
||||
][0].items()[0].item()
|
||||
|
||||
# add to track as clip item
|
||||
trackItem = hiero.core.TrackItem(
|
||||
split_name, hiero.core.TrackItem.kVideo)
|
||||
trackItem.setSource(new_source)
|
||||
trackItem.setSourceIn(source_in)
|
||||
trackItem.setSourceOut(source_out)
|
||||
trackItem.setTimelineIn(nk["clipIn"])
|
||||
trackItem.setTimelineOut(nk["clipIn"] + (source_out - source_in))
|
||||
track.addTrackItem(trackItem)
|
||||
clips_lst.append(trackItem)
|
||||
|
||||
return clips_lst
|
||||
|
||||
|
||||
def create_bin(path=None, project=None):
|
||||
'''
|
||||
Create bin in project.
|
||||
If the path is "bin1/bin2/bin3" it will create whole depth
|
||||
and return `bin3`
|
||||
|
||||
'''
|
||||
# get the first loaded project
|
||||
project = project or get_current_project()
|
||||
|
||||
path = path or self.default_bin_name
|
||||
|
||||
path = path.replace("\\", "/").split("/")
|
||||
|
||||
root_bin = project.clipsBin()
|
||||
|
||||
done_bin_lst = []
|
||||
for i, b in enumerate(path):
|
||||
if i == 0 and len(path) > 1:
|
||||
if b in [bin.name() for bin in root_bin.bins()]:
|
||||
bin = [bin for bin in root_bin.bins() if b in bin.name()][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
root_bin.addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
|
||||
elif i >= 1 and i < len(path) - 1:
|
||||
if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]:
|
||||
bin = [
|
||||
bin for bin in done_bin_lst[i - 1].bins()
|
||||
if b in bin.name()
|
||||
][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
done_bin_lst[i - 1].addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
|
||||
elif i == len(path) - 1:
|
||||
if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]:
|
||||
bin = [
|
||||
bin for bin in done_bin_lst[i - 1].bins()
|
||||
if b in bin.name()
|
||||
][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
done_bin_lst[i - 1].addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
|
||||
return done_bin_lst[-1]
|
||||
|
||||
|
||||
def split_by_client_version(string):
|
||||
regex = r"[/_.]v\d+"
|
||||
try:
|
||||
matches = re.findall(regex, string, re.IGNORECASE)
|
||||
return string.split(matches[0])
|
||||
except Exception as error:
|
||||
log.error(error)
|
||||
return None
|
||||
|
||||
|
||||
def get_selected_track_items(sequence=None):
|
||||
_sequence = sequence or get_current_sequence()
|
||||
|
||||
# Getting selection
|
||||
timeline_editor = hiero.ui.getTimelineEditor(_sequence)
|
||||
return timeline_editor.selection()
|
||||
|
||||
|
||||
def set_selected_track_items(track_items_list, sequence=None):
|
||||
_sequence = sequence or get_current_sequence()
|
||||
|
||||
# Getting selection
|
||||
timeline_editor = hiero.ui.getTimelineEditor(_sequence)
|
||||
return timeline_editor.setSelection(track_items_list)
|
||||
|
||||
|
||||
def _read_doc_from_path(path):
|
||||
# reading QDomDocument from HROX path
|
||||
hrox_file = QFile(path)
|
||||
if not hrox_file.open(QFile.ReadOnly):
|
||||
raise RuntimeError("Failed to open file for reading")
|
||||
doc = QDomDocument()
|
||||
doc.setContent(hrox_file)
|
||||
hrox_file.close()
|
||||
return doc
|
||||
|
||||
|
||||
def _write_doc_to_path(doc, path):
|
||||
# write QDomDocument to path as HROX
|
||||
hrox_file = QFile(path)
|
||||
if not hrox_file.open(QFile.WriteOnly):
|
||||
raise RuntimeError("Failed to open file for writing")
|
||||
stream = QTextStream(hrox_file)
|
||||
doc.save(stream, 1)
|
||||
hrox_file.close()
|
||||
|
||||
|
||||
def _set_hrox_project_knobs(doc, **knobs):
|
||||
# set attributes to Project Tag
|
||||
proj_elem = doc.documentElement().firstChildElement("Project")
|
||||
for k, v in knobs.items():
|
||||
proj_elem.setAttribute(k, v)
|
||||
|
||||
|
||||
def apply_colorspace_project():
|
||||
# get path the the active projects
|
||||
project = get_current_project(remove_untitled=True)
|
||||
current_file = project.path()
|
||||
|
||||
# close the active project
|
||||
project.close()
|
||||
|
||||
# get presets for hiero
|
||||
presets = config.get_init_presets()
|
||||
colorspace = presets["colorspace"]
|
||||
hiero_project_clrs = colorspace.get("hiero", {}).get("project", {})
|
||||
|
||||
# save the workfile as subversion "comment:_colorspaceChange"
|
||||
split_current_file = os.path.splitext(current_file)
|
||||
copy_current_file = current_file
|
||||
|
||||
if "_colorspaceChange" not in current_file:
|
||||
copy_current_file = (
|
||||
split_current_file[0]
|
||||
+ "_colorspaceChange"
|
||||
+ split_current_file[1]
|
||||
)
|
||||
|
||||
try:
|
||||
# duplicate the file so the changes are applied only to the copy
|
||||
shutil.copyfile(current_file, copy_current_file)
|
||||
except shutil.Error:
|
||||
# in case the file already exists and it want to copy to the
|
||||
# same filewe need to do this trick
|
||||
# TEMP file name change
|
||||
copy_current_file_tmp = copy_current_file + "_tmp"
|
||||
# create TEMP file
|
||||
shutil.copyfile(current_file, copy_current_file_tmp)
|
||||
# remove original file
|
||||
os.remove(current_file)
|
||||
# copy TEMP back to original name
|
||||
shutil.copyfile(copy_current_file_tmp, copy_current_file)
|
||||
# remove the TEMP file as we dont need it
|
||||
os.remove(copy_current_file_tmp)
|
||||
|
||||
# use the code from bellow for changing xml hrox Attributes
|
||||
hiero_project_clrs.update({"name": os.path.basename(copy_current_file)})
|
||||
|
||||
# read HROX in as QDomSocument
|
||||
doc = _read_doc_from_path(copy_current_file)
|
||||
|
||||
# apply project colorspace properties
|
||||
_set_hrox_project_knobs(doc, **hiero_project_clrs)
|
||||
|
||||
# write QDomSocument back as HROX
|
||||
_write_doc_to_path(doc, copy_current_file)
|
||||
|
||||
# open the file as current project
|
||||
hiero.core.openProject(copy_current_file)
|
||||
|
||||
|
||||
def apply_colorspace_clips():
|
||||
project = get_current_project(remove_untitled=True)
|
||||
clips = project.clips()
|
||||
|
||||
# get presets for hiero
|
||||
presets = config.get_init_presets()
|
||||
colorspace = presets["colorspace"]
|
||||
hiero_clips_clrs = colorspace.get("hiero", {}).get("clips", {})
|
||||
|
||||
for clip in clips:
|
||||
clip_media_source_path = clip.mediaSource().firstpath()
|
||||
clip_name = clip.name()
|
||||
clip_colorspace = clip.sourceMediaColourTransform()
|
||||
|
||||
if "default" in clip_colorspace:
|
||||
continue
|
||||
|
||||
# check if any colorspace presets for read is mathing
|
||||
preset_clrsp = next((hiero_clips_clrs[k]
|
||||
for k in hiero_clips_clrs
|
||||
if bool(re.search(k, clip_media_source_path))),
|
||||
None)
|
||||
|
||||
if preset_clrsp:
|
||||
log.debug("Changing clip.path: {}".format(clip_media_source_path))
|
||||
log.info("Changing clip `{}` colorspace {} to {}".format(
|
||||
clip_name, clip_colorspace, preset_clrsp))
|
||||
# set the found preset to the clip
|
||||
clip.setSourceMediaColourTransform(preset_clrsp)
|
||||
|
||||
# save project after all is changed
|
||||
project.save()
|
||||
|
||||
|
||||
def is_overlapping(ti_test, ti_original, strict=False):
|
||||
covering_exp = bool(
|
||||
(ti_test.timelineIn() <= ti_original.timelineIn())
|
||||
and (ti_test.timelineOut() >= ti_original.timelineOut())
|
||||
)
|
||||
inside_exp = bool(
|
||||
(ti_test.timelineIn() >= ti_original.timelineIn())
|
||||
and (ti_test.timelineOut() <= ti_original.timelineOut())
|
||||
)
|
||||
overlaying_right_exp = bool(
|
||||
(ti_test.timelineIn() < ti_original.timelineOut())
|
||||
and (ti_test.timelineOut() >= ti_original.timelineOut())
|
||||
)
|
||||
overlaying_left_exp = bool(
|
||||
(ti_test.timelineOut() > ti_original.timelineIn())
|
||||
and (ti_test.timelineIn() <= ti_original.timelineIn())
|
||||
)
|
||||
|
||||
if not strict:
|
||||
return any((
|
||||
covering_exp,
|
||||
inside_exp,
|
||||
overlaying_right_exp,
|
||||
overlaying_left_exp
|
||||
))
|
||||
else:
|
||||
return covering_exp
|
||||
|
||||
|
||||
def get_sequence_pattern_and_padding(file):
|
||||
""" Return sequence pattern and padding from file
|
||||
|
||||
Attributes:
|
||||
file (string): basename form path
|
||||
|
||||
Example:
|
||||
Can find file.0001.ext, file.%02d.ext, file.####.ext
|
||||
|
||||
Return:
|
||||
string: any matching sequence patern
|
||||
int: padding of sequnce numbering
|
||||
"""
|
||||
foundall = re.findall(
|
||||
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
|
||||
if foundall:
|
||||
found = sorted(list(set(foundall[0])))[-1]
|
||||
|
||||
if "%" in found:
|
||||
padding = int(re.findall(r"\d+", found)[-1])
|
||||
else:
|
||||
padding = len(found)
|
||||
|
||||
return found, padding
|
||||
else:
|
||||
return None, None
|
||||
|
|
@ -5,12 +5,7 @@ from pype.api import Logger
|
|||
from avalon.api import Session
|
||||
from hiero.ui import findMenuAction
|
||||
|
||||
from .tags import add_tags_from_presets
|
||||
|
||||
from .lib import (
|
||||
reload_config,
|
||||
set_workfiles
|
||||
)
|
||||
from . import tags
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
|
|
@ -18,7 +13,7 @@ self = sys.modules[__name__]
|
|||
self._change_context_menu = None
|
||||
|
||||
|
||||
def _update_menu_task_label(*args):
|
||||
def update_menu_task_label(*args):
|
||||
"""Update the task label in Avalon menu to current session"""
|
||||
|
||||
object_name = self._change_context_menu
|
||||
|
|
@ -36,14 +31,17 @@ def _update_menu_task_label(*args):
|
|||
menu.setTitle(label)
|
||||
|
||||
|
||||
def install():
|
||||
def menu_install():
|
||||
"""
|
||||
Installing menu into Hiero
|
||||
|
||||
"""
|
||||
|
||||
from . import (
|
||||
publish, launch_workfiles_app, reload_config,
|
||||
apply_colorspace_project, apply_colorspace_clips
|
||||
)
|
||||
# here is the best place to add menu
|
||||
from avalon.tools import publish, cbloader
|
||||
from avalon.tools import cbloader, creator, sceneinventory
|
||||
from avalon.vendor.Qt import QtGui
|
||||
|
||||
menu_name = os.environ['AVALON_LABEL']
|
||||
|
|
@ -72,36 +70,45 @@ def install():
|
|||
|
||||
workfiles_action = menu.addAction("Work Files...")
|
||||
workfiles_action.setIcon(QtGui.QIcon("icons:Position.png"))
|
||||
workfiles_action.triggered.connect(set_workfiles)
|
||||
workfiles_action.triggered.connect(launch_workfiles_app)
|
||||
|
||||
default_tags_action = menu.addAction("Create Default Tags...")
|
||||
default_tags_action.setIcon(QtGui.QIcon("icons:Position.png"))
|
||||
default_tags_action.triggered.connect(add_tags_from_presets)
|
||||
default_tags_action.triggered.connect(tags.add_tags_to_workfile)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
publish_action = menu.addAction("Publish...")
|
||||
publish_action.setIcon(QtGui.QIcon("icons:Output.png"))
|
||||
publish_action.triggered.connect(
|
||||
lambda *args: publish.show(hiero.ui.mainWindow())
|
||||
lambda *args: publish(hiero.ui.mainWindow())
|
||||
)
|
||||
|
||||
creator_action = menu.addAction("Create...")
|
||||
creator_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
creator_action.triggered.connect(creator.show)
|
||||
|
||||
loader_action = menu.addAction("Load...")
|
||||
loader_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
loader_action.triggered.connect(cbloader.show)
|
||||
|
||||
sceneinventory_action = menu.addAction("Manage...")
|
||||
sceneinventory_action.setIcon(QtGui.QIcon("icons:CopyRectangle.png"))
|
||||
sceneinventory_action.triggered.connect(sceneinventory.show)
|
||||
menu.addSeparator()
|
||||
|
||||
reload_action = menu.addAction("Reload pipeline...")
|
||||
reload_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
|
||||
reload_action.triggered.connect(reload_config)
|
||||
|
||||
# Is this required?
|
||||
# hiero.ui.registerAction(context_label_action)
|
||||
# hiero.ui.registerAction(workfiles_action)
|
||||
# hiero.ui.registerAction(default_tags_action)
|
||||
# hiero.ui.registerAction(publish_action)
|
||||
# hiero.ui.registerAction(loader_action)
|
||||
# hiero.ui.registerAction(reload_action)
|
||||
menu.addSeparator()
|
||||
apply_colorspace_p_action = menu.addAction("Apply Colorspace Project...")
|
||||
apply_colorspace_p_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
|
||||
apply_colorspace_p_action.triggered.connect(apply_colorspace_project)
|
||||
|
||||
apply_colorspace_c_action = menu.addAction("Apply Colorspace Clips...")
|
||||
apply_colorspace_c_action.setIcon(QtGui.QIcon("icons:ColorAdd.png"))
|
||||
apply_colorspace_c_action.triggered.connect(apply_colorspace_clips)
|
||||
|
||||
self.context_label_action = context_label_action
|
||||
self.workfile_actions = workfiles_action
|
||||
300
pype/hosts/hiero/api/pipeline.py
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
"""
|
||||
Basic avalon integration
|
||||
"""
|
||||
import os
|
||||
import contextlib
|
||||
from collections import OrderedDict
|
||||
from avalon.tools import (
|
||||
workfiles,
|
||||
publish as _publish
|
||||
)
|
||||
from avalon.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon import api as avalon
|
||||
from avalon import schema
|
||||
from pyblish import api as pyblish
|
||||
from pype.api import Logger
|
||||
from . import lib, menu, events
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||
|
||||
# plugin paths
|
||||
API_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
HOST_DIR = os.path.dirname(API_DIR)
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish").replace("\\", "/")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "load").replace("\\", "/")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create").replace("\\", "/")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory").replace("\\", "/")
|
||||
|
||||
AVALON_CONTAINERS = ":AVALON_CONTAINERS"
|
||||
|
||||
|
||||
def install():
|
||||
"""
|
||||
Installing Hiero integration for avalon
|
||||
|
||||
Args:
|
||||
config (obj): avalon config module `pype` in our case, it is not
|
||||
used but required by avalon.api.install()
|
||||
|
||||
"""
|
||||
|
||||
# adding all events
|
||||
events.register_events()
|
||||
|
||||
log.info("Registering Hiero plug-ins..")
|
||||
pyblish.register_host("hiero")
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
# Disable all families except for the ones we explicitly want to see
|
||||
family_states = [
|
||||
"write",
|
||||
"review",
|
||||
"plate"
|
||||
]
|
||||
|
||||
avalon.data["familiesStateDefault"] = False
|
||||
avalon.data["familiesStateToggled"] = family_states
|
||||
|
||||
# install menu
|
||||
menu.menu_install()
|
||||
|
||||
# register hiero events
|
||||
events.register_hiero_events()
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""
|
||||
Uninstalling Hiero integration for avalon
|
||||
|
||||
"""
|
||||
log.info("Deregistering Hiero plug-ins..")
|
||||
pyblish.deregister_host("hiero")
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
|
||||
def containerise(track_item,
|
||||
name,
|
||||
namespace,
|
||||
context,
|
||||
loader=None,
|
||||
data=None):
|
||||
"""Bundle Hiero's object into an assembly and imprint it with metadata
|
||||
|
||||
Containerisation enables a tracking of version, author and origin
|
||||
for loaded assets.
|
||||
|
||||
Arguments:
|
||||
track_item (hiero.core.TrackItem): object to imprint as container
|
||||
name (str): Name of resulting assembly
|
||||
namespace (str): Namespace under which to host container
|
||||
context (dict): Asset information
|
||||
loader (str, optional): Name of node used to produce this container.
|
||||
|
||||
Returns:
|
||||
track_item (hiero.core.TrackItem): containerised object
|
||||
|
||||
"""
|
||||
|
||||
data_imprint = OrderedDict({
|
||||
"schema": "avalon-core:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
"name": str(name),
|
||||
"namespace": str(namespace),
|
||||
"loader": str(loader),
|
||||
"representation": str(context["representation"]["_id"]),
|
||||
})
|
||||
|
||||
if data:
|
||||
for k, v in data.items():
|
||||
data_imprint.update({k: v})
|
||||
|
||||
log.debug("_ data_imprint: {}".format(data_imprint))
|
||||
lib.set_track_item_pype_tag(track_item, data_imprint)
|
||||
|
||||
return track_item
|
||||
|
||||
|
||||
def ls():
|
||||
"""List available containers.
|
||||
|
||||
This function is used by the Container Manager in Nuke. You'll
|
||||
need to implement a for-loop that then *yields* one Container at
|
||||
a time.
|
||||
|
||||
See the `container.json` schema for details on how it should look,
|
||||
and the Maya equivalent, which is in `avalon.maya.pipeline`
|
||||
"""
|
||||
|
||||
# get all track items from current timeline
|
||||
all_track_items = lib.get_track_items()
|
||||
|
||||
for track_item in all_track_items:
|
||||
container = parse_container(track_item)
|
||||
if container:
|
||||
yield container
|
||||
|
||||
|
||||
def parse_container(track_item, validate=True):
|
||||
"""Return container data from track_item's pype tag.
|
||||
|
||||
Args:
|
||||
track_item (hiero.core.TrackItem): A containerised track item.
|
||||
validate (bool)[optional]: validating with avalon scheme
|
||||
|
||||
Returns:
|
||||
dict: The container schema data for input containerized track item.
|
||||
|
||||
"""
|
||||
# convert tag metadata to normal keys names
|
||||
data = lib.get_track_item_pype_data(track_item)
|
||||
|
||||
if validate and data and data.get("schema"):
|
||||
schema.validate(data)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
return
|
||||
|
||||
# If not all required data return the empty container
|
||||
required = ['schema', 'id', 'name',
|
||||
'namespace', 'loader', 'representation']
|
||||
|
||||
if not all(key in data for key in required):
|
||||
return
|
||||
|
||||
container = {key: data[key] for key in required}
|
||||
|
||||
container["objectName"] = track_item.name()
|
||||
|
||||
# Store reference to the node object
|
||||
container["_track_item"] = track_item
|
||||
|
||||
return container
|
||||
|
||||
|
||||
def update_container(track_item, data=None):
|
||||
"""Update container data to input track_item's pype tag.
|
||||
|
||||
Args:
|
||||
track_item (hiero.core.TrackItem): A containerised track item.
|
||||
data (dict)[optional]: dictionery with data to be updated
|
||||
|
||||
Returns:
|
||||
bool: True if container was updated correctly
|
||||
|
||||
"""
|
||||
data = data or dict()
|
||||
|
||||
container = lib.get_track_item_pype_data(track_item)
|
||||
|
||||
for _key, _value in container.items():
|
||||
try:
|
||||
container[_key] = data[_key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
log.info("Updating container: `{}`".format(track_item.name()))
|
||||
return bool(lib.set_track_item_pype_tag(track_item, container))
|
||||
|
||||
|
||||
def launch_workfiles_app(*args):
|
||||
''' Wrapping function for workfiles launcher '''
|
||||
|
||||
workdir = os.environ["AVALON_WORKDIR"]
|
||||
|
||||
# show workfile gui
|
||||
workfiles.show(workdir)
|
||||
|
||||
|
||||
def publish(parent):
|
||||
"""Shorthand to publish from within host"""
|
||||
return _publish.show(parent)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
"""Maintain selection during context
|
||||
|
||||
Example:
|
||||
>>> with maintained_selection():
|
||||
... for track_item in track_items:
|
||||
... < do some stuff >
|
||||
"""
|
||||
from .lib import (
|
||||
set_selected_track_items,
|
||||
get_selected_track_items
|
||||
)
|
||||
previous_selection = get_selected_track_items()
|
||||
reset_selection()
|
||||
try:
|
||||
# do the operation
|
||||
yield
|
||||
finally:
|
||||
reset_selection()
|
||||
set_selected_track_items(previous_selection)
|
||||
|
||||
|
||||
def reset_selection():
|
||||
"""Deselect all selected nodes
|
||||
"""
|
||||
from .lib import set_selected_track_items
|
||||
set_selected_track_items([])
|
||||
|
||||
|
||||
def reload_config():
|
||||
"""Attempt to reload pipeline at run-time.
|
||||
|
||||
CAUTION: This is primarily for development and debugging purposes.
|
||||
|
||||
"""
|
||||
import importlib
|
||||
|
||||
for module in (
|
||||
"avalon",
|
||||
"avalon.lib",
|
||||
"avalon.pipeline",
|
||||
"pyblish",
|
||||
"pypeapp",
|
||||
"{}.api".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.lib".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.menu".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.tags".format(AVALON_CONFIG)
|
||||
):
|
||||
log.info("Reloading module: {}...".format(module))
|
||||
try:
|
||||
module = importlib.import_module(module)
|
||||
import imp
|
||||
imp.reload(module)
|
||||
except Exception as e:
|
||||
log.warning("Cannot reload module: {}".format(e))
|
||||
importlib.reload(module)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
from pype.hosts.hiero.api import (
|
||||
get_track_item_pype_tag,
|
||||
set_publish_attribute
|
||||
)
|
||||
|
||||
# Whether instances should be passthrough based on new value
|
||||
track_item = instance.data["item"]
|
||||
tag = get_track_item_pype_tag(track_item)
|
||||
set_publish_attribute(tag, new_value)
|
||||
909
pype/hosts/hiero/api/plugin.py
Normal file
|
|
@ -0,0 +1,909 @@
|
|||
import re
|
||||
import os
|
||||
import hiero
|
||||
from Qt import QtWidgets, QtCore
|
||||
from avalon.vendor import qargparse
|
||||
import avalon.api as avalon
|
||||
import pype.api as pype
|
||||
from . import lib
|
||||
|
||||
log = pype.Logger().get_logger(__name__)
|
||||
|
||||
|
||||
def load_stylesheet():
|
||||
path = os.path.join(os.path.dirname(__file__), "style.css")
|
||||
if not os.path.exists(path):
|
||||
log.warning("Unable to load stylesheet, file not found in resources")
|
||||
return ""
|
||||
|
||||
with open(path, "r") as file_stream:
|
||||
stylesheet = file_stream.read()
|
||||
return stylesheet
|
||||
|
||||
|
||||
class CreatorWidget(QtWidgets.QDialog):
|
||||
|
||||
# output items
|
||||
items = dict()
|
||||
|
||||
def __init__(self, name, info, ui_inputs, parent=None):
|
||||
super(CreatorWidget, self).__init__(parent)
|
||||
|
||||
self.setObjectName(name)
|
||||
|
||||
self.setWindowFlags(
|
||||
QtCore.Qt.Window
|
||||
| QtCore.Qt.CustomizeWindowHint
|
||||
| QtCore.Qt.WindowTitleHint
|
||||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
self.setWindowTitle(name or "Pype Creator Input")
|
||||
self.resize(500, 700)
|
||||
|
||||
# Where inputs and labels are set
|
||||
self.content_widget = [QtWidgets.QWidget(self)]
|
||||
top_layout = QtWidgets.QFormLayout(self.content_widget[0])
|
||||
top_layout.setObjectName("ContentLayout")
|
||||
top_layout.addWidget(Spacer(5, self))
|
||||
|
||||
# first add widget tag line
|
||||
top_layout.addWidget(QtWidgets.QLabel(info))
|
||||
|
||||
# main dynamic layout
|
||||
self.scroll_area = QtWidgets.QScrollArea(self, widgetResizable=True)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAsNeeded)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.scroll_area.setHorizontalScrollBarPolicy(
|
||||
QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
|
||||
self.content_widget.append(self.scroll_area)
|
||||
|
||||
scroll_widget = QtWidgets.QWidget(self)
|
||||
in_scroll_area = QtWidgets.QVBoxLayout(scroll_widget)
|
||||
self.content_layout = [in_scroll_area]
|
||||
|
||||
# add preset data into input widget layout
|
||||
self.items = self.populate_widgets(ui_inputs)
|
||||
self.scroll_area.setWidget(scroll_widget)
|
||||
|
||||
# Confirmation buttons
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel")
|
||||
btns_layout.addWidget(cancel_btn)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok")
|
||||
btns_layout.addWidget(ok_btn)
|
||||
|
||||
# Main layout of the dialog
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# adding content widget
|
||||
for w in self.content_widget:
|
||||
main_layout.addWidget(w)
|
||||
|
||||
main_layout.addWidget(btns_widget)
|
||||
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
||||
stylesheet = load_stylesheet()
|
||||
self.setStyleSheet(stylesheet)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
self.result = self.value(self.items)
|
||||
self.close()
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self.result = None
|
||||
self.close()
|
||||
|
||||
def value(self, data, new_data=None):
|
||||
new_data = new_data or dict()
|
||||
for k, v in data.items():
|
||||
new_data[k] = {
|
||||
"target": None,
|
||||
"value": None
|
||||
}
|
||||
if v["type"] == "dict":
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = self.value(v["value"])
|
||||
if v["type"] == "section":
|
||||
new_data.pop(k)
|
||||
new_data = self.value(v["value"], new_data)
|
||||
elif getattr(v["value"], "currentText", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].currentText()
|
||||
elif getattr(v["value"], "isChecked", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].isChecked()
|
||||
elif getattr(v["value"], "value", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].value()
|
||||
elif getattr(v["value"], "text", None):
|
||||
new_data[k]["target"] = v["target"]
|
||||
new_data[k]["value"] = v["value"].text()
|
||||
|
||||
return new_data
|
||||
|
||||
def camel_case_split(self, text):
|
||||
matches = re.finditer(
|
||||
'.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', text)
|
||||
return " ".join([str(m.group(0)).capitalize() for m in matches])
|
||||
|
||||
def create_row(self, layout, type, text, **kwargs):
|
||||
# get type attribute from qwidgets
|
||||
attr = getattr(QtWidgets, type)
|
||||
|
||||
# convert label text to normal capitalized text with spaces
|
||||
label_text = self.camel_case_split(text)
|
||||
|
||||
# assign the new text to lable widget
|
||||
label = QtWidgets.QLabel(label_text)
|
||||
label.setObjectName("LineLabel")
|
||||
|
||||
# create attribute name text strip of spaces
|
||||
attr_name = text.replace(" ", "")
|
||||
|
||||
# create attribute and assign default values
|
||||
setattr(
|
||||
self,
|
||||
attr_name,
|
||||
attr(parent=self))
|
||||
|
||||
# assign the created attribute to variable
|
||||
item = getattr(self, attr_name)
|
||||
for func, val in kwargs.items():
|
||||
if getattr(item, func):
|
||||
func_attr = getattr(item, func)
|
||||
func_attr(val)
|
||||
|
||||
# add to layout
|
||||
layout.addRow(label, item)
|
||||
|
||||
return item
|
||||
|
||||
def populate_widgets(self, data, content_layout=None):
|
||||
"""
|
||||
Populate widget from input dict.
|
||||
|
||||
Each plugin has its own set of widget rows defined in dictionary
|
||||
each row values should have following keys: `type`, `target`,
|
||||
`label`, `order`, `value` and optionally also `toolTip`.
|
||||
|
||||
Args:
|
||||
data (dict): widget rows or organized groups defined
|
||||
by types `dict` or `section`
|
||||
content_layout (QtWidgets.QFormLayout)[optional]: used when nesting
|
||||
|
||||
Returns:
|
||||
dict: redefined data dict updated with created widgets
|
||||
|
||||
"""
|
||||
|
||||
content_layout = content_layout or self.content_layout[-1]
|
||||
# fix order of process by defined order value
|
||||
ordered_keys = data.keys()
|
||||
for k, v in data.items():
|
||||
try:
|
||||
# try removing a key from index which should
|
||||
# be filled with new
|
||||
ordered_keys.pop(v["order"])
|
||||
except IndexError:
|
||||
pass
|
||||
# add key into correct order
|
||||
ordered_keys.insert(v["order"], k)
|
||||
|
||||
# process ordered
|
||||
for k in ordered_keys:
|
||||
v = data[k]
|
||||
tool_tip = v.get("toolTip", "")
|
||||
if v["type"] == "dict":
|
||||
# adding spacer between sections
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
self.content_layout[-1].setObjectName("sectionHeadline")
|
||||
|
||||
headline = QtWidgets.QVBoxLayout(self.content_layout[-1])
|
||||
headline.addWidget(Spacer(20, self))
|
||||
headline.addWidget(QtWidgets.QLabel(v["label"]))
|
||||
|
||||
# adding nested layout with label
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
self.content_layout[-1].setObjectName("sectionContent")
|
||||
|
||||
nested_content_layout = QtWidgets.QFormLayout(
|
||||
self.content_layout[-1])
|
||||
nested_content_layout.setObjectName("NestedContentLayout")
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
|
||||
# add nested key as label
|
||||
data[k]["value"] = self.populate_widgets(
|
||||
v["value"], nested_content_layout)
|
||||
|
||||
if v["type"] == "section":
|
||||
# adding spacer between sections
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
self.content_layout[-1].setObjectName("sectionHeadline")
|
||||
|
||||
headline = QtWidgets.QVBoxLayout(self.content_layout[-1])
|
||||
headline.addWidget(Spacer(20, self))
|
||||
headline.addWidget(QtWidgets.QLabel(v["label"]))
|
||||
|
||||
# adding nested layout with label
|
||||
self.content_layout.append(QtWidgets.QWidget(self))
|
||||
self.content_layout[-1].setObjectName("sectionContent")
|
||||
|
||||
nested_content_layout = QtWidgets.QFormLayout(
|
||||
self.content_layout[-1])
|
||||
nested_content_layout.setObjectName("NestedContentLayout")
|
||||
content_layout.addWidget(self.content_layout[-1])
|
||||
|
||||
# add nested key as label
|
||||
data[k]["value"] = self.populate_widgets(
|
||||
v["value"], nested_content_layout)
|
||||
|
||||
elif v["type"] == "QLineEdit":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QLineEdit", v["label"],
|
||||
setText=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QComboBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QComboBox", v["label"],
|
||||
addItems=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QCheckBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QCheckBox", v["label"],
|
||||
setChecked=v["value"], setToolTip=tool_tip)
|
||||
elif v["type"] == "QSpinBox":
|
||||
data[k]["value"] = self.create_row(
|
||||
content_layout, "QSpinBox", v["label"],
|
||||
setValue=v["value"], setMaximum=10000, setToolTip=tool_tip)
|
||||
return data
|
||||
|
||||
|
||||
class Spacer(QtWidgets.QWidget):
|
||||
def __init__(self, height, *args, **kwargs):
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setFixedHeight(height)
|
||||
|
||||
real_spacer = QtWidgets.QWidget(self)
|
||||
real_spacer.setObjectName("Spacer")
|
||||
real_spacer.setFixedHeight(height)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(real_spacer)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def get_reference_node_parents(ref):
|
||||
"""Return all parent reference nodes of reference node
|
||||
|
||||
Args:
|
||||
ref (str): reference node.
|
||||
|
||||
Returns:
|
||||
list: The upstream parent reference nodes.
|
||||
|
||||
"""
|
||||
parents = []
|
||||
return parents
|
||||
|
||||
|
||||
class SequenceLoader(avalon.Loader):
|
||||
"""A basic SequenceLoader for Resolve
|
||||
|
||||
This will implement the basic behavior for a loader to inherit from that
|
||||
will containerize the reference and will implement the `remove` and
|
||||
`update` logic.
|
||||
|
||||
"""
|
||||
|
||||
options = [
|
||||
qargparse.Boolean(
|
||||
"handles",
|
||||
label="Include handles",
|
||||
default=0,
|
||||
help="Load with handles or without?"
|
||||
),
|
||||
qargparse.Choice(
|
||||
"load_to",
|
||||
label="Where to load clips",
|
||||
items=[
|
||||
"Current timeline",
|
||||
"New timeline"
|
||||
],
|
||||
default="Current timeline",
|
||||
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 orignal timing?"
|
||||
)
|
||||
]
|
||||
|
||||
def load(
|
||||
self,
|
||||
context,
|
||||
name=None,
|
||||
namespace=None,
|
||||
options=None
|
||||
):
|
||||
pass
|
||||
|
||||
def update(self, container, representation):
|
||||
"""Update an existing `container`
|
||||
"""
|
||||
pass
|
||||
|
||||
def remove(self, container):
|
||||
"""Remove an existing `container`
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class ClipLoader:
|
||||
|
||||
active_bin = None
|
||||
data = dict()
|
||||
|
||||
def __init__(self, cls, context, **options):
|
||||
""" Initialize object
|
||||
|
||||
Arguments:
|
||||
cls (avalon.api.Loader): plugin object
|
||||
context (dict): loader plugin context
|
||||
options (dict)[optional]: possible keys:
|
||||
projectBinPath: "path/to/binItem"
|
||||
|
||||
"""
|
||||
self.__dict__.update(cls.__dict__)
|
||||
self.context = context
|
||||
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)
|
||||
# try to get value from options or evaluate key value for `load_how`
|
||||
self.sequencial_load = options.get("sequencially") or bool(
|
||||
"Sequentially in order" in options.get("load_how", ""))
|
||||
# try to get value from options or evaluate key value for `load_to`
|
||||
self.new_sequence = options.get("newSequence") or bool(
|
||||
"New timeline" in options.get("load_to", ""))
|
||||
|
||||
assert self._populate_data(), str(
|
||||
"Cannot Load selected data, look into database "
|
||||
"or call your supervisor")
|
||||
|
||||
# inject asset data to representation dict
|
||||
self._get_asset_data()
|
||||
log.debug("__init__ self.data: `{}`".format(self.data))
|
||||
|
||||
# add active components to class
|
||||
if self.new_sequence:
|
||||
if options.get("sequence"):
|
||||
# if multiselection is set then use options sequence
|
||||
self.active_sequence = options["sequence"]
|
||||
else:
|
||||
# create new sequence
|
||||
self.active_sequence = lib.get_current_sequence(new=True)
|
||||
self.active_sequence.setFramerate(
|
||||
hiero.core.TimeBase.fromString(
|
||||
str(self.data["assetData"]["fps"])))
|
||||
else:
|
||||
self.active_sequence = lib.get_current_sequence()
|
||||
|
||||
if options.get("track"):
|
||||
# if multiselection is set then use options track
|
||||
self.active_track = options["track"]
|
||||
else:
|
||||
self.active_track = lib.get_current_track(
|
||||
self.active_sequence, self.data["track_name"])
|
||||
|
||||
def _populate_data(self):
|
||||
""" Gets context and convert it to self.data
|
||||
data structure:
|
||||
{
|
||||
"name": "assetName_subsetName_representationName"
|
||||
"path": "path/to/file/created/by/get_repr..",
|
||||
"binPath": "projectBinPath",
|
||||
}
|
||||
"""
|
||||
# create name
|
||||
repr = self.context["representation"]
|
||||
repr_cntx = repr["context"]
|
||||
asset = str(repr_cntx["asset"])
|
||||
subset = str(repr_cntx["subset"])
|
||||
representation = str(repr_cntx["representation"])
|
||||
self.data["clip_name"] = "_".join([asset, subset, representation])
|
||||
self.data["track_name"] = "_".join([subset, representation])
|
||||
self.data["versionData"] = self.context["version"]["data"]
|
||||
# gets file path
|
||||
file = self.fname
|
||||
if not file:
|
||||
repr_id = repr["_id"]
|
||||
log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return None
|
||||
self.data["path"] = file.replace("\\", "/")
|
||||
|
||||
# convert to hashed path
|
||||
if repr_cntx.get("frame"):
|
||||
self._fix_path_hashes()
|
||||
|
||||
# solve project bin structure path
|
||||
hierarchy = str("/".join((
|
||||
"Loader",
|
||||
repr_cntx["hierarchy"].replace("\\", "/"),
|
||||
asset
|
||||
)))
|
||||
|
||||
self.data["binPath"] = hierarchy
|
||||
|
||||
return True
|
||||
|
||||
def _fix_path_hashes(self):
|
||||
""" Convert file path where it is needed padding with hashes
|
||||
"""
|
||||
file = self.data["path"]
|
||||
if "#" not in file:
|
||||
frame = self.context["representation"]["context"].get("frame")
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
self.data["path"] = file
|
||||
|
||||
def _get_asset_data(self):
|
||||
""" Get all available asset data
|
||||
|
||||
joint `data` key with asset.data dict into the representaion
|
||||
|
||||
"""
|
||||
asset_name = self.context["representation"]["context"]["asset"]
|
||||
self.data["assetData"] = pype.get_asset(asset_name)["data"]
|
||||
|
||||
def _make_track_item(self, source_bin_item, audio=False):
|
||||
""" Create track item with """
|
||||
|
||||
clip = source_bin_item.activeItem()
|
||||
|
||||
# add to track as clip item
|
||||
if not audio:
|
||||
track_item = hiero.core.TrackItem(
|
||||
self.data["clip_name"], hiero.core.TrackItem.kVideo)
|
||||
else:
|
||||
track_item = hiero.core.TrackItem(
|
||||
self.data["clip_name"], hiero.core.TrackItem.kAudio)
|
||||
|
||||
track_item.setSource(clip)
|
||||
track_item.setSourceIn(self.handle_start)
|
||||
track_item.setTimelineIn(self.timeline_in)
|
||||
track_item.setSourceOut(self.media_duration - self.handle_end)
|
||||
track_item.setTimelineOut(self.timeline_out)
|
||||
track_item.setPlaybackSpeed(1)
|
||||
self.active_track.addTrackItem(track_item)
|
||||
|
||||
return track_item
|
||||
|
||||
def load(self):
|
||||
# create project bin for the media to be imported into
|
||||
self.active_bin = lib.create_bin(self.data["binPath"])
|
||||
|
||||
# create mediaItem in active project bin
|
||||
# create clip media
|
||||
self.media = hiero.core.MediaSource(self.data["path"])
|
||||
self.media_duration = int(self.media.duration())
|
||||
|
||||
# get handles
|
||||
self.handle_start = self.data["versionData"].get("handleStart")
|
||||
self.handle_end = self.data["versionData"].get("handleEnd")
|
||||
if self.handle_start is None:
|
||||
self.handle_start = int(self.data["assetData"]["handleStart"])
|
||||
if self.handle_end is None:
|
||||
self.handle_end = int(self.data["assetData"]["handleEnd"])
|
||||
|
||||
if self.sequencial_load:
|
||||
last_track_item = lib.get_track_items(
|
||||
sequence_name=self.active_sequence.name(),
|
||||
track_name=self.active_track.name())
|
||||
if len(last_track_item) == 0:
|
||||
last_timeline_out = 0
|
||||
else:
|
||||
last_track_item = last_track_item[-1]
|
||||
last_timeline_out = int(last_track_item.timelineOut()) + 1
|
||||
self.timeline_in = last_timeline_out
|
||||
self.timeline_out = last_timeline_out + int(
|
||||
self.data["assetData"]["clipOut"]
|
||||
- self.data["assetData"]["clipIn"])
|
||||
else:
|
||||
self.timeline_in = int(self.data["assetData"]["clipIn"])
|
||||
self.timeline_out = int(self.data["assetData"]["clipOut"])
|
||||
|
||||
# check if slate is included
|
||||
# either in version data families or by calculating frame diff
|
||||
slate_on = next(
|
||||
# check iterate if slate is in families
|
||||
(f for f in self.context["version"]["data"]["families"]
|
||||
if "slate" in f),
|
||||
# if nothing was found then use default None
|
||||
# so other bool could be used
|
||||
None) or bool(((
|
||||
# put together duration of clip attributes
|
||||
self.timeline_out - self.timeline_in + 1) \
|
||||
+ self.handle_start \
|
||||
+ self.handle_end
|
||||
# and compare it with meda duration
|
||||
) > self.media_duration)
|
||||
|
||||
print("__ slate_on: `{}`".format(slate_on))
|
||||
|
||||
# if slate is on then remove the slate frame from begining
|
||||
if slate_on:
|
||||
self.media_duration -= 1
|
||||
self.handle_start += 1
|
||||
|
||||
# create Clip from Media
|
||||
clip = hiero.core.Clip(self.media)
|
||||
clip.setName(self.data["clip_name"])
|
||||
|
||||
# add Clip to bin if not there yet
|
||||
if self.data["clip_name"] not in [
|
||||
b.name() for b in self.active_bin.items()]:
|
||||
bin_item = hiero.core.BinItem(clip)
|
||||
self.active_bin.addItem(bin_item)
|
||||
|
||||
# just make sure the clip is created
|
||||
# there were some cases were hiero was not creating it
|
||||
source_bin_item = None
|
||||
for item in self.active_bin.items():
|
||||
if self.data["clip_name"] in item.name():
|
||||
source_bin_item = item
|
||||
if not source_bin_item:
|
||||
log.warning("Problem with created Source clip: `{}`".format(
|
||||
self.data["clip_name"]))
|
||||
|
||||
# include handles
|
||||
if self.with_handles:
|
||||
self.timeline_in -= self.handle_start
|
||||
self.timeline_out += self.handle_end
|
||||
self.handle_start = 0
|
||||
self.handle_end = 0
|
||||
|
||||
# make track item from source in bin as item
|
||||
track_item = self._make_track_item(source_bin_item)
|
||||
|
||||
log.info("Loading clips: `{}`".format(self.data["clip_name"]))
|
||||
return track_item
|
||||
|
||||
|
||||
class Creator(avalon.Creator):
|
||||
"""Creator class wrapper
|
||||
"""
|
||||
clip_color = "Purple"
|
||||
rename_index = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
import pype.hosts.hiero.api as phiero
|
||||
super(Creator, self).__init__(*args, **kwargs)
|
||||
self.presets = pype.get_current_project_settings()[
|
||||
"hiero"]["create"].get(self.__class__.__name__, {})
|
||||
|
||||
# adding basic current context resolve objects
|
||||
self.project = phiero.get_current_project()
|
||||
self.sequence = phiero.get_current_sequence()
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
self.selected = phiero.get_track_items(selected=True)
|
||||
else:
|
||||
self.selected = phiero.get_track_items()
|
||||
|
||||
self.widget = CreatorWidget
|
||||
|
||||
|
||||
class PublishClip:
|
||||
"""
|
||||
Convert a track item to publishable instance
|
||||
|
||||
Args:
|
||||
track_item (hiero.core.TrackItem): hiero track item object
|
||||
kwargs (optional): additional data needed for rename=True (presets)
|
||||
|
||||
Returns:
|
||||
hiero.core.TrackItem: hiero track item object with pype tag
|
||||
"""
|
||||
vertical_clip_match = dict()
|
||||
tag_data = dict()
|
||||
types = {
|
||||
"shot": "shot",
|
||||
"folder": "folder",
|
||||
"episode": "episode",
|
||||
"sequence": "sequence",
|
||||
"track": "sequence",
|
||||
}
|
||||
|
||||
# parents search patern
|
||||
parents_search_patern = r"\{([a-z]*?)\}"
|
||||
|
||||
# default templates for non-ui use
|
||||
rename_default = False
|
||||
hierarchy_default = "{_folder_}/{_sequence_}/{_track_}"
|
||||
clip_name_default = "shot_{_trackIndex_:0>3}_{_clipIndex_:0>4}"
|
||||
subset_name_default = "<track_name>"
|
||||
review_track_default = "< none >"
|
||||
subset_family_default = "plate"
|
||||
count_from_default = 10
|
||||
count_steps_default = 10
|
||||
vertical_sync_default = False
|
||||
driving_layer_default = ""
|
||||
|
||||
def __init__(self, cls, track_item, **kwargs):
|
||||
# populate input cls attribute onto self.[attr]
|
||||
self.__dict__.update(cls.__dict__)
|
||||
|
||||
# get main parent objects
|
||||
self.track_item = track_item
|
||||
sequence_name = lib.get_current_sequence().name()
|
||||
self.sequence_name = str(sequence_name).replace(" ", "_")
|
||||
|
||||
# track item (clip) main attributes
|
||||
self.ti_name = track_item.name()
|
||||
self.ti_index = int(track_item.eventNumber())
|
||||
|
||||
# get track name and index
|
||||
track_name = track_item.parent().name()
|
||||
self.track_name = str(track_name).replace(" ", "_")
|
||||
self.track_index = int(track_item.parent().trackIndex())
|
||||
|
||||
# adding tag.family into tag
|
||||
if kwargs.get("avalon"):
|
||||
self.tag_data.update(kwargs["avalon"])
|
||||
|
||||
# adding ui inputs if any
|
||||
self.ui_inputs = kwargs.get("ui_inputs", {})
|
||||
|
||||
# populate default data before we get other attributes
|
||||
self._populate_track_item_default_data()
|
||||
|
||||
# use all populated default data to create all important attributes
|
||||
self._populate_attributes()
|
||||
|
||||
# create parents with correct types
|
||||
self._create_parents()
|
||||
|
||||
def convert(self):
|
||||
# solve track item data and add them to tag data
|
||||
self._convert_to_tag_data()
|
||||
|
||||
# if track name is in review track name and also if driving track name
|
||||
# is not in review track name: skip tag creation
|
||||
if (self.track_name in self.review_layer) and (
|
||||
self.driving_layer not in self.review_layer):
|
||||
return
|
||||
|
||||
# deal with clip name
|
||||
new_name = self.tag_data.pop("newClipName")
|
||||
|
||||
if self.rename:
|
||||
# rename track item
|
||||
self.track_item.setName(new_name)
|
||||
self.tag_data["asset"] = new_name
|
||||
else:
|
||||
self.tag_data["asset"] = self.ti_name
|
||||
|
||||
# create pype tag on track_item and add data
|
||||
lib.imprint(self.track_item, self.tag_data)
|
||||
|
||||
return self.track_item
|
||||
|
||||
def _populate_track_item_default_data(self):
|
||||
""" Populate default formating data from track item. """
|
||||
|
||||
self.track_item_default_data = {
|
||||
"_folder_": "shots",
|
||||
"_sequence_": self.sequence_name,
|
||||
"_track_": self.track_name,
|
||||
"_clip_": self.ti_name,
|
||||
"_trackIndex_": self.track_index,
|
||||
"_clipIndex_": self.ti_index
|
||||
}
|
||||
|
||||
def _populate_attributes(self):
|
||||
""" Populate main object attributes. """
|
||||
# track item frame range and parent track name for vertical sync check
|
||||
self.clip_in = int(self.track_item.timelineIn())
|
||||
self.clip_out = int(self.track_item.timelineOut())
|
||||
|
||||
# define ui inputs if non gui mode was used
|
||||
self.shot_num = self.ti_index
|
||||
log.debug(
|
||||
"____ self.shot_num: {}".format(self.shot_num))
|
||||
|
||||
# ui_inputs data or default values if gui was not used
|
||||
self.rename = self.ui_inputs.get(
|
||||
"clipRename", {}).get("value") or self.rename_default
|
||||
self.clip_name = self.ui_inputs.get(
|
||||
"clipName", {}).get("value") or self.clip_name_default
|
||||
self.hierarchy = self.ui_inputs.get(
|
||||
"hierarchy", {}).get("value") or self.hierarchy_default
|
||||
self.hierarchy_data = self.ui_inputs.get(
|
||||
"hierarchyData", {}).get("value") or \
|
||||
self.track_item_default_data.copy()
|
||||
self.count_from = self.ui_inputs.get(
|
||||
"countFrom", {}).get("value") or self.count_from_default
|
||||
self.count_steps = self.ui_inputs.get(
|
||||
"countSteps", {}).get("value") or self.count_steps_default
|
||||
self.subset_name = self.ui_inputs.get(
|
||||
"subsetName", {}).get("value") or self.subset_name_default
|
||||
self.subset_family = self.ui_inputs.get(
|
||||
"subsetFamily", {}).get("value") or self.subset_family_default
|
||||
self.vertical_sync = self.ui_inputs.get(
|
||||
"vSyncOn", {}).get("value") or self.vertical_sync_default
|
||||
self.driving_layer = self.ui_inputs.get(
|
||||
"vSyncTrack", {}).get("value") or self.driving_layer_default
|
||||
self.review_track = self.ui_inputs.get(
|
||||
"reviewTrack", {}).get("value") or self.review_track_default
|
||||
self.audio = self.ui_inputs.get(
|
||||
"audio", {}).get("value") or False
|
||||
|
||||
# build subset name from layer name
|
||||
if self.subset_name == "<track_name>":
|
||||
self.subset_name = self.track_name
|
||||
|
||||
# create subset for publishing
|
||||
self.subset = self.subset_family + self.subset_name.capitalize()
|
||||
|
||||
def _replace_hash_to_expression(self, name, text):
|
||||
""" Replace hash with number in correct padding. """
|
||||
_spl = text.split("#")
|
||||
_len = (len(_spl) - 1)
|
||||
_repl = "{{{0}:0>{1}}}".format(name, _len)
|
||||
new_text = text.replace(("#" * _len), _repl)
|
||||
return new_text
|
||||
|
||||
def _convert_to_tag_data(self):
|
||||
""" Convert internal data to tag data.
|
||||
|
||||
Populating the tag data into internal variable self.tag_data
|
||||
"""
|
||||
# define vertical sync attributes
|
||||
master_layer = True
|
||||
self.review_layer = ""
|
||||
if self.vertical_sync:
|
||||
# check if track name is not in driving layer
|
||||
if self.track_name not in self.driving_layer:
|
||||
# if it is not then define vertical sync as None
|
||||
master_layer = False
|
||||
|
||||
# increasing steps by index of rename iteration
|
||||
self.count_steps *= self.rename_index
|
||||
|
||||
hierarchy_formating_data = dict()
|
||||
_data = self.track_item_default_data.copy()
|
||||
if self.ui_inputs:
|
||||
# adding tag metadata from ui
|
||||
for _k, _v in self.ui_inputs.items():
|
||||
if _v["target"] == "tag":
|
||||
self.tag_data[_k] = _v["value"]
|
||||
|
||||
# driving layer is set as positive match
|
||||
if master_layer or self.vertical_sync:
|
||||
# mark review layer
|
||||
if self.review_track and (
|
||||
self.review_track not in self.review_track_default):
|
||||
# if review layer is defined and not the same as defalut
|
||||
self.review_layer = self.review_track
|
||||
# shot num calculate
|
||||
if self.rename_index == 0:
|
||||
self.shot_num = self.count_from
|
||||
else:
|
||||
self.shot_num = self.count_from + self.count_steps
|
||||
|
||||
# clip name sequence number
|
||||
_data.update({"shot": self.shot_num})
|
||||
|
||||
# solve # in test to pythonic expression
|
||||
for _k, _v in self.hierarchy_data.items():
|
||||
if "#" not in _v["value"]:
|
||||
continue
|
||||
self.hierarchy_data[
|
||||
_k]["value"] = self._replace_hash_to_expression(
|
||||
_k, _v["value"])
|
||||
|
||||
# fill up pythonic expresisons in hierarchy data
|
||||
for k, _v in self.hierarchy_data.items():
|
||||
hierarchy_formating_data[k] = _v["value"].format(**_data)
|
||||
else:
|
||||
# if no gui mode then just pass default data
|
||||
hierarchy_formating_data = self.hierarchy_data
|
||||
|
||||
tag_hierarchy_data = self._solve_tag_hierarchy_data(
|
||||
hierarchy_formating_data
|
||||
)
|
||||
|
||||
tag_hierarchy_data.update({"masterLayer": True})
|
||||
if master_layer and self.vertical_sync:
|
||||
self.vertical_clip_match.update({
|
||||
(self.clip_in, self.clip_out): tag_hierarchy_data
|
||||
})
|
||||
|
||||
if not master_layer and self.vertical_sync:
|
||||
# driving layer is set as negative match
|
||||
for (_in, _out), master_data in self.vertical_clip_match.items():
|
||||
master_data.update({"masterLayer": False})
|
||||
if _in == self.clip_in and _out == self.clip_out:
|
||||
data_subset = master_data["subset"]
|
||||
# add track index in case duplicity of names in master data
|
||||
if self.subset in data_subset:
|
||||
master_data["subset"] = self.subset + str(
|
||||
self.track_index)
|
||||
# in case track name and subset name is the same then add
|
||||
if self.subset_name == self.track_name:
|
||||
master_data["subset"] = self.subset
|
||||
# assing data to return hierarchy data to tag
|
||||
tag_hierarchy_data = master_data
|
||||
|
||||
# add data to return data dict
|
||||
self.tag_data.update(tag_hierarchy_data)
|
||||
|
||||
if master_layer and self.review_layer:
|
||||
self.tag_data.update({"reviewTrack": self.review_layer})
|
||||
|
||||
def _solve_tag_hierarchy_data(self, hierarchy_formating_data):
|
||||
""" Solve tag data from hierarchy data and templates. """
|
||||
# fill up clip name and hierarchy keys
|
||||
hierarchy_filled = self.hierarchy.format(**hierarchy_formating_data)
|
||||
clip_name_filled = self.clip_name.format(**hierarchy_formating_data)
|
||||
|
||||
return {
|
||||
"newClipName": clip_name_filled,
|
||||
"hierarchy": hierarchy_filled,
|
||||
"parents": self.parents,
|
||||
"hierarchyData": hierarchy_formating_data,
|
||||
"subset": self.subset,
|
||||
"family": self.subset_family,
|
||||
"families": [self.data["family"]]
|
||||
}
|
||||
|
||||
def _convert_to_entity(self, key):
|
||||
""" Converting input key to key with type. """
|
||||
# convert to entity type
|
||||
entity_type = self.types.get(key, None)
|
||||
|
||||
assert entity_type, "Missing entity type for `{}`".format(
|
||||
key
|
||||
)
|
||||
|
||||
return {
|
||||
"entity_type": entity_type,
|
||||
"entity_name": self.hierarchy_data[key]["value"].format(
|
||||
**self.track_item_default_data
|
||||
)
|
||||
}
|
||||
|
||||
def _create_parents(self):
|
||||
""" Create parents and return it in list. """
|
||||
self.parents = list()
|
||||
|
||||
patern = re.compile(self.parents_search_patern)
|
||||
par_split = [patern.findall(t).pop()
|
||||
for t in self.hierarchy.split("/")]
|
||||
|
||||
for key in par_split:
|
||||
parent = self._convert_to_entity(key)
|
||||
self.parents.append(parent)
|
||||
26
pype/hosts/hiero/api/style.css
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
QWidget {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
QSpinBox {
|
||||
padding: 2;
|
||||
max-width: 8em;
|
||||
}
|
||||
|
||||
QLineEdit {
|
||||
padding: 2;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
QVBoxLayout {
|
||||
min-width: 15em;
|
||||
background-color: #201f1f;
|
||||
}
|
||||
|
||||
QComboBox {
|
||||
min-width: 8em;
|
||||
}
|
||||
|
||||
#sectionContent {
|
||||
background-color: #2E2D2D;
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
import re
|
||||
import os
|
||||
import json
|
||||
import hiero
|
||||
|
||||
from pprint import pformat
|
||||
|
||||
from pype.api import Logger
|
||||
from avalon import io
|
||||
|
||||
|
|
@ -12,55 +9,99 @@ log = Logger().get_logger(__name__)
|
|||
|
||||
|
||||
def tag_data():
|
||||
current_dir = os.path.dirname(__file__)
|
||||
json_path = os.path.join(current_dir, "tags.json")
|
||||
with open(json_path, "r") as json_stream:
|
||||
data = json.load(json_stream)
|
||||
return data
|
||||
return {
|
||||
"Retiming": {
|
||||
"editable": "1",
|
||||
"note": "Clip has retime or TimeWarp effects (or multiple effects stacked on the clip)", # noqa
|
||||
"icon": "retiming.png",
|
||||
"metadata": {
|
||||
"family": "retiming",
|
||||
"marginIn": 1,
|
||||
"marginOut": 1
|
||||
}
|
||||
},
|
||||
"[Lenses]": {
|
||||
"Set lense here": {
|
||||
"editable": "1",
|
||||
"note": "Adjust parameters of your lense and then drop to clip. Remember! You can always overwrite on clip", # noqa
|
||||
"icon": "lense.png",
|
||||
"metadata": {
|
||||
"focalLengthMm": 57
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
"NukeScript": {
|
||||
"editable": "1",
|
||||
"note": "Collecting track items to Nuke scripts.",
|
||||
"icon": "icons:TagNuke.png",
|
||||
"metadata": {
|
||||
"family": "nukescript",
|
||||
"subset": "main"
|
||||
}
|
||||
},
|
||||
"Comment": {
|
||||
"editable": "1",
|
||||
"note": "Comment on a shot.",
|
||||
"icon": "icons:TagComment.png",
|
||||
"metadata": {
|
||||
"family": "comment",
|
||||
"subset": "main"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def create_tag(key, value):
|
||||
def create_tag(key, data):
|
||||
"""
|
||||
Creating Tag object.
|
||||
|
||||
Args:
|
||||
key (str): name of tag
|
||||
value (dict): parameters of tag
|
||||
data (dict): parameters of tag
|
||||
|
||||
Returns:
|
||||
object: Tag object
|
||||
"""
|
||||
|
||||
tag = hiero.core.Tag(str(key))
|
||||
|
||||
return update_tag(tag, value)
|
||||
return update_tag(tag, data)
|
||||
|
||||
|
||||
def update_tag(tag, value):
|
||||
def update_tag(tag, data):
|
||||
"""
|
||||
Fixing Tag object.
|
||||
|
||||
Args:
|
||||
tag (obj): Tag object
|
||||
value (dict): parameters of tag
|
||||
data (dict): parameters of tag
|
||||
"""
|
||||
|
||||
tag.setNote(value["note"])
|
||||
tag.setIcon(str(value["icon"]["path"]))
|
||||
# set icon if any available in input data
|
||||
if data.get("icon"):
|
||||
tag.setIcon(str(data["icon"]))
|
||||
# set note description of tag
|
||||
tag.setNote(data["note"])
|
||||
# get metadata of tag
|
||||
mtd = tag.metadata()
|
||||
pres_mtd = value.get("metadata", None)
|
||||
if pres_mtd:
|
||||
[mtd.setValue("tag.{}".format(str(k)), str(v))
|
||||
for k, v in pres_mtd.items()]
|
||||
|
||||
# get metadata key from data
|
||||
data_mtd = data.get("metadata", {})
|
||||
# set all data metadata to tag metadata
|
||||
for k, v in data_mtd.items():
|
||||
mtd.setValue(
|
||||
"tag.{}".format(str(k)),
|
||||
str(v)
|
||||
)
|
||||
return tag
|
||||
|
||||
|
||||
def add_tags_from_presets():
|
||||
def add_tags_to_workfile():
|
||||
"""
|
||||
Will create default tags from presets.
|
||||
"""
|
||||
project = hiero.core.projects()[-1]
|
||||
from .lib import get_current_project
|
||||
|
||||
# get project and root bin object
|
||||
project = get_current_project()
|
||||
root_bin = project.tagsBin()
|
||||
|
||||
if "Tag Presets" in project.name():
|
||||
return
|
||||
|
|
@ -73,7 +114,7 @@ def add_tags_from_presets():
|
|||
# Get project task types.
|
||||
tasks = io.find_one({"type": "project"})["config"]["tasks"]
|
||||
nks_pres_tags["[Tasks]"] = {}
|
||||
log.debug("__ tasks: {}".format(pformat(tasks)))
|
||||
log.debug("__ tasks: {}".format(tasks))
|
||||
for task_type in tasks.keys():
|
||||
nks_pres_tags["[Tasks]"][task_type.lower()] = {
|
||||
"editable": "1",
|
||||
|
|
@ -104,24 +145,32 @@ def add_tags_from_presets():
|
|||
}
|
||||
}
|
||||
|
||||
# get project and root bin object
|
||||
project = hiero.core.projects()[-1]
|
||||
root_bin = project.tagsBin()
|
||||
|
||||
# loop trough tag data dict and create deep tag structure
|
||||
for _k, _val in nks_pres_tags.items():
|
||||
# check if key is not decorated with [] so it is defined as bin
|
||||
bin_find = None
|
||||
pattern = re.compile(r"\[(.*)\]")
|
||||
bin_find = pattern.findall(_k)
|
||||
bin_finds = pattern.findall(_k)
|
||||
# if there is available any then pop it to string
|
||||
if bin_finds:
|
||||
bin_find = bin_finds.pop()
|
||||
|
||||
# if bin was found then create or update
|
||||
if bin_find:
|
||||
# check what is in root bin
|
||||
root_add = False
|
||||
# first check if in root lever is not already created bins
|
||||
bins = [b for b in root_bin.items()
|
||||
if b.name() in str(bin_find[0])]
|
||||
if b.name() in str(bin_find)]
|
||||
log.debug(">>> bins: {}".format(bins))
|
||||
|
||||
if bins:
|
||||
bin = bins[0]
|
||||
bin = bins.pop()
|
||||
else:
|
||||
# create Bin object
|
||||
bin = hiero.core.Bin(str(bin_find[0]))
|
||||
root_add = True
|
||||
# create Bin object for processing
|
||||
bin = hiero.core.Bin(str(bin_find))
|
||||
|
||||
# update or create tags in the bin
|
||||
for k, v in _val.items():
|
||||
tags = [t for t in bin.items()
|
||||
if str(k) in t.name()
|
||||
|
|
@ -133,13 +182,15 @@ def add_tags_from_presets():
|
|||
# adding Tag to Bin
|
||||
bin.addItem(tag)
|
||||
else:
|
||||
update_tag(tags[0], v)
|
||||
update_tag(tags.pop(), v)
|
||||
|
||||
if not bins:
|
||||
# finally add the Bin object to the root level Bin
|
||||
if root_add:
|
||||
# adding Tag to Root Bin
|
||||
root_bin.addItem(bin)
|
||||
|
||||
else:
|
||||
# for Tags to be created in root level Bin
|
||||
# at first check if any of input data tag is not already created
|
||||
tags = None
|
||||
tags = [t for t in root_bin.items()
|
||||
if str(_k) in t.name()]
|
||||
|
|
@ -151,16 +202,18 @@ def add_tags_from_presets():
|
|||
# adding Tag to Root Bin
|
||||
root_bin.addItem(tag)
|
||||
else:
|
||||
# check if Hierarchy in name
|
||||
# update Tag if already exists
|
||||
# update Tags if they already exists
|
||||
for _t in tags:
|
||||
# skip bin objects
|
||||
if isinstance(_t, hiero.core.Bin):
|
||||
continue
|
||||
|
||||
# check if Hierarchy in name and skip it
|
||||
# because hierarchy could be edited
|
||||
if "hierarchy" in _t.name().lower():
|
||||
continue
|
||||
|
||||
# update only non hierarchy tags
|
||||
# because hierarchy could be edited
|
||||
update_tag(_t, _val)
|
||||
|
||||
log.info("Default Tags were set...")
|
||||
|
|
@ -1,817 +0,0 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
import hiero
|
||||
import pyblish.api
|
||||
import avalon.api as avalon
|
||||
import avalon.io
|
||||
from avalon.vendor.Qt import (QtWidgets, QtGui)
|
||||
import pype.api as pype
|
||||
from pype.api import Logger, Anatomy
|
||||
|
||||
log = Logger().get_logger(__name__)
|
||||
|
||||
cached_process = None
|
||||
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._has_been_setup = False
|
||||
self._has_menu = False
|
||||
self._registered_gui = None
|
||||
|
||||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||
|
||||
|
||||
def set_workfiles():
|
||||
''' Wrapping function for workfiles launcher '''
|
||||
from avalon.tools import workfiles
|
||||
|
||||
workdir = os.environ["AVALON_WORKDIR"]
|
||||
|
||||
# show workfile gui
|
||||
workfiles.show(workdir)
|
||||
|
||||
|
||||
def sync_avalon_data_to_workfile():
|
||||
# import session to get project dir
|
||||
project_name = avalon.Session["AVALON_PROJECT"]
|
||||
|
||||
anatomy = Anatomy(project_name)
|
||||
work_template = anatomy.templates["work"]["path"]
|
||||
work_root = anatomy.root_value_for_template(work_template)
|
||||
active_project_root = (
|
||||
os.path.join(work_root, project_name)
|
||||
).replace("\\", "/")
|
||||
# getting project
|
||||
project = hiero.core.projects()[-1]
|
||||
|
||||
if "Tag Presets" in project.name():
|
||||
return
|
||||
|
||||
log.debug("Synchronizing Pype metadata to project: {}".format(
|
||||
project.name()))
|
||||
|
||||
# set project root with backward compatibility
|
||||
try:
|
||||
project.setProjectDirectory(active_project_root)
|
||||
except Exception:
|
||||
# old way of seting it
|
||||
project.setProjectRoot(active_project_root)
|
||||
|
||||
# get project data from avalon db
|
||||
project_doc = avalon.io.find_one({"type": "project"})
|
||||
project_data = project_doc["data"]
|
||||
|
||||
log.debug("project_data: {}".format(project_data))
|
||||
|
||||
# get format and fps property from avalon db on project
|
||||
width = project_data["resolutionWidth"]
|
||||
height = project_data["resolutionHeight"]
|
||||
pixel_aspect = project_data["pixelAspect"]
|
||||
fps = project_data['fps']
|
||||
format_name = project_data['code']
|
||||
|
||||
# create new format in hiero project
|
||||
format = hiero.core.Format(width, height, pixel_aspect, format_name)
|
||||
project.setOutputFormat(format)
|
||||
|
||||
# set fps to hiero project
|
||||
project.setFramerate(fps)
|
||||
|
||||
# TODO: add auto colorspace set from project drop
|
||||
log.info("Project property has been synchronised with Avalon db")
|
||||
|
||||
|
||||
def launch_workfiles_app(event):
|
||||
"""
|
||||
Event for launching workfiles after hiero start
|
||||
|
||||
Args:
|
||||
event (obj): required but unused
|
||||
"""
|
||||
set_workfiles()
|
||||
|
||||
|
||||
|
||||
def reload_config():
|
||||
"""Attempt to reload pipeline at run-time.
|
||||
|
||||
CAUTION: This is primarily for development and debugging purposes.
|
||||
|
||||
"""
|
||||
|
||||
import importlib
|
||||
|
||||
for module in (
|
||||
"avalon",
|
||||
"avalon.lib",
|
||||
"avalon.pipeline",
|
||||
"pyblish",
|
||||
"pyblish_lite",
|
||||
"{}.api".format(AVALON_CONFIG),
|
||||
"{}.templates".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.lib".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.menu".format(AVALON_CONFIG),
|
||||
"{}.hosts.hiero.tags".format(AVALON_CONFIG)
|
||||
):
|
||||
log.info("Reloading module: {}...".format(module))
|
||||
try:
|
||||
module = importlib.import_module(module)
|
||||
reload(module)
|
||||
except Exception as e:
|
||||
log.warning("Cannot reload module: {}".format(e))
|
||||
importlib.reload(module)
|
||||
|
||||
|
||||
def setup(console=False, port=None, menu=True):
|
||||
"""Setup integration
|
||||
|
||||
Registers Pyblish for Hiero plug-ins and appends an item to the File-menu
|
||||
|
||||
Arguments:
|
||||
console (bool): Display console with GUI
|
||||
port (int, optional): Port from which to start looking for an
|
||||
available port to connect with Pyblish QML, default
|
||||
provided by Pyblish Integration.
|
||||
menu (bool, optional): Display file menu in Hiero.
|
||||
"""
|
||||
|
||||
if self._has_been_setup:
|
||||
teardown()
|
||||
|
||||
add_submission()
|
||||
|
||||
if menu:
|
||||
add_to_filemenu()
|
||||
self._has_menu = True
|
||||
|
||||
self._has_been_setup = True
|
||||
print("pyblish: Loaded successfully.")
|
||||
|
||||
|
||||
def show():
|
||||
"""Try showing the most desirable GUI
|
||||
This function cycles through the currently registered
|
||||
graphical user interfaces, if any, and presents it to
|
||||
the user.
|
||||
"""
|
||||
|
||||
return (_discover_gui() or _show_no_gui)()
|
||||
|
||||
|
||||
def _discover_gui():
|
||||
"""Return the most desirable of the currently registered GUIs"""
|
||||
|
||||
# Prefer last registered
|
||||
guis = reversed(pyblish.api.registered_guis())
|
||||
|
||||
for gui in list(guis) + ["pyblish_lite"]:
|
||||
try:
|
||||
gui = __import__(gui).show
|
||||
except (ImportError, AttributeError):
|
||||
continue
|
||||
else:
|
||||
return gui
|
||||
|
||||
|
||||
def teardown():
|
||||
"""Remove integration"""
|
||||
if not self._has_been_setup:
|
||||
return
|
||||
|
||||
if self._has_menu:
|
||||
remove_from_filemenu()
|
||||
self._has_menu = False
|
||||
|
||||
self._has_been_setup = False
|
||||
print("pyblish: Integration torn down successfully")
|
||||
|
||||
|
||||
def remove_from_filemenu():
|
||||
raise NotImplementedError("Implement me please.")
|
||||
|
||||
|
||||
def add_to_filemenu():
|
||||
PublishAction()
|
||||
|
||||
|
||||
class PyblishSubmission(hiero.exporters.FnSubmission.Submission):
|
||||
|
||||
def __init__(self):
|
||||
hiero.exporters.FnSubmission.Submission.__init__(self)
|
||||
|
||||
def addToQueue(self):
|
||||
# Add submission to Hiero module for retrieval in plugins.
|
||||
hiero.submission = self
|
||||
show()
|
||||
|
||||
|
||||
def add_submission():
|
||||
registry = hiero.core.taskRegistry
|
||||
registry.addSubmission("Pyblish", PyblishSubmission)
|
||||
|
||||
|
||||
class PublishAction(QtWidgets.QAction):
|
||||
"""
|
||||
Action with is showing as menu item
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
QtWidgets.QAction.__init__(self, "Publish", None)
|
||||
self.triggered.connect(self.publish)
|
||||
|
||||
for interest in ["kShowContextMenu/kTimeline",
|
||||
"kShowContextMenukBin",
|
||||
"kShowContextMenu/kSpreadsheet"]:
|
||||
hiero.core.events.registerInterest(interest, self.eventHandler)
|
||||
|
||||
self.setShortcut("Ctrl+Alt+P")
|
||||
|
||||
def publish(self):
|
||||
# Removing "submission" attribute from hiero module, to prevent tasks
|
||||
# from getting picked up when not using the "Export" dialog.
|
||||
if hasattr(hiero, "submission"):
|
||||
del hiero.submission
|
||||
show()
|
||||
|
||||
def eventHandler(self, event):
|
||||
# Add the Menu to the right-click menu
|
||||
event.menu.addAction(self)
|
||||
|
||||
|
||||
def _show_no_gui():
|
||||
"""
|
||||
Popup with information about how to register a new GUI
|
||||
In the event of no GUI being registered or available,
|
||||
this information dialog will appear to guide the user
|
||||
through how to get set up with one.
|
||||
"""
|
||||
|
||||
messagebox = QtWidgets.QMessageBox()
|
||||
messagebox.setIcon(messagebox.Warning)
|
||||
messagebox.setWindowIcon(QtGui.QIcon(os.path.join(
|
||||
os.path.dirname(pyblish.__file__),
|
||||
"icons",
|
||||
"logo-32x32.svg"))
|
||||
)
|
||||
|
||||
spacer = QtWidgets.QWidget()
|
||||
spacer.setMinimumSize(400, 0)
|
||||
spacer.setSizePolicy(QtWidgets.QSizePolicy.Minimum,
|
||||
QtWidgets.QSizePolicy.Expanding)
|
||||
|
||||
layout = messagebox.layout()
|
||||
layout.addWidget(spacer, layout.rowCount(), 0, 1, layout.columnCount())
|
||||
|
||||
messagebox.setWindowTitle("Uh oh")
|
||||
messagebox.setText("No registered GUI found.")
|
||||
|
||||
if not pyblish.api.registered_guis():
|
||||
messagebox.setInformativeText(
|
||||
"In order to show you a GUI, one must first be registered. "
|
||||
"Press \"Show details...\" below for information on how to "
|
||||
"do that.")
|
||||
|
||||
messagebox.setDetailedText(
|
||||
"Pyblish supports one or more graphical user interfaces "
|
||||
"to be registered at once, the next acting as a fallback to "
|
||||
"the previous."
|
||||
"\n"
|
||||
"\n"
|
||||
"For example, to use Pyblish Lite, first install it:"
|
||||
"\n"
|
||||
"\n"
|
||||
"$ pip install pyblish-lite"
|
||||
"\n"
|
||||
"\n"
|
||||
"Then register it, like so:"
|
||||
"\n"
|
||||
"\n"
|
||||
">>> import pyblish.api\n"
|
||||
">>> pyblish.api.register_gui(\"pyblish_lite\")"
|
||||
"\n"
|
||||
"\n"
|
||||
"The next time you try running this, Lite will appear."
|
||||
"\n"
|
||||
"See http://api.pyblish.com/register_gui.html for "
|
||||
"more information.")
|
||||
|
||||
else:
|
||||
messagebox.setInformativeText(
|
||||
"None of the registered graphical user interfaces "
|
||||
"could be found."
|
||||
"\n"
|
||||
"\n"
|
||||
"Press \"Show details\" for more information.")
|
||||
|
||||
messagebox.setDetailedText(
|
||||
"These interfaces are currently registered."
|
||||
"\n"
|
||||
"%s" % "\n".join(pyblish.api.registered_guis()))
|
||||
|
||||
messagebox.setStandardButtons(messagebox.Ok)
|
||||
messagebox.exec_()
|
||||
|
||||
|
||||
def CreateNukeWorkfile(nodes=None,
|
||||
nodes_effects=None,
|
||||
to_timeline=False,
|
||||
**kwargs):
|
||||
''' Creating nuke workfile with particular version with given nodes
|
||||
Also it is creating timeline track items as precomps.
|
||||
|
||||
Arguments:
|
||||
nodes(list of dict): each key in dict is knob order is important
|
||||
to_timeline(type): will build trackItem with metadata
|
||||
|
||||
Returns:
|
||||
bool: True if done
|
||||
|
||||
Raises:
|
||||
Exception: with traceback
|
||||
|
||||
'''
|
||||
import hiero.core
|
||||
from avalon.nuke import imprint
|
||||
from pype.hosts.nuke import (
|
||||
lib as nklib
|
||||
)
|
||||
|
||||
# check if the file exists if does then Raise "File exists!"
|
||||
if os.path.exists(filepath):
|
||||
raise FileExistsError("File already exists: `{}`".format(filepath))
|
||||
|
||||
# if no representations matching then
|
||||
# Raise "no representations to be build"
|
||||
if len(representations) == 0:
|
||||
raise AttributeError("Missing list of `representations`")
|
||||
|
||||
# check nodes input
|
||||
if len(nodes) == 0:
|
||||
log.warning("Missing list of `nodes`")
|
||||
|
||||
# create temp nk file
|
||||
nuke_script = hiero.core.nuke.ScriptWriter()
|
||||
|
||||
# create root node and save all metadata
|
||||
root_node = hiero.core.nuke.RootNode()
|
||||
|
||||
anatomy = Anatomy(os.environ["AVALON_PROJECT"])
|
||||
work_template = anatomy.templates["work"]["path"]
|
||||
root_path = anatomy.root_value_for_template(work_template)
|
||||
|
||||
nuke_script.addNode(root_node)
|
||||
|
||||
# here to call pype.hosts.nuke.lib.BuildWorkfile
|
||||
script_builder = nklib.BuildWorkfile(
|
||||
root_node=root_node,
|
||||
root_path=root_path,
|
||||
nodes=nuke_script.getNodes(),
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class ClipLoader:
|
||||
|
||||
active_bin = None
|
||||
|
||||
def __init__(self, plugin_cls, context, sequence=None, track=None, **kwargs):
|
||||
""" Initialize object
|
||||
|
||||
Arguments:
|
||||
plugin_cls (api.Loader): plugin object
|
||||
context (dict): loader plugin context
|
||||
sequnce (hiero.core.Sequence): sequence object
|
||||
track (hiero.core.Track): track object
|
||||
kwargs (dict)[optional]: possible keys:
|
||||
projectBinPath: "path/to/binItem"
|
||||
hieroWorkfileName: "name_of_hiero_project_file_no_extension"
|
||||
|
||||
"""
|
||||
self.cls = plugin_cls
|
||||
self.context = context
|
||||
self.kwargs = kwargs
|
||||
self.active_project = self._get_active_project()
|
||||
self.project_bin = self.active_project.clipsBin()
|
||||
|
||||
self.data = dict()
|
||||
|
||||
assert self._set_data(), str("Cannot Load selected data, look into "
|
||||
"database or call your supervisor")
|
||||
|
||||
# inject asset data to representation dict
|
||||
self._get_asset_data()
|
||||
log.debug("__init__ self.data: `{}`".format(self.data))
|
||||
|
||||
# add active components to class
|
||||
self.active_sequence = self._get_active_sequence(sequence)
|
||||
self.active_track = self._get_active_track(track)
|
||||
|
||||
def _set_data(self):
|
||||
""" Gets context and convert it to self.data
|
||||
data structure:
|
||||
{
|
||||
"name": "assetName_subsetName_representationName"
|
||||
"path": "path/to/file/created/by/get_repr..",
|
||||
"binPath": "projectBinPath",
|
||||
}
|
||||
"""
|
||||
# create name
|
||||
repr = self.context["representation"]
|
||||
repr_cntx = repr["context"]
|
||||
asset = str(repr_cntx["asset"])
|
||||
subset = str(repr_cntx["subset"])
|
||||
representation = str(repr_cntx["representation"])
|
||||
self.data["clip_name"] = "_".join([asset, subset, representation])
|
||||
self.data["track_name"] = "_".join([subset, representation])
|
||||
|
||||
# gets file path
|
||||
file = self.cls.fname
|
||||
if not file:
|
||||
repr_id = repr["_id"]
|
||||
log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return None
|
||||
self.data["path"] = file.replace("\\", "/")
|
||||
|
||||
# convert to hashed path
|
||||
if repr_cntx.get("frame"):
|
||||
self._fix_path_hashes()
|
||||
|
||||
# solve project bin structure path
|
||||
hierarchy = str("/".join((
|
||||
"Loader",
|
||||
repr_cntx["hierarchy"].replace("\\", "/"),
|
||||
asset
|
||||
)))
|
||||
|
||||
self.data["binPath"] = self.kwargs.get(
|
||||
"projectBinPath",
|
||||
hierarchy
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _fix_path_hashes(self):
|
||||
""" Convert file path where it is needed padding with hashes
|
||||
"""
|
||||
file = self.data["path"]
|
||||
if "#" not in file:
|
||||
frame = self.context["representation"]["context"].get("frame")
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#"*padding)
|
||||
self.data["path"] = file
|
||||
|
||||
def _get_active_project(self):
|
||||
""" Get hiero active project object
|
||||
"""
|
||||
fname = self.kwargs.get("hieroWorkfileName", "")
|
||||
|
||||
return next((p for p in hiero.core.projects()
|
||||
if fname in p.name()),
|
||||
hiero.core.projects()[-1])
|
||||
|
||||
def _get_asset_data(self):
|
||||
""" Get all available asset data
|
||||
|
||||
joint `data` key with asset.data dict into the representaion
|
||||
|
||||
"""
|
||||
asset_name = self.context["representation"]["context"]["asset"]
|
||||
self.data["assetData"] = pype.get_asset(asset_name)["data"]
|
||||
|
||||
def _make_project_bin(self, hierarchy):
|
||||
""" Creare bins by given hierarchy path
|
||||
|
||||
It will also make sure no duplicit bins will be created
|
||||
|
||||
Arguments:
|
||||
hierarchy (str): path devided by slashes "bin0/bin1/bin2"
|
||||
|
||||
Returns:
|
||||
bin (hiero.core.BinItem): with the bin to be used for mediaItem
|
||||
"""
|
||||
if self.active_bin:
|
||||
return self.active_bin
|
||||
|
||||
assert hierarchy != "", "Please add hierarchy!"
|
||||
log.debug("__ hierarchy1: `{}`".format(hierarchy))
|
||||
if '/' in hierarchy:
|
||||
hierarchy = hierarchy.split('/')
|
||||
else:
|
||||
hierarchy = [hierarchy]
|
||||
|
||||
parent_bin = None
|
||||
for i, name in enumerate(hierarchy):
|
||||
# if first index and list is more then one long
|
||||
if i == 0:
|
||||
bin = next((bin for bin in self.project_bin.bins()
|
||||
if name in bin.name()), None)
|
||||
if not bin:
|
||||
bin = hiero.core.Bin(name)
|
||||
self.project_bin.addItem(bin)
|
||||
log.debug("__ bin.name: `{}`".format(bin.name()))
|
||||
parent_bin = bin
|
||||
|
||||
# if second to prelast
|
||||
elif (i >= 1) and (i <= (len(hierarchy) - 1)):
|
||||
bin = next((bin for bin in parent_bin.bins()
|
||||
if name in bin.name()), None)
|
||||
if not bin:
|
||||
bin = hiero.core.Bin(name)
|
||||
parent_bin.addItem(bin)
|
||||
|
||||
parent_bin = bin
|
||||
|
||||
return parent_bin
|
||||
|
||||
def _make_track_item(self):
|
||||
""" Create track item with """
|
||||
pass
|
||||
|
||||
def _set_clip_color(self, last_version=True):
|
||||
""" Sets color of clip on clip/track item
|
||||
|
||||
Arguments:
|
||||
last_version (bool): True = green | False = red
|
||||
"""
|
||||
pass
|
||||
|
||||
def _set_container_tag(self, item, metadata):
|
||||
""" Sets container tag to given clip/track item
|
||||
|
||||
Arguments:
|
||||
item (hiero.core.BinItem or hiero.core.TrackItem)
|
||||
metadata (dict): data to be added to tag
|
||||
"""
|
||||
pass
|
||||
|
||||
def _get_active_sequence(self, sequence):
|
||||
if not sequence:
|
||||
return hiero.ui.activeSequence()
|
||||
else:
|
||||
return sequence
|
||||
|
||||
def _get_active_track(self, track):
|
||||
if not track:
|
||||
track_name = self.data["track_name"]
|
||||
else:
|
||||
track_name = track.name()
|
||||
|
||||
track_pass = next(
|
||||
(t for t in self.active_sequence.videoTracks()
|
||||
if t.name() in track_name), None
|
||||
)
|
||||
|
||||
if not track_pass:
|
||||
track_pass = hiero.core.VideoTrack(track_name)
|
||||
self.active_sequence.addTrack(track_pass)
|
||||
|
||||
return track_pass
|
||||
|
||||
def load(self):
|
||||
log.debug("__ active_project: `{}`".format(self.active_project))
|
||||
log.debug("__ active_sequence: `{}`".format(self.active_sequence))
|
||||
|
||||
# create project bin for the media to be imported into
|
||||
self.active_bin = self._make_project_bin(self.data["binPath"])
|
||||
log.debug("__ active_bin: `{}`".format(self.active_bin))
|
||||
|
||||
log.debug("__ version.data: `{}`".format(
|
||||
self.context["version"]["data"]))
|
||||
|
||||
# create mediaItem in active project bin
|
||||
# create clip media
|
||||
media = hiero.core.MediaSource(self.data["path"])
|
||||
media_duration = int(media.duration())
|
||||
|
||||
handle_start = int(self.data["assetData"]["handleStart"])
|
||||
handle_end = int(self.data["assetData"]["handleEnd"])
|
||||
|
||||
clip_in = int(self.data["assetData"]["clipIn"])
|
||||
clip_out = int(self.data["assetData"]["clipOut"])
|
||||
|
||||
log.debug("__ media_duration: `{}`".format(media_duration))
|
||||
log.debug("__ handle_start: `{}`".format(handle_start))
|
||||
log.debug("__ handle_end: `{}`".format(handle_end))
|
||||
log.debug("__ clip_in: `{}`".format(clip_in))
|
||||
log.debug("__ clip_out: `{}`".format(clip_out))
|
||||
|
||||
# check if slate is included
|
||||
# either in version data families or by calculating frame diff
|
||||
slate_on = next(
|
||||
(f for f in self.context["version"]["data"]["families"]
|
||||
if "slate" in f),
|
||||
None) or bool(((
|
||||
clip_out - clip_in + 1) + handle_start + handle_end
|
||||
) - media_duration)
|
||||
|
||||
log.debug("__ slate_on: `{}`".format(slate_on))
|
||||
|
||||
# calculate slate differences
|
||||
if slate_on:
|
||||
media_duration -= 1
|
||||
handle_start += 1
|
||||
|
||||
fps = self.data["assetData"]["fps"]
|
||||
|
||||
# create Clip from Media
|
||||
_clip = hiero.core.Clip(media)
|
||||
_clip.setName(self.data["clip_name"])
|
||||
|
||||
# add Clip to bin if not there yet
|
||||
if self.data["clip_name"] not in [
|
||||
b.name()
|
||||
for b in self.active_bin.items()]:
|
||||
binItem = hiero.core.BinItem(_clip)
|
||||
self.active_bin.addItem(binItem)
|
||||
|
||||
_source = next((item for item in self.active_bin.items()
|
||||
if self.data["clip_name"] in item.name()), None)
|
||||
|
||||
if not _source:
|
||||
log.warning("Problem with created Source clip: `{}`".format(
|
||||
self.data["clip_name"]))
|
||||
|
||||
version = next((s for s in _source.items()), None)
|
||||
clip = version.item()
|
||||
|
||||
# add to track as clip item
|
||||
track_item = hiero.core.TrackItem(
|
||||
self.data["clip_name"], hiero.core.TrackItem.kVideo)
|
||||
|
||||
track_item.setSource(clip)
|
||||
|
||||
track_item.setSourceIn(handle_start)
|
||||
track_item.setTimelineIn(clip_in)
|
||||
|
||||
track_item.setSourceOut(media_duration - handle_end)
|
||||
track_item.setTimelineOut(clip_out)
|
||||
track_item.setPlaybackSpeed(1)
|
||||
self.active_track.addTrackItem(track_item)
|
||||
|
||||
log.info("Loading clips: `{}`".format(self.data["clip_name"]))
|
||||
|
||||
|
||||
def create_nk_workfile_clips(nk_workfiles, seq=None):
|
||||
'''
|
||||
nk_workfile is list of dictionaries like:
|
||||
[{
|
||||
'path': 'P:/Jakub_testy_pipeline/test_v01.nk',
|
||||
'name': 'test',
|
||||
'handleStart': 15, # added asymetrically to handles
|
||||
'handleEnd': 10, # added asymetrically to handles
|
||||
"clipIn": 16,
|
||||
"frameStart": 991,
|
||||
"frameEnd": 1023,
|
||||
'task': 'Comp-tracking',
|
||||
'work_dir': 'VFX_PR',
|
||||
'shot': '00010'
|
||||
}]
|
||||
'''
|
||||
|
||||
proj = hiero.core.projects()[-1]
|
||||
root = proj.clipsBin()
|
||||
|
||||
if not seq:
|
||||
seq = hiero.core.Sequence('NewSequences')
|
||||
root.addItem(hiero.core.BinItem(seq))
|
||||
# todo will ned to define this better
|
||||
# track = seq[1] # lazy example to get a destination# track
|
||||
clips_lst = []
|
||||
for nk in nk_workfiles:
|
||||
task_path = '/'.join([nk['work_dir'], nk['shot'], nk['task']])
|
||||
bin = create_bin_in_project(task_path, proj)
|
||||
|
||||
if nk['task'] not in seq.videoTracks():
|
||||
track = hiero.core.VideoTrack(nk['task'])
|
||||
seq.addTrack(track)
|
||||
else:
|
||||
track = seq.tracks(nk['task'])
|
||||
|
||||
# create clip media
|
||||
media = hiero.core.MediaSource(nk['path'])
|
||||
media_in = int(media.startTime() or 0)
|
||||
media_duration = int(media.duration() or 0)
|
||||
|
||||
handle_start = nk.get("handleStart")
|
||||
handle_end = nk.get("handleEnd")
|
||||
|
||||
if media_in:
|
||||
source_in = media_in + handle_start
|
||||
else:
|
||||
source_in = nk["frameStart"] + handle_start
|
||||
|
||||
if media_duration:
|
||||
source_out = (media_in + media_duration - 1) - handle_end
|
||||
else:
|
||||
source_out = nk["frameEnd"] - handle_end
|
||||
|
||||
source = hiero.core.Clip(media)
|
||||
|
||||
name = os.path.basename(os.path.splitext(nk['path'])[0])
|
||||
split_name = split_by_client_version(name)[0] or name
|
||||
|
||||
# add to bin as clip item
|
||||
items_in_bin = [b.name() for b in bin.items()]
|
||||
if split_name not in items_in_bin:
|
||||
binItem = hiero.core.BinItem(source)
|
||||
bin.addItem(binItem)
|
||||
|
||||
new_source = [
|
||||
item for item in bin.items() if split_name in item.name()
|
||||
][0].items()[0].item()
|
||||
|
||||
# add to track as clip item
|
||||
trackItem = hiero.core.TrackItem(
|
||||
split_name, hiero.core.TrackItem.kVideo)
|
||||
trackItem.setSource(new_source)
|
||||
trackItem.setSourceIn(source_in)
|
||||
trackItem.setSourceOut(source_out)
|
||||
trackItem.setTimelineIn(nk["clipIn"])
|
||||
trackItem.setTimelineOut(nk["clipIn"] + (source_out - source_in))
|
||||
track.addTrackItem(trackItem)
|
||||
clips_lst.append(trackItem)
|
||||
|
||||
return clips_lst
|
||||
|
||||
|
||||
def create_bin_in_project(bin_name='', project=''):
|
||||
'''
|
||||
create bin in project and
|
||||
if the bin_name is "bin1/bin2/bin3" it will create whole depth
|
||||
'''
|
||||
|
||||
if not project:
|
||||
# get the first loaded project
|
||||
project = hiero.core.projects()[-1]
|
||||
if not bin_name:
|
||||
return None
|
||||
if '/' in bin_name:
|
||||
bin_name = bin_name.split('/')
|
||||
else:
|
||||
bin_name = [bin_name]
|
||||
|
||||
clipsBin = project.clipsBin()
|
||||
|
||||
done_bin_lst = []
|
||||
for i, b in enumerate(bin_name):
|
||||
if i == 0 and len(bin_name) > 1:
|
||||
if b in [bin.name() for bin in clipsBin.bins()]:
|
||||
bin = [bin for bin in clipsBin.bins() if b in bin.name()][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
clipsBin.addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
|
||||
elif i >= 1 and i < len(bin_name) - 1:
|
||||
if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]:
|
||||
bin = [
|
||||
bin for bin in done_bin_lst[i - 1].bins()
|
||||
if b in bin.name()
|
||||
][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
done_bin_lst[i - 1].addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
|
||||
elif i == len(bin_name) - 1:
|
||||
if b in [bin.name() for bin in done_bin_lst[i - 1].bins()]:
|
||||
bin = [
|
||||
bin for bin in done_bin_lst[i - 1].bins()
|
||||
if b in bin.name()
|
||||
][0]
|
||||
done_bin_lst.append(bin)
|
||||
else:
|
||||
create_bin = hiero.core.Bin(b)
|
||||
done_bin_lst[i - 1].addItem(create_bin)
|
||||
done_bin_lst.append(create_bin)
|
||||
# print [bin.name() for bin in clipsBin.bins()]
|
||||
return done_bin_lst[-1]
|
||||
|
||||
|
||||
def split_by_client_version(string):
|
||||
regex = r"[/_.]v\d+"
|
||||
try:
|
||||
matches = re.findall(regex, string, re.IGNORECASE)
|
||||
return string.split(matches[0])
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return None
|
||||
|
||||
|
||||
# nk_workfiles = [{
|
||||
# 'path': 'C:/Users/hubert/_PYPE_testing/projects/D001_projectx/episodes/ep120/ep120sq01/120sh020/publish/plates/platesMain/v023/prjx_120sh020_platesMain_v023.nk',
|
||||
# 'name': '120sh020_platesMain',
|
||||
# 'handles': 10,
|
||||
# 'handleStart': 10,
|
||||
# 'handleEnd': 10,
|
||||
# "clipIn": 16,
|
||||
# "frameStart": 991,
|
||||
# "frameEnd": 1023,
|
||||
# 'task': 'platesMain',
|
||||
# 'work_dir': 'shots',
|
||||
# 'shot': '120sh020'
|
||||
# }]
|
||||
257
pype/hosts/hiero/plugins/create/create_shot_clip.py
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
import pype.hosts.hiero.api as phiero
|
||||
# from pype.hosts.hiero.api import plugin, lib
|
||||
# reload(lib)
|
||||
# reload(plugin)
|
||||
# reload(phiero)
|
||||
|
||||
|
||||
class CreateShotClip(phiero.Creator):
|
||||
"""Publishable clip"""
|
||||
|
||||
label = "Create Publishable Clip"
|
||||
family = "clip"
|
||||
icon = "film"
|
||||
defaults = ["Main"]
|
||||
|
||||
gui_tracks = [track.name()
|
||||
for track in phiero.get_current_sequence().videoTracks()]
|
||||
gui_name = "Pype publish attributes creator"
|
||||
gui_info = "Define sequential rename and fill hierarchy data."
|
||||
gui_inputs = {
|
||||
"renameHierarchy": {
|
||||
"type": "section",
|
||||
"label": "Shot Hierarchy And Rename Settings",
|
||||
"target": "ui",
|
||||
"order": 0,
|
||||
"value": {
|
||||
"hierarchy": {
|
||||
"value": "{folder}/{sequence}",
|
||||
"type": "QLineEdit",
|
||||
"label": "Shot Parent Hierarchy",
|
||||
"target": "tag",
|
||||
"toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa
|
||||
"order": 0},
|
||||
"clipRename": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Rename clips",
|
||||
"target": "ui",
|
||||
"toolTip": "Renaming selected clips on fly", # noqa
|
||||
"order": 1},
|
||||
"clipName": {
|
||||
"value": "{sequence}{shot}",
|
||||
"type": "QLineEdit",
|
||||
"label": "Clip Name Template",
|
||||
"target": "ui",
|
||||
"toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa
|
||||
"order": 2},
|
||||
"countFrom": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Count sequence from",
|
||||
"target": "ui",
|
||||
"toolTip": "Set when the sequence number stafrom", # noqa
|
||||
"order": 3},
|
||||
"countSteps": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Stepping number",
|
||||
"target": "ui",
|
||||
"toolTip": "What number is adding every new step", # noqa
|
||||
"order": 4},
|
||||
}
|
||||
},
|
||||
"hierarchyData": {
|
||||
"type": "dict",
|
||||
"label": "Shot Template Keywords",
|
||||
"target": "tag",
|
||||
"order": 1,
|
||||
"value": {
|
||||
"folder": {
|
||||
"value": "shots",
|
||||
"type": "QLineEdit",
|
||||
"label": "{folder}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of folder used for root of generated shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 0},
|
||||
"episode": {
|
||||
"value": "ep01",
|
||||
"type": "QLineEdit",
|
||||
"label": "{episode}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of episode.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 1},
|
||||
"sequence": {
|
||||
"value": "sq01",
|
||||
"type": "QLineEdit",
|
||||
"label": "{sequence}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 2},
|
||||
"track": {
|
||||
"value": "{_track_}",
|
||||
"type": "QLineEdit",
|
||||
"label": "{track}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of sequence of shots.\nUsable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 3},
|
||||
"shot": {
|
||||
"value": "sh###",
|
||||
"type": "QLineEdit",
|
||||
"label": "{shot}",
|
||||
"target": "tag",
|
||||
"toolTip": "Name of shot. `#` is converted to paded number. \nAlso could be used with usable tokens:\n\t{_clip_}: name of used clip\n\t{_track_}: name of parent track layer\n\t{_sequence_}: name of parent sequence (timeline)", # noqa
|
||||
"order": 4}
|
||||
}
|
||||
},
|
||||
"verticalSync": {
|
||||
"type": "section",
|
||||
"label": "Vertical Synchronization Of Attributes",
|
||||
"target": "ui",
|
||||
"order": 2,
|
||||
"value": {
|
||||
"vSyncOn": {
|
||||
"value": True,
|
||||
"type": "QCheckBox",
|
||||
"label": "Enable Vertical Sync",
|
||||
"target": "ui",
|
||||
"toolTip": "Switch on if you want clips above each other to share its attributes", # noqa
|
||||
"order": 0},
|
||||
"vSyncTrack": {
|
||||
"value": gui_tracks, # noqa
|
||||
"type": "QComboBox",
|
||||
"label": "Master track",
|
||||
"target": "ui",
|
||||
"toolTip": "Select driving track name which should be mastering all others", # noqa
|
||||
"order": 1}
|
||||
}
|
||||
},
|
||||
"publishSettings": {
|
||||
"type": "section",
|
||||
"label": "Publish Settings",
|
||||
"target": "ui",
|
||||
"order": 3,
|
||||
"value": {
|
||||
"subsetName": {
|
||||
"value": ["<track_name>", "main", "bg", "fg", "bg",
|
||||
"animatic"],
|
||||
"type": "QComboBox",
|
||||
"label": "Subset Name",
|
||||
"target": "ui",
|
||||
"toolTip": "chose subset name patern, if <track_name> is selected, name of track layer will be used", # noqa
|
||||
"order": 0},
|
||||
"subsetFamily": {
|
||||
"value": ["plate", "take"],
|
||||
"type": "QComboBox",
|
||||
"label": "Subset Family",
|
||||
"target": "ui", "toolTip": "What use of this subset is for", # noqa
|
||||
"order": 1},
|
||||
"reviewTrack": {
|
||||
"value": ["< none >"] + gui_tracks,
|
||||
"type": "QComboBox",
|
||||
"label": "Use Review Track",
|
||||
"target": "ui",
|
||||
"toolTip": "Generate preview videos on fly, if `< none >` is defined nothing will be generated.", # noqa
|
||||
"order": 2},
|
||||
"audio": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Include audio",
|
||||
"target": "tag",
|
||||
"toolTip": "Process subsets with corresponding audio", # noqa
|
||||
"order": 3},
|
||||
"sourceResolution": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Source resolution",
|
||||
"target": "tag",
|
||||
"toolTip": "Is resloution taken from timeline or source?", # noqa
|
||||
"order": 4},
|
||||
}
|
||||
},
|
||||
"frameRangeAttr": {
|
||||
"type": "section",
|
||||
"label": "Shot Attributes",
|
||||
"target": "ui",
|
||||
"order": 4,
|
||||
"value": {
|
||||
"workfileFrameStart": {
|
||||
"value": 1001,
|
||||
"type": "QSpinBox",
|
||||
"label": "Workfiles Start Frame",
|
||||
"target": "tag",
|
||||
"toolTip": "Set workfile starting frame number", # noqa
|
||||
"order": 0
|
||||
},
|
||||
"handleStart": {
|
||||
"value": 0,
|
||||
"type": "QSpinBox",
|
||||
"label": "Handle Start",
|
||||
"target": "tag",
|
||||
"toolTip": "Handle at start of clip", # noqa
|
||||
"order": 1
|
||||
},
|
||||
"handleEnd": {
|
||||
"value": 0,
|
||||
"type": "QSpinBox",
|
||||
"label": "Handle End",
|
||||
"target": "tag",
|
||||
"toolTip": "Handle at end of clip", # noqa
|
||||
"order": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
presets = None
|
||||
|
||||
def process(self):
|
||||
# get key pares from presets and match it on ui inputs
|
||||
for k, v in self.gui_inputs.items():
|
||||
if v["type"] in ("dict", "section"):
|
||||
# nested dictionary (only one level allowed
|
||||
# for sections and dict)
|
||||
for _k, _v in v["value"].items():
|
||||
if self.presets.get(_k):
|
||||
self.gui_inputs[k][
|
||||
"value"][_k]["value"] = self.presets[_k]
|
||||
if self.presets.get(k):
|
||||
self.gui_inputs[k]["value"] = self.presets[k]
|
||||
|
||||
# open widget for plugins inputs
|
||||
widget = self.widget(self.gui_name, self.gui_info, self.gui_inputs)
|
||||
widget.exec_()
|
||||
|
||||
if len(self.selected) < 1:
|
||||
return
|
||||
|
||||
if not widget.result:
|
||||
print("Operation aborted")
|
||||
return
|
||||
|
||||
self.rename_add = 0
|
||||
|
||||
# get ui output for track name for vertical sync
|
||||
v_sync_track = widget.result["vSyncTrack"]["value"]
|
||||
|
||||
# sort selected trackItems by
|
||||
sorted_selected_track_items = list()
|
||||
unsorted_selected_track_items = list()
|
||||
for _ti in self.selected:
|
||||
if _ti.parent().name() in v_sync_track:
|
||||
sorted_selected_track_items.append(_ti)
|
||||
else:
|
||||
unsorted_selected_track_items.append(_ti)
|
||||
|
||||
sorted_selected_track_items.extend(unsorted_selected_track_items)
|
||||
|
||||
kwargs = {
|
||||
"ui_inputs": widget.result,
|
||||
"avalon": self.data
|
||||
}
|
||||
|
||||
for i, track_item in enumerate(sorted_selected_track_items):
|
||||
self.rename_index = i
|
||||
|
||||
# convert track item to timeline media pool item
|
||||
phiero.PublishClip(self, track_item, **kwargs).convert()
|
||||
170
pype/hosts/hiero/plugins/load/load_clip.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from avalon import io, api
|
||||
import pype.hosts.hiero.api as phiero
|
||||
# from pype.hosts.hiero.api import plugin, lib
|
||||
# reload(lib)
|
||||
# reload(plugin)
|
||||
# reload(phiero)
|
||||
|
||||
|
||||
class LoadClip(phiero.SequenceLoader):
|
||||
"""Load a subset to timeline as clip
|
||||
|
||||
Place clip to timeline on its asset origin timings collected
|
||||
during conforming to project
|
||||
"""
|
||||
|
||||
families = ["render2d", "source", "plate", "render", "review"]
|
||||
representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"]
|
||||
|
||||
label = "Load as clip"
|
||||
order = -10
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
# for loader multiselection
|
||||
sequence = None
|
||||
track = None
|
||||
|
||||
# presets
|
||||
clip_color_last = "green"
|
||||
clip_color = "red"
|
||||
|
||||
def load(self, context, name, namespace, options):
|
||||
|
||||
# in case loader uses multiselection
|
||||
if self.track and self.sequence:
|
||||
options.update({
|
||||
"sequence": self.sequence,
|
||||
"track": self.track
|
||||
})
|
||||
|
||||
# load clip to timeline and get main variables
|
||||
track_item = phiero.ClipLoader(self, context, **options).load()
|
||||
namespace = namespace or track_item.name()
|
||||
version = context['version']
|
||||
version_data = version.get("data", {})
|
||||
version_name = version.get("name", None)
|
||||
colorspace = version_data.get("colorspace", None)
|
||||
object_name = "{}_{}".format(name, namespace)
|
||||
|
||||
# add additional metadata from the version to imprint Avalon knob
|
||||
add_keys = [
|
||||
"frameStart", "frameEnd", "source", "author",
|
||||
"fps", "handleStart", "handleEnd"
|
||||
]
|
||||
|
||||
# move all version data keys to tag data
|
||||
data_imprint = {}
|
||||
for key in add_keys:
|
||||
data_imprint.update({
|
||||
key: version_data.get(key, str(None))
|
||||
})
|
||||
|
||||
# add variables related to version context
|
||||
data_imprint.update({
|
||||
"version": version_name,
|
||||
"colorspace": colorspace,
|
||||
"objectName": object_name
|
||||
})
|
||||
|
||||
# update color of clip regarding the version order
|
||||
self.set_item_color(track_item, version)
|
||||
|
||||
# deal with multiselection
|
||||
self.multiselection(track_item)
|
||||
|
||||
self.log.info("Loader done: `{}`".format(name))
|
||||
|
||||
return phiero.containerise(
|
||||
track_item,
|
||||
name, namespace, context,
|
||||
self.__class__.__name__,
|
||||
data_imprint)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
""" Updating previously loaded clips
|
||||
"""
|
||||
|
||||
# load clip to timeline and get main variables
|
||||
name = container['name']
|
||||
namespace = container['namespace']
|
||||
track_item = phiero.get_track_items(
|
||||
track_item_name=namespace)
|
||||
version = io.find_one({
|
||||
"type": "version",
|
||||
"_id": representation["parent"]
|
||||
})
|
||||
version_data = version.get("data", {})
|
||||
version_name = version.get("name", None)
|
||||
colorspace = version_data.get("colorspace", None)
|
||||
object_name = "{}_{}".format(name, namespace)
|
||||
file = api.get_representation_path(representation).replace("\\", "/")
|
||||
|
||||
# reconnect media to new path
|
||||
track_item.source().reconnectMedia(file)
|
||||
|
||||
# add additional metadata from the version to imprint Avalon knob
|
||||
add_keys = [
|
||||
"frameStart", "frameEnd", "source", "author",
|
||||
"fps", "handleStart", "handleEnd"
|
||||
]
|
||||
|
||||
# move all version data keys to tag data
|
||||
data_imprint = {}
|
||||
for key in add_keys:
|
||||
data_imprint.update({
|
||||
key: version_data.get(key, str(None))
|
||||
})
|
||||
|
||||
# add variables related to version context
|
||||
data_imprint.update({
|
||||
"representation": str(representation["_id"]),
|
||||
"version": version_name,
|
||||
"colorspace": colorspace,
|
||||
"objectName": object_name
|
||||
})
|
||||
|
||||
# update color of clip regarding the version order
|
||||
self.set_item_color(track_item, version)
|
||||
|
||||
return phiero.update_container(track_item, data_imprint)
|
||||
|
||||
def remove(self, container):
|
||||
""" Removing previously loaded clips
|
||||
"""
|
||||
# load clip to timeline and get main variables
|
||||
namespace = container['namespace']
|
||||
track_item = phiero.get_track_items(
|
||||
track_item_name=namespace)
|
||||
track = track_item.parent()
|
||||
|
||||
# remove track item from track
|
||||
track.removeItem(track_item)
|
||||
|
||||
@classmethod
|
||||
def multiselection(cls, track_item):
|
||||
if not cls.track:
|
||||
cls.track = track_item.parent()
|
||||
cls.sequence = cls.track.parent()
|
||||
|
||||
@classmethod
|
||||
def set_item_color(cls, track_item, version):
|
||||
|
||||
# define version name
|
||||
version_name = version.get("name", None)
|
||||
# get all versions in list
|
||||
versions = io.find({
|
||||
"type": "version",
|
||||
"parent": version["parent"]
|
||||
}).distinct('name')
|
||||
|
||||
max_version = max(versions)
|
||||
|
||||
# set clip colour
|
||||
if version_name == max_version:
|
||||
track_item.source().binItem().setColor(cls.clip_color_last)
|
||||
else:
|
||||
track_item.source().binItem().setColor(cls.clip_color)
|
||||
|
|
@ -30,9 +30,12 @@ class CollectAssetBuilds(api.ContextPlugin):
|
|||
# Exclude non-tagged instances.
|
||||
tagged = False
|
||||
asset_names = []
|
||||
|
||||
for tag in instance.data["tags"]:
|
||||
family = dict(tag["metadata"]).get("tag.family", "")
|
||||
if family.lower() == "assetbuild":
|
||||
t_metadata = dict(tag.metadata())
|
||||
t_family = t_metadata.get("tag.family", "")
|
||||
|
||||
if t_family.lower() == "assetbuild":
|
||||
asset_names.append(tag["name"])
|
||||
tagged = True
|
||||
|
||||
38
pype/hosts/hiero/plugins/publish/collect_clip_resolution.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectClipResolution(pyblish.api.InstancePlugin):
|
||||
"""Collect clip geometry resolution"""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.1
|
||||
label = "Collect Clip Resoluton"
|
||||
hosts = ["hiero"]
|
||||
families = ["clip"]
|
||||
|
||||
def process(self, instance):
|
||||
sequence = instance.context.data['activeSequence']
|
||||
item = instance.data["item"]
|
||||
source_resolution = instance.data.get("sourceResolution", None)
|
||||
|
||||
resolution_width = int(sequence.format().width())
|
||||
resolution_height = int(sequence.format().height())
|
||||
pixel_aspect = sequence.format().pixelAspect()
|
||||
|
||||
# source exception
|
||||
if source_resolution:
|
||||
resolution_width = int(item.source().mediaSource().width())
|
||||
resolution_height = int(item.source().mediaSource().height())
|
||||
pixel_aspect = item.source().mediaSource().pixelAspect()
|
||||
|
||||
resolution_data = {
|
||||
"resolutionWidth": resolution_width,
|
||||
"resolutionHeight": resolution_height,
|
||||
"pixelAspect": pixel_aspect
|
||||
}
|
||||
# add to instacne data
|
||||
instance.data.update(resolution_data)
|
||||
|
||||
self.log.info("Resolution of instance '{}' is: {}".format(
|
||||
instance,
|
||||
resolution_data
|
||||
))
|
||||
70
pype/hosts/hiero/plugins/publish/collect_frame_ranges.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectFrameRanges(pyblish.api.InstancePlugin):
|
||||
""" Collect all framranges.
|
||||
"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Frame Ranges"
|
||||
hosts = ["hiero"]
|
||||
families = ["clip", "effect"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
data = dict()
|
||||
track_item = instance.data["item"]
|
||||
|
||||
# handles
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
|
||||
# source frame ranges
|
||||
source_in = int(track_item.sourceIn())
|
||||
source_out = int(track_item.sourceOut())
|
||||
source_in_h = int(source_in - handle_start)
|
||||
source_out_h = int(source_out + handle_end)
|
||||
|
||||
# timeline frame ranges
|
||||
clip_in = int(track_item.timelineIn())
|
||||
clip_out = int(track_item.timelineOut())
|
||||
clip_in_h = clip_in - handle_start
|
||||
clip_out_h = clip_out + handle_end
|
||||
|
||||
# durations
|
||||
clip_duration = (clip_out - clip_in) + 1
|
||||
clip_duration_h = clip_duration + (handle_start + handle_end)
|
||||
|
||||
# set frame start with tag or take it from timeline `startingFrame`
|
||||
frame_start = instance.data.get("workfileFrameStart")
|
||||
|
||||
if not frame_start:
|
||||
frame_start = clip_in
|
||||
|
||||
frame_end = frame_start + (clip_out - clip_in)
|
||||
|
||||
data.update({
|
||||
# media source frame range
|
||||
"sourceIn": source_in,
|
||||
"sourceOut": source_out,
|
||||
"sourceInH": source_in_h,
|
||||
"sourceOutH": source_out_h,
|
||||
|
||||
# timeline frame range
|
||||
"clipIn": clip_in,
|
||||
"clipOut": clip_out,
|
||||
"clipInH": clip_in_h,
|
||||
"clipOutH": clip_out_h,
|
||||
|
||||
# workfile frame range
|
||||
"frameStart": frame_start,
|
||||
"frameEnd": frame_end,
|
||||
|
||||
"clipDuration": clip_duration,
|
||||
"clipDurationH": clip_duration_h,
|
||||
|
||||
"fps": instance.context.data["fps"]
|
||||
})
|
||||
self.log.info("Frame range data for instance `{}` are: {}".format(
|
||||
instance, data))
|
||||
instance.data.update(data)
|
||||
116
pype/hosts/hiero/plugins/publish/collect_hierarchy_context.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import pyblish.api
|
||||
import avalon.api as avalon
|
||||
|
||||
|
||||
class CollectHierarchy(pyblish.api.ContextPlugin):
|
||||
"""Collecting hierarchy from `parents`.
|
||||
|
||||
present in `clip` family instances coming from the request json data file
|
||||
|
||||
It will add `hierarchical_context` into each instance for integrate
|
||||
plugins to be able to create needed parents for the context if they
|
||||
don't exist yet
|
||||
"""
|
||||
|
||||
label = "Collect Hierarchy"
|
||||
order = pyblish.api.CollectorOrder
|
||||
families = ["clip"]
|
||||
|
||||
def process(self, context):
|
||||
temp_context = {}
|
||||
project_name = avalon.Session["AVALON_PROJECT"]
|
||||
final_context = {}
|
||||
final_context[project_name] = {}
|
||||
final_context[project_name]['entity_type'] = 'Project'
|
||||
|
||||
for instance in context:
|
||||
self.log.info("Processing instance: `{}` ...".format(instance))
|
||||
|
||||
# shot data dict
|
||||
shot_data = {}
|
||||
families = instance.data.get("families")
|
||||
|
||||
# filter out all unepropriate instances
|
||||
if not instance.data["publish"]:
|
||||
continue
|
||||
if not families:
|
||||
continue
|
||||
# exclude other families then self.families with intersection
|
||||
if not set(self.families).intersection(families):
|
||||
continue
|
||||
|
||||
# exclude if not masterLayer True
|
||||
if not instance.data.get("masterLayer"):
|
||||
continue
|
||||
|
||||
# update families to include `shot` for hierarchy integration
|
||||
instance.data["families"] = families + ["shot"]
|
||||
|
||||
# get asset build data if any available
|
||||
shot_data["inputs"] = [
|
||||
x["_id"] for x in instance.data.get("assetbuilds", [])
|
||||
]
|
||||
|
||||
# suppose that all instances are Shots
|
||||
shot_data['entity_type'] = 'Shot'
|
||||
shot_data['tasks'] = instance.data.get("tasks") or []
|
||||
shot_data["comments"] = instance.data.get("comments", [])
|
||||
|
||||
shot_data['custom_attributes'] = {
|
||||
"handleStart": instance.data["handleStart"],
|
||||
"handleEnd": instance.data["handleEnd"],
|
||||
"frameStart": instance.data["frameStart"],
|
||||
"frameEnd": instance.data["frameEnd"],
|
||||
"clipIn": instance.data["clipIn"],
|
||||
"clipOut": instance.data["clipOut"],
|
||||
'fps': instance.context.data["fps"],
|
||||
"resolutionWidth": instance.data["resolutionWidth"],
|
||||
"resolutionHeight": instance.data["resolutionHeight"],
|
||||
"pixelAspect": instance.data["pixelAspect"]
|
||||
}
|
||||
|
||||
actual = {instance.data["asset"]: shot_data}
|
||||
|
||||
for parent in reversed(instance.data["parents"]):
|
||||
next_dict = {}
|
||||
parent_name = parent["entity_name"]
|
||||
next_dict[parent_name] = {}
|
||||
next_dict[parent_name]["entity_type"] = parent[
|
||||
"entity_type"].capitalize()
|
||||
next_dict[parent_name]["childs"] = actual
|
||||
actual = next_dict
|
||||
|
||||
temp_context = self._update_dict(temp_context, actual)
|
||||
|
||||
# skip if nothing for hierarchy available
|
||||
if not temp_context:
|
||||
return
|
||||
|
||||
final_context[project_name]['childs'] = temp_context
|
||||
|
||||
# adding hierarchy context to context
|
||||
context.data["hierarchyContext"] = final_context
|
||||
self.log.debug("context.data[hierarchyContext] is: {}".format(
|
||||
context.data["hierarchyContext"]))
|
||||
|
||||
def _update_dict(self, parent_dict, child_dict):
|
||||
"""
|
||||
Nesting each children into its parent.
|
||||
|
||||
Args:
|
||||
parent_dict (dict): parent dict wich should be nested with children
|
||||
child_dict (dict): children dict which should be injested
|
||||
"""
|
||||
|
||||
for key in parent_dict:
|
||||
if key in child_dict and isinstance(parent_dict[key], dict):
|
||||
child_dict[key] = self._update_dict(
|
||||
parent_dict[key], child_dict[key]
|
||||
)
|
||||
else:
|
||||
if parent_dict.get(key) and child_dict.get(key):
|
||||
continue
|
||||
else:
|
||||
child_dict[key] = parent_dict[key]
|
||||
|
||||
return child_dict
|
||||
|
|
@ -4,9 +4,12 @@ import pyblish.api
|
|||
class CollectHostVersion(pyblish.api.ContextPlugin):
|
||||
"""Inject the hosts version into context"""
|
||||
|
||||
order = pyblish.api.CollectorOrder
|
||||
label = "Collect Host and HostVersion"
|
||||
order = pyblish.api.CollectorOrder - 0.5
|
||||
|
||||
def process(self, context):
|
||||
import nuke
|
||||
import pyblish.api
|
||||
|
||||
context.set_data("host", pyblish.api.current_host())
|
||||
context.set_data('hostVersion', value=nuke.NUKE_VERSION_STRING)
|
||||
169
pype/hosts/hiero/plugins/publish/collect_plates.py
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
from pyblish import api
|
||||
import os
|
||||
import re
|
||||
import clique
|
||||
|
||||
|
||||
class CollectPlates(api.InstancePlugin):
|
||||
"""Collect plate representations.
|
||||
"""
|
||||
|
||||
# Run just before CollectSubsets
|
||||
order = api.CollectorOrder + 0.1020
|
||||
label = "Collect Plates"
|
||||
hosts = ["hiero"]
|
||||
families = ["plate"]
|
||||
|
||||
def process(self, instance):
|
||||
# add to representations
|
||||
if not instance.data.get("representations"):
|
||||
instance.data["representations"] = list()
|
||||
|
||||
self.main_clip = instance.data["item"]
|
||||
# get plate source attributes
|
||||
source_media = instance.data["sourceMedia"]
|
||||
source_path = instance.data["sourcePath"]
|
||||
source_first = instance.data["sourceFirst"]
|
||||
frame_start = instance.data["frameStart"]
|
||||
frame_end = instance.data["frameEnd"]
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
source_in = instance.data["sourceIn"]
|
||||
source_out = instance.data["sourceOut"]
|
||||
source_in_h = instance.data["sourceInH"]
|
||||
source_out_h = instance.data["sourceOutH"]
|
||||
|
||||
# define if review media is sequence
|
||||
is_sequence = bool(not source_media.singleFile())
|
||||
self.log.debug("is_sequence: {}".format(is_sequence))
|
||||
|
||||
file_dir = os.path.dirname(source_path)
|
||||
file = os.path.basename(source_path)
|
||||
ext = os.path.splitext(file)[-1]
|
||||
|
||||
# detect if sequence
|
||||
if not is_sequence:
|
||||
# is video file
|
||||
files = file
|
||||
else:
|
||||
files = list()
|
||||
spliter, padding = self.detect_sequence(file)
|
||||
self.log.debug("_ spliter, padding: {}, {}".format(
|
||||
spliter, padding))
|
||||
base_name = file.split(spliter)[0]
|
||||
|
||||
# define collection and calculate frame range
|
||||
collection = clique.Collection(
|
||||
base_name,
|
||||
ext,
|
||||
padding,
|
||||
set(range(
|
||||
int(source_first + source_in_h),
|
||||
int(source_first + source_out_h) + 1
|
||||
))
|
||||
)
|
||||
self.log.debug("_ collection: {}".format(collection))
|
||||
|
||||
real_files = os.listdir(file_dir)
|
||||
self.log.debug("_ real_files: {}".format(real_files))
|
||||
|
||||
# collect frames to repre files list
|
||||
self.handle_start_exclude = list()
|
||||
self.handle_end_exclude = list()
|
||||
for findex, item in enumerate(collection):
|
||||
if item not in real_files:
|
||||
self.log.debug("_ item: {}".format(item))
|
||||
test_index = findex + int(source_first + source_in_h)
|
||||
test_start = int(source_first + source_in)
|
||||
test_end = int(source_first + source_out)
|
||||
if (test_index < test_start):
|
||||
self.handle_start_exclude.append(test_index)
|
||||
elif (test_index > test_end):
|
||||
self.handle_end_exclude.append(test_index)
|
||||
continue
|
||||
files.append(item)
|
||||
|
||||
# change label
|
||||
instance.data["label"] = "{0} - ({1})".format(
|
||||
instance.data["label"], ext
|
||||
)
|
||||
|
||||
self.log.debug("Instance review: {}".format(instance.data["name"]))
|
||||
|
||||
# adding representation for review mov
|
||||
representation = {
|
||||
"files": files,
|
||||
"stagingDir": file_dir,
|
||||
"frameStart": frame_start - handle_start,
|
||||
"frameEnd": frame_end + handle_end,
|
||||
"name": ext[1:],
|
||||
"ext": ext[1:]
|
||||
}
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
self.version_data(instance)
|
||||
|
||||
self.log.debug(
|
||||
"Added representations: {}".format(
|
||||
instance.data["representations"]))
|
||||
|
||||
self.log.debug(
|
||||
"instance.data: {}".format(instance.data))
|
||||
|
||||
def version_data(self, instance):
|
||||
transfer_data = [
|
||||
"handleStart", "handleEnd", "sourceIn", "sourceOut",
|
||||
"frameStart", "frameEnd", "sourceInH", "sourceOutH",
|
||||
"clipIn", "clipOut", "clipInH", "clipOutH", "asset",
|
||||
"track"
|
||||
]
|
||||
|
||||
version_data = dict()
|
||||
# pass data to version
|
||||
version_data.update({k: instance.data[k] for k in transfer_data})
|
||||
|
||||
if 'version' in instance.data:
|
||||
version_data["version"] = instance.data["version"]
|
||||
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
|
||||
if self.handle_start_exclude:
|
||||
handle_start -= len(self.handle_start_exclude)
|
||||
|
||||
if self.handle_end_exclude:
|
||||
handle_end -= len(self.handle_end_exclude)
|
||||
|
||||
# add to data of representation
|
||||
version_data.update({
|
||||
"colorspace": self.main_clip.sourceMediaColourTransform(),
|
||||
"families": instance.data["families"],
|
||||
"subset": instance.data["subset"],
|
||||
"fps": instance.data["fps"],
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end
|
||||
})
|
||||
instance.data["versionData"] = version_data
|
||||
|
||||
def detect_sequence(self, file):
|
||||
""" Get identificating pater for image sequence
|
||||
|
||||
Can find file.0001.ext, file.%02d.ext, file.####.ext
|
||||
|
||||
Return:
|
||||
string: any matching sequence patern
|
||||
int: padding of sequnce numbering
|
||||
"""
|
||||
foundall = re.findall(
|
||||
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
|
||||
if foundall:
|
||||
found = sorted(list(set(foundall[0])))[-1]
|
||||
|
||||
if "%" in found:
|
||||
padding = int(re.findall(r"\d+", found)[-1])
|
||||
else:
|
||||
padding = len(found)
|
||||
|
||||
return found, padding
|
||||
else:
|
||||
return None, None
|
||||
261
pype/hosts/hiero/plugins/publish/collect_review.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
from pyblish import api
|
||||
import os
|
||||
import clique
|
||||
from pype.hosts.hiero.api import (
|
||||
is_overlapping, get_sequence_pattern_and_padding)
|
||||
|
||||
|
||||
class CollectReview(api.InstancePlugin):
|
||||
"""Collect review representation.
|
||||
"""
|
||||
|
||||
# Run just before CollectSubsets
|
||||
order = api.CollectorOrder + 0.1022
|
||||
label = "Collect Review"
|
||||
hosts = ["hiero"]
|
||||
families = ["review"]
|
||||
|
||||
def get_review_item(self, instance):
|
||||
"""
|
||||
Get review clip track item from review track name
|
||||
|
||||
Args:
|
||||
instance (obj): publishing instance
|
||||
|
||||
Returns:
|
||||
hiero.core.TrackItem: corresponding track item
|
||||
|
||||
Raises:
|
||||
Exception: description
|
||||
|
||||
"""
|
||||
review_track = instance.data.get("review")
|
||||
video_tracks = instance.context.data["videoTracks"]
|
||||
for track in video_tracks:
|
||||
if review_track not in track.name():
|
||||
continue
|
||||
for item in track.items():
|
||||
self.log.debug(item)
|
||||
if is_overlapping(item, self.main_clip):
|
||||
self.log.debug("Winner is: {}".format(item))
|
||||
break
|
||||
|
||||
# validate the clip is fully converted with review clip
|
||||
assert is_overlapping(
|
||||
item, self.main_clip, strict=True), (
|
||||
"Review clip not cowering fully "
|
||||
"the clip `{}`").format(self.main_clip.name())
|
||||
|
||||
return item
|
||||
|
||||
def process(self, instance):
|
||||
tags = ["review", "ftrackreview"]
|
||||
|
||||
# get reviewable item from `review` instance.data attribute
|
||||
self.main_clip = instance.data.get("item")
|
||||
self.rw_clip = self.get_review_item(instance)
|
||||
|
||||
# let user know there is missing review clip and convert instance
|
||||
# back as not reviewable
|
||||
assert self.rw_clip, "Missing reviewable clip for '{}'".format(
|
||||
self.main_clip.name()
|
||||
)
|
||||
|
||||
# add to representations
|
||||
if not instance.data.get("representations"):
|
||||
instance.data["representations"] = list()
|
||||
|
||||
# get review media main info
|
||||
rw_source = self.rw_clip.source().mediaSource()
|
||||
rw_source_duration = int(rw_source.duration())
|
||||
self.rw_source_path = rw_source.firstpath()
|
||||
rw_source_file_info = rw_source.fileinfos().pop()
|
||||
|
||||
# define if review media is sequence
|
||||
is_sequence = bool(not rw_source.singleFile())
|
||||
self.log.debug("is_sequence: {}".format(is_sequence))
|
||||
|
||||
# get handles
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
|
||||
# review timeline and source frame ranges
|
||||
rw_clip_in = int(self.rw_clip.timelineIn())
|
||||
rw_clip_out = int(self.rw_clip.timelineOut())
|
||||
self.rw_clip_source_in = int(self.rw_clip.sourceIn())
|
||||
self.rw_clip_source_out = int(self.rw_clip.sourceOut())
|
||||
rw_source_first = int(rw_source_file_info.startFrame())
|
||||
|
||||
# calculate delivery source_in and source_out
|
||||
# main_clip_timeline_in - review_item_timeline_in + 1
|
||||
main_clip_in = self.main_clip.timelineIn()
|
||||
main_clip_out = self.main_clip.timelineOut()
|
||||
|
||||
source_in_diff = main_clip_in - rw_clip_in
|
||||
source_out_diff = main_clip_out - rw_clip_out
|
||||
|
||||
if source_in_diff:
|
||||
self.rw_clip_source_in += source_in_diff
|
||||
if source_out_diff:
|
||||
self.rw_clip_source_out += source_out_diff
|
||||
|
||||
# review clip durations
|
||||
rw_clip_duration = (
|
||||
self.rw_clip_source_out - self.rw_clip_source_in) + 1
|
||||
rw_clip_duration_h = rw_clip_duration + (
|
||||
handle_start + handle_end)
|
||||
|
||||
# add created data to review item data
|
||||
instance.data["reviewItemData"] = {
|
||||
"mediaDuration": rw_source_duration
|
||||
}
|
||||
|
||||
file_dir = os.path.dirname(self.rw_source_path)
|
||||
file = os.path.basename(self.rw_source_path)
|
||||
ext = os.path.splitext(file)[-1]
|
||||
|
||||
# detect if sequence
|
||||
if not is_sequence:
|
||||
# is video file
|
||||
files = file
|
||||
else:
|
||||
files = list()
|
||||
spliter, padding = get_sequence_pattern_and_padding(file)
|
||||
self.log.debug("_ spliter, padding: {}, {}".format(
|
||||
spliter, padding))
|
||||
base_name = file.split(spliter)[0]
|
||||
|
||||
# define collection and calculate frame range
|
||||
collection = clique.Collection(base_name, ext, padding, set(range(
|
||||
int(rw_source_first + int(
|
||||
self.rw_clip_source_in - handle_start)),
|
||||
int(rw_source_first + int(
|
||||
self.rw_clip_source_out + handle_end) + 1))))
|
||||
self.log.debug("_ collection: {}".format(collection))
|
||||
|
||||
real_files = os.listdir(file_dir)
|
||||
self.log.debug("_ real_files: {}".format(real_files))
|
||||
|
||||
# collect frames to repre files list
|
||||
for item in collection:
|
||||
if item not in real_files:
|
||||
self.log.debug("_ item: {}".format(item))
|
||||
continue
|
||||
files.append(item)
|
||||
|
||||
# add prep tag
|
||||
tags.extend(["prep", "delete"])
|
||||
|
||||
# change label
|
||||
instance.data["label"] = "{0} - ({1})".format(
|
||||
instance.data["label"], ext
|
||||
)
|
||||
|
||||
self.log.debug("Instance review: {}".format(instance.data["name"]))
|
||||
|
||||
# adding representation for review mov
|
||||
representation = {
|
||||
"files": files,
|
||||
"stagingDir": file_dir,
|
||||
"frameStart": rw_source_first + self.rw_clip_source_in,
|
||||
"frameEnd": rw_source_first + self.rw_clip_source_out,
|
||||
"frameStartFtrack": int(
|
||||
self.rw_clip_source_in - handle_start),
|
||||
"frameEndFtrack": int(self.rw_clip_source_out + handle_end),
|
||||
"step": 1,
|
||||
"fps": instance.data["fps"],
|
||||
"name": "review",
|
||||
"tags": tags,
|
||||
"ext": ext[1:]
|
||||
}
|
||||
|
||||
if rw_source_duration > rw_clip_duration_h:
|
||||
self.log.debug("Media duration higher: {}".format(
|
||||
(rw_source_duration - rw_clip_duration_h)))
|
||||
representation.update({
|
||||
"frameStart": rw_source_first + int(
|
||||
self.rw_clip_source_in - handle_start),
|
||||
"frameEnd": rw_source_first + int(
|
||||
self.rw_clip_source_out + handle_end),
|
||||
"tags": ["_cut-bigger", "prep", "delete"]
|
||||
})
|
||||
elif rw_source_duration < rw_clip_duration_h:
|
||||
self.log.debug("Media duration higher: {}".format(
|
||||
(rw_source_duration - rw_clip_duration_h)))
|
||||
representation.update({
|
||||
"frameStart": rw_source_first + int(
|
||||
self.rw_clip_source_in - handle_start),
|
||||
"frameEnd": rw_source_first + int(
|
||||
self.rw_clip_source_out + handle_end),
|
||||
"tags": ["prep", "delete"]
|
||||
})
|
||||
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
self.create_thumbnail(instance)
|
||||
|
||||
self.log.debug(
|
||||
"Added representations: {}".format(
|
||||
instance.data["representations"]))
|
||||
|
||||
def create_thumbnail(self, instance):
|
||||
source_file = os.path.basename(self.rw_source_path)
|
||||
spliter, padding = get_sequence_pattern_and_padding(source_file)
|
||||
|
||||
if spliter:
|
||||
head, ext = source_file.split(spliter)
|
||||
else:
|
||||
head, ext = os.path.splitext(source_file)
|
||||
|
||||
# staging dir creation
|
||||
staging_dir = os.path.dirname(
|
||||
self.rw_source_path)
|
||||
|
||||
# get thumbnail frame from the middle
|
||||
thumb_frame = int(self.rw_clip_source_in + (
|
||||
(self.rw_clip_source_out - self.rw_clip_source_in) / 2))
|
||||
|
||||
thumb_file = "{}thumbnail{}{}".format(head, thumb_frame, ".png")
|
||||
thumb_path = os.path.join(staging_dir, thumb_file)
|
||||
|
||||
thumbnail = self.rw_clip.thumbnail(thumb_frame).save(
|
||||
thumb_path,
|
||||
format='png'
|
||||
)
|
||||
self.log.debug(
|
||||
"__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame))
|
||||
|
||||
self.log.debug("__ thumbnail: {}".format(thumbnail))
|
||||
thumb_representation = {
|
||||
'files': thumb_file,
|
||||
'stagingDir': staging_dir,
|
||||
'name': "thumbnail",
|
||||
'thumbnail': True,
|
||||
'ext': "png"
|
||||
}
|
||||
instance.data["representations"].append(
|
||||
thumb_representation)
|
||||
|
||||
def version_data(self, instance):
|
||||
transfer_data = [
|
||||
"handleStart", "handleEnd", "sourceIn", "sourceOut",
|
||||
"frameStart", "frameEnd", "sourceInH", "sourceOutH",
|
||||
"clipIn", "clipOut", "clipInH", "clipOutH", "asset",
|
||||
"track"
|
||||
]
|
||||
|
||||
version_data = dict()
|
||||
# pass data to version
|
||||
version_data.update({k: instance.data[k] for k in transfer_data})
|
||||
|
||||
if 'version' in instance.data:
|
||||
version_data["version"] = instance.data["version"]
|
||||
|
||||
# add to data of representation
|
||||
version_data.update({
|
||||
"colorspace": self.rw_clip.sourceMediaColourTransform(),
|
||||
"families": instance.data["families"],
|
||||
"subset": instance.data["subset"],
|
||||
"fps": instance.data["fps"]
|
||||
})
|
||||
instance.data["versionData"] = version_data
|
||||
|
|
@ -4,7 +4,7 @@ from pyblish import api
|
|||
class CollectClipTagTasks(api.InstancePlugin):
|
||||
"""Collect Tags from selected track items."""
|
||||
|
||||
order = api.CollectorOrder + 0.012
|
||||
order = api.CollectorOrder
|
||||
label = "Collect Tag Tasks"
|
||||
hosts = ["hiero"]
|
||||
families = ['clip']
|
||||
|
|
@ -14,8 +14,8 @@ class CollectClipTagTasks(api.InstancePlugin):
|
|||
tags = instance.data["tags"]
|
||||
|
||||
tasks = dict()
|
||||
for t in tags:
|
||||
t_metadata = dict(t["metadata"])
|
||||
for tag in tags:
|
||||
t_metadata = dict(tag.metadata())
|
||||
t_family = t_metadata.get("tag.family", "")
|
||||
|
||||
# gets only task family tags and collect labels
|
||||
|
|
@ -1,47 +1,44 @@
|
|||
from pyblish import api
|
||||
import os
|
||||
from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles
|
||||
import pyblish
|
||||
import pype
|
||||
|
||||
|
||||
class ExtractAudioFile(pype.api.Extractor):
|
||||
"""Extracts audio subset file"""
|
||||
"""Extracts audio subset file from all active timeline audio tracks"""
|
||||
|
||||
order = api.ExtractorOrder
|
||||
order = pyblish.api.ExtractorOrder
|
||||
label = "Extract Subset Audio"
|
||||
hosts = ["hiero"]
|
||||
families = ["clip", "audio"]
|
||||
match = api.Intersection
|
||||
families = ["audio"]
|
||||
match = pyblish.api.Intersection
|
||||
|
||||
def process(self, instance):
|
||||
import os
|
||||
|
||||
from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles
|
||||
|
||||
item = instance.data["item"]
|
||||
context = instance.context
|
||||
|
||||
self.log.debug("creating staging dir")
|
||||
self.staging_dir(instance)
|
||||
|
||||
staging_dir = instance.data["stagingDir"]
|
||||
# get sequence
|
||||
sequence = instance.context.data["activeSequence"]
|
||||
subset = instance.data["subset"]
|
||||
|
||||
# get timeline in / out
|
||||
clip_in = instance.data["clipIn"]
|
||||
clip_out = instance.data["clipOut"]
|
||||
# get handles from context
|
||||
handle_start = instance.data["handleStart"]
|
||||
handle_end = instance.data["handleEnd"]
|
||||
|
||||
# get sequence from context
|
||||
sequence = context.data["activeSequence"]
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Created staging dir: {}...".format(staging_dir))
|
||||
|
||||
# path to wav file
|
||||
audio_file = os.path.join(
|
||||
staging_dir, "{0}.wav".format(instance.data["subset"])
|
||||
staging_dir, "{}.wav".format(subset)
|
||||
)
|
||||
|
||||
# export audio to disk
|
||||
writeSequenceAudioWithHandles(
|
||||
audio_file,
|
||||
sequence,
|
||||
item.timelineIn(),
|
||||
item.timelineOut(),
|
||||
clip_in,
|
||||
clip_out,
|
||||
handle_start,
|
||||
handle_end
|
||||
)
|
||||
100
pype/hosts/hiero/plugins/publish/extract_clip_effects.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# from pype import plugins
|
||||
import os
|
||||
import json
|
||||
import pyblish.api
|
||||
import pype
|
||||
|
||||
|
||||
class ExtractClipEffects(pype.api.Extractor):
|
||||
"""Extract clip effects instances."""
|
||||
|
||||
order = pyblish.api.ExtractorOrder
|
||||
label = "Export Clip Effects"
|
||||
families = ["effect"]
|
||||
|
||||
def process(self, instance):
|
||||
item = instance.data["item"]
|
||||
effects = instance.data.get("effects")
|
||||
|
||||
# skip any without effects
|
||||
if not effects:
|
||||
return
|
||||
|
||||
subset = instance.data.get("subset")
|
||||
family = instance.data["family"]
|
||||
|
||||
self.log.debug("creating staging dir")
|
||||
staging_dir = self.staging_dir(instance)
|
||||
|
||||
transfers = list()
|
||||
if "transfers" not in instance.data:
|
||||
instance.data["transfers"] = list()
|
||||
|
||||
ext = "json"
|
||||
file = subset + "." + ext
|
||||
|
||||
# when instance is created during collection part
|
||||
resources_dir = instance.data["resourcesDir"]
|
||||
|
||||
# change paths in effects to files
|
||||
for k, effect in effects.items():
|
||||
if "assignTo" in k:
|
||||
continue
|
||||
trn = self.copy_linked_files(effect, resources_dir)
|
||||
if trn:
|
||||
transfers.append((trn[0], trn[1]))
|
||||
|
||||
instance.data["transfers"].extend(transfers)
|
||||
self.log.debug("_ transfers: `{}`".format(
|
||||
instance.data["transfers"]))
|
||||
|
||||
# create representations
|
||||
instance.data["representations"] = list()
|
||||
|
||||
transfer_data = [
|
||||
"handleStart", "handleEnd", "sourceIn", "sourceOut",
|
||||
"frameStart", "frameEnd", "sourceInH", "sourceOutH",
|
||||
"clipIn", "clipOut", "clipInH", "clipOutH", "asset", "track",
|
||||
"version"
|
||||
]
|
||||
|
||||
# pass data to version
|
||||
version_data = dict()
|
||||
version_data.update({k: instance.data[k] for k in transfer_data})
|
||||
|
||||
# add to data of representation
|
||||
version_data.update({
|
||||
"colorspace": item.sourceMediaColourTransform(),
|
||||
"colorspaceScript": instance.context.data["colorspace"],
|
||||
"families": [family, "plate"],
|
||||
"subset": subset,
|
||||
"fps": instance.context.data["fps"]
|
||||
})
|
||||
instance.data["versionData"] = version_data
|
||||
|
||||
representation = {
|
||||
'files': file,
|
||||
'stagingDir': staging_dir,
|
||||
'name': family + ext.title(),
|
||||
'ext': ext
|
||||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
self.log.debug("_ representations: `{}`".format(
|
||||
instance.data["representations"]))
|
||||
|
||||
self.log.debug("_ version_data: `{}`".format(
|
||||
instance.data["versionData"]))
|
||||
|
||||
with open(os.path.join(staging_dir, file), "w") as outfile:
|
||||
outfile.write(json.dumps(effects, indent=4, sort_keys=True))
|
||||
|
||||
def copy_linked_files(self, effect, dst_dir):
|
||||
for k, v in effect["node"].items():
|
||||
if k in "file" and v != '':
|
||||
base_name = os.path.basename(v)
|
||||
dst = os.path.join(dst_dir, base_name).replace("\\", "/")
|
||||
|
||||
# add it to the json
|
||||
effect["node"][k] = dst
|
||||
return (v, dst)
|
||||
|
|
@ -8,12 +8,11 @@ import clique
|
|||
from avalon.vendor import filelink
|
||||
|
||||
|
||||
class ExtractReviewCutUp(pype.api.Extractor):
|
||||
class ExtractReviewPreparation(pype.api.Extractor):
|
||||
"""Cut up clips from long video file"""
|
||||
|
||||
order = api.ExtractorOrder
|
||||
# order = api.CollectorOrder + 0.1023
|
||||
label = "Extract Review CutUp"
|
||||
label = "Extract Review Preparation"
|
||||
hosts = ["hiero"]
|
||||
families = ["review"]
|
||||
|
||||
|
|
@ -22,22 +21,18 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
|
||||
def process(self, instance):
|
||||
inst_data = instance.data
|
||||
asset = inst_data['asset']
|
||||
item = inst_data['item']
|
||||
event_number = int(item.eventNumber())
|
||||
asset = inst_data["asset"]
|
||||
review_item_data = instance.data.get("reviewItemData")
|
||||
|
||||
# get representation and loop them
|
||||
representations = inst_data["representations"]
|
||||
|
||||
# check if sequence
|
||||
is_sequence = inst_data["isSequence"]
|
||||
|
||||
# get resolution default
|
||||
resolution_width = inst_data["resolutionWidth"]
|
||||
resolution_height = inst_data["resolutionHeight"]
|
||||
|
||||
# frame range data
|
||||
media_duration = inst_data["mediaDuration"]
|
||||
media_duration = review_item_data["mediaDuration"]
|
||||
|
||||
ffmpeg_path = pype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
ffprobe_path = pype.lib.get_ffmpeg_tool_path("ffprobe")
|
||||
|
|
@ -52,7 +47,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
|
||||
# check if supported tags are in representation for activation
|
||||
filter_tag = False
|
||||
for tag in ["_cut-bigger", "_cut-smaller"]:
|
||||
for tag in ["_cut-bigger", "prep"]:
|
||||
if tag in tags:
|
||||
filter_tag = True
|
||||
break
|
||||
|
|
@ -70,7 +65,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
full_output_dir = os.path.join(
|
||||
staging_dir, "cuts")
|
||||
|
||||
if is_sequence:
|
||||
if isinstance(files, list):
|
||||
new_files = list()
|
||||
|
||||
# frame range delivery included handles
|
||||
|
|
@ -99,12 +94,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
index = 0
|
||||
for image in collection:
|
||||
dst_file_num = frame_start + index
|
||||
dst_file_name = "".join([
|
||||
str(event_number),
|
||||
head,
|
||||
str(padding % dst_file_num),
|
||||
tail
|
||||
])
|
||||
dst_file_name = head + str(padding % dst_file_num) + tail
|
||||
src = os.path.join(staging_dir, image)
|
||||
dst = os.path.join(full_output_dir, dst_file_name)
|
||||
self.log.info("Creating temp hardlinks: {}".format(dst))
|
||||
|
|
@ -142,7 +132,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
).format(**locals())
|
||||
|
||||
self.log.debug("ffprob_cmd: {}".format(ffprob_cmd))
|
||||
audio_check_output = pype.api.run_subprocess(ffprob_cmd)
|
||||
audio_check_output = pype.api.subprocess(ffprob_cmd)
|
||||
self.log.debug(
|
||||
"audio_check_output: {}".format(audio_check_output))
|
||||
|
||||
|
|
@ -177,7 +167,7 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
|
||||
# try to get video native resolution data
|
||||
try:
|
||||
resolution_output = pype.api.run_subprocess((
|
||||
resolution_output = pype.api.subprocess((
|
||||
"\"{ffprobe_path}\" -i \"{full_input_path}\""
|
||||
" -v error "
|
||||
"-select_streams v:0 -show_entries "
|
||||
|
|
@ -290,7 +280,8 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
|
||||
# run subprocess
|
||||
self.log.debug("Executing: {}".format(subprcs_cmd))
|
||||
pype.api.run_subprocess(subprcs_cmd, logger=self.log)
|
||||
output = pype.api.subprocess(subprcs_cmd)
|
||||
self.log.debug("Output: {}".format(output))
|
||||
|
||||
repre_new = {
|
||||
"files": new_files,
|
||||
|
|
@ -302,7 +293,8 @@ class ExtractReviewCutUp(pype.api.Extractor):
|
|||
"step": 1,
|
||||
"fps": fps,
|
||||
"name": "cut_up_preview",
|
||||
"tags": ["review"] + self.tags_addition,
|
||||
"tags": [
|
||||
"review", "ftrackreview", "delete"] + self.tags_addition,
|
||||
"ext": ext,
|
||||
"anatomy_template": "publish"
|
||||
}
|
||||
164
pype/hosts/hiero/plugins/publish/precollect_clip_effects.py
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import re
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class PreCollectClipEffects(pyblish.api.InstancePlugin):
|
||||
"""Collect soft effects instances."""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.508
|
||||
label = "Pre-collect Clip Effects Instances"
|
||||
families = ["clip"]
|
||||
|
||||
def process(self, instance):
|
||||
family = "effect"
|
||||
effects = {}
|
||||
review = instance.data.get("review")
|
||||
review_track_index = instance.context.data.get("reviewTrackIndex")
|
||||
item = instance.data["item"]
|
||||
|
||||
# frame range
|
||||
self.handle_start = instance.data["handleStart"]
|
||||
self.handle_end = instance.data["handleEnd"]
|
||||
self.clip_in = int(item.timelineIn())
|
||||
self.clip_out = int(item.timelineOut())
|
||||
self.clip_in_h = self.clip_in - self.handle_start
|
||||
self.clip_out_h = self.clip_out + self.handle_end
|
||||
|
||||
track = instance.data["trackItem"]
|
||||
track_index = track.trackIndex()
|
||||
tracks_effect_items = instance.context.data.get("tracksEffectItems")
|
||||
clip_effect_items = instance.data.get("clipEffectItems")
|
||||
|
||||
# add clips effects to track's:
|
||||
if clip_effect_items:
|
||||
tracks_effect_items[track_index] = clip_effect_items
|
||||
|
||||
# process all effects and devide them to instance
|
||||
for _track_index, sub_track_items in tracks_effect_items.items():
|
||||
# skip if track index is the same as review track index
|
||||
if review and review_track_index == _track_index:
|
||||
continue
|
||||
for sitem in sub_track_items:
|
||||
if not (track_index <= _track_index):
|
||||
continue
|
||||
|
||||
effect = self.add_effect(_track_index, sitem)
|
||||
|
||||
if effect:
|
||||
effects.update(effect)
|
||||
|
||||
# skip any without effects
|
||||
if not effects:
|
||||
return
|
||||
|
||||
subset = instance.data.get("subset")
|
||||
effects.update({"assignTo": subset})
|
||||
|
||||
subset_split = re.findall(r'[A-Z][^A-Z]*', subset)
|
||||
|
||||
if len(subset_split) > 0:
|
||||
root_name = subset.replace(subset_split[0], "")
|
||||
subset_split.insert(0, root_name.capitalize())
|
||||
|
||||
subset_split.insert(0, "effect")
|
||||
|
||||
name = "".join(subset_split)
|
||||
|
||||
# create new instance and inherit data
|
||||
data = {}
|
||||
for key, value in instance.data.items():
|
||||
if "clipEffectItems" in key:
|
||||
continue
|
||||
data[key] = value
|
||||
|
||||
# change names
|
||||
data["subset"] = name
|
||||
data["family"] = family
|
||||
data["families"] = [family]
|
||||
data["name"] = data["subset"] + "_" + data["asset"]
|
||||
data["label"] = "{} - {}".format(
|
||||
data['asset'], data["subset"]
|
||||
)
|
||||
data["effects"] = effects
|
||||
|
||||
# create new instance
|
||||
_instance = instance.context.create_instance(**data)
|
||||
self.log.info("Created instance `{}`".format(_instance))
|
||||
self.log.debug("instance.data `{}`".format(_instance.data))
|
||||
|
||||
def test_overlap(self, effect_t_in, effect_t_out):
|
||||
covering_exp = bool(
|
||||
(effect_t_in <= self.clip_in)
|
||||
and (effect_t_out >= self.clip_out)
|
||||
)
|
||||
overlaying_right_exp = bool(
|
||||
(effect_t_in < self.clip_out)
|
||||
and (effect_t_out >= self.clip_out)
|
||||
)
|
||||
overlaying_left_exp = bool(
|
||||
(effect_t_out > self.clip_in)
|
||||
and (effect_t_in <= self.clip_in)
|
||||
)
|
||||
|
||||
return any((
|
||||
covering_exp,
|
||||
overlaying_right_exp,
|
||||
overlaying_left_exp
|
||||
))
|
||||
|
||||
def add_effect(self, track_index, sitem):
|
||||
track = sitem.parentTrack().name()
|
||||
# node serialization
|
||||
node = sitem.node()
|
||||
node_serialized = self.node_serialisation(node)
|
||||
node_name = sitem.name()
|
||||
node_class = re.sub(r"\d+", "", node_name)
|
||||
# collect timelineIn/Out
|
||||
effect_t_in = int(sitem.timelineIn())
|
||||
effect_t_out = int(sitem.timelineOut())
|
||||
|
||||
if not self.test_overlap(effect_t_in, effect_t_out):
|
||||
return
|
||||
|
||||
self.log.debug("node_name: `{}`".format(node_name))
|
||||
|
||||
return {node_name: {
|
||||
"class": node_class,
|
||||
"timelineIn": effect_t_in,
|
||||
"timelineOut": effect_t_out,
|
||||
"subTrackIndex": sitem.subTrackIndex(),
|
||||
"trackIndex": track_index,
|
||||
"track": track,
|
||||
"node": node_serialized
|
||||
}}
|
||||
|
||||
def node_serialisation(self, node):
|
||||
node_serialized = {}
|
||||
|
||||
# adding ignoring knob keys
|
||||
_ignoring_keys = ['invert_mask', 'help', 'mask',
|
||||
'xpos', 'ypos', 'layer', 'process_mask', 'channel',
|
||||
'channels', 'maskChannelMask', 'maskChannelInput',
|
||||
'note_font', 'note_font_size', 'unpremult',
|
||||
'postage_stamp_frame', 'maskChannel', 'export_cc',
|
||||
'select_cccid', 'mix', 'version', 'matrix']
|
||||
|
||||
# loop trough all knobs and collect not ignored
|
||||
# and any with any value
|
||||
for knob in node.knobs().keys():
|
||||
# skip nodes in ignore keys
|
||||
if knob in _ignoring_keys:
|
||||
continue
|
||||
|
||||
# get animation if node is animated
|
||||
if node[knob].isAnimated():
|
||||
# grab animation including handles
|
||||
knob_anim = [node[knob].getValueAt(i)
|
||||
for i in range(
|
||||
self.clip_in_h, self.clip_in_h + 1)]
|
||||
|
||||
node_serialized[knob] = knob_anim
|
||||
else:
|
||||
node_serialized[knob] = node[knob].value()
|
||||
|
||||
return node_serialized
|
||||
221
pype/hosts/hiero/plugins/publish/precollect_instances.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
from compiler.ast import flatten
|
||||
from pyblish import api
|
||||
from pype.hosts.hiero import api as phiero
|
||||
import hiero
|
||||
# from pype.hosts.hiero.api import lib
|
||||
# reload(lib)
|
||||
# reload(phiero)
|
||||
|
||||
|
||||
class PreCollectInstances(api.ContextPlugin):
|
||||
"""Collect all Track items selection."""
|
||||
|
||||
order = api.CollectorOrder - 0.509
|
||||
label = "Pre-collect Instances"
|
||||
hosts = ["hiero"]
|
||||
|
||||
def process(self, context):
|
||||
track_items = phiero.get_track_items(
|
||||
selected=True, check_tagged=True, check_enabled=True)
|
||||
# only return enabled track items
|
||||
if not track_items:
|
||||
track_items = phiero.get_track_items(
|
||||
check_enabled=True, check_tagged=True)
|
||||
# get sequence and video tracks
|
||||
sequence = context.data["activeSequence"]
|
||||
tracks = sequence.videoTracks()
|
||||
|
||||
# add collection to context
|
||||
tracks_effect_items = self.collect_sub_track_items(tracks)
|
||||
|
||||
context.data["tracksEffectItems"] = tracks_effect_items
|
||||
|
||||
self.log.info(
|
||||
"Processing enabled track items: {}".format(len(track_items)))
|
||||
|
||||
for _ti in track_items:
|
||||
data = dict()
|
||||
clip = _ti.source()
|
||||
|
||||
# get clips subtracks and anotations
|
||||
annotations = self.clip_annotations(clip)
|
||||
subtracks = self.clip_subtrack(_ti)
|
||||
self.log.debug("Annotations: {}".format(annotations))
|
||||
self.log.debug(">> Subtracks: {}".format(subtracks))
|
||||
|
||||
# get pype tag data
|
||||
tag_parsed_data = phiero.get_track_item_pype_data(_ti)
|
||||
# self.log.debug(pformat(tag_parsed_data))
|
||||
|
||||
if not tag_parsed_data:
|
||||
continue
|
||||
|
||||
if tag_parsed_data.get("id") != "pyblish.avalon.instance":
|
||||
continue
|
||||
# add tag data to instance data
|
||||
data.update({
|
||||
k: v for k, v in tag_parsed_data.items()
|
||||
if k not in ("id", "applieswhole", "label")
|
||||
})
|
||||
|
||||
asset = tag_parsed_data["asset"]
|
||||
subset = tag_parsed_data["subset"]
|
||||
review = tag_parsed_data.get("review")
|
||||
audio = tag_parsed_data.get("audio")
|
||||
|
||||
# remove audio attribute from data
|
||||
data.pop("audio")
|
||||
|
||||
# insert family into families
|
||||
family = tag_parsed_data["family"]
|
||||
families = [str(f) for f in tag_parsed_data["families"]]
|
||||
families.insert(0, str(family))
|
||||
|
||||
track = _ti.parent()
|
||||
media_source = _ti.source().mediaSource()
|
||||
source_path = media_source.firstpath()
|
||||
file_head = media_source.filenameHead()
|
||||
file_info = media_source.fileinfos().pop()
|
||||
source_first_frame = int(file_info.startFrame())
|
||||
|
||||
# apply only for feview and master track instance
|
||||
if review:
|
||||
families += ["review", "ftrack"]
|
||||
|
||||
data.update({
|
||||
"name": "{} {} {}".format(asset, subset, families),
|
||||
"asset": asset,
|
||||
"item": _ti,
|
||||
"families": families,
|
||||
|
||||
# tags
|
||||
"tags": _ti.tags(),
|
||||
|
||||
# track item attributes
|
||||
"track": track.name(),
|
||||
"trackItem": track,
|
||||
|
||||
# version data
|
||||
"versionData": {
|
||||
"colorspace": _ti.sourceMediaColourTransform()
|
||||
},
|
||||
|
||||
# source attribute
|
||||
"source": source_path,
|
||||
"sourceMedia": media_source,
|
||||
"sourcePath": source_path,
|
||||
"sourceFileHead": file_head,
|
||||
"sourceFirst": source_first_frame,
|
||||
|
||||
# clip's effect
|
||||
"clipEffectItems": subtracks
|
||||
})
|
||||
|
||||
instance = context.create_instance(**data)
|
||||
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
|
||||
if audio:
|
||||
a_data = dict()
|
||||
|
||||
# add tag data to instance data
|
||||
a_data.update({
|
||||
k: v for k, v in tag_parsed_data.items()
|
||||
if k not in ("id", "applieswhole", "label")
|
||||
})
|
||||
|
||||
# create main attributes
|
||||
subset = "audioMain"
|
||||
family = "audio"
|
||||
families = ["clip", "ftrack"]
|
||||
families.insert(0, str(family))
|
||||
|
||||
name = "{} {} {}".format(asset, subset, families)
|
||||
|
||||
a_data.update({
|
||||
"name": name,
|
||||
"subset": subset,
|
||||
"asset": asset,
|
||||
"family": family,
|
||||
"families": families,
|
||||
"item": _ti,
|
||||
|
||||
# tags
|
||||
"tags": _ti.tags(),
|
||||
})
|
||||
|
||||
a_instance = context.create_instance(**a_data)
|
||||
self.log.info("Creating audio instance: {}".format(a_instance))
|
||||
|
||||
@staticmethod
|
||||
def clip_annotations(clip):
|
||||
"""
|
||||
Returns list of Clip's hiero.core.Annotation
|
||||
"""
|
||||
annotations = []
|
||||
subTrackItems = flatten(clip.subTrackItems())
|
||||
annotations += [item for item in subTrackItems if isinstance(
|
||||
item, hiero.core.Annotation)]
|
||||
return annotations
|
||||
|
||||
@staticmethod
|
||||
def clip_subtrack(clip):
|
||||
"""
|
||||
Returns list of Clip's hiero.core.SubTrackItem
|
||||
"""
|
||||
subtracks = []
|
||||
subTrackItems = flatten(clip.parent().subTrackItems())
|
||||
for item in subTrackItems:
|
||||
# avoid all anotation
|
||||
if isinstance(item, hiero.core.Annotation):
|
||||
continue
|
||||
# # avoid all not anaibled
|
||||
if not item.isEnabled():
|
||||
continue
|
||||
subtracks.append(item)
|
||||
return subtracks
|
||||
|
||||
@staticmethod
|
||||
def collect_sub_track_items(tracks):
|
||||
"""
|
||||
Returns dictionary with track index as key and list of subtracks
|
||||
"""
|
||||
# collect all subtrack items
|
||||
sub_track_items = dict()
|
||||
for track in tracks:
|
||||
items = track.items()
|
||||
|
||||
# skip if no clips on track > need track with effect only
|
||||
if items:
|
||||
continue
|
||||
|
||||
# skip all disabled tracks
|
||||
if not track.isEnabled():
|
||||
continue
|
||||
|
||||
track_index = track.trackIndex()
|
||||
_sub_track_items = flatten(track.subTrackItems())
|
||||
|
||||
# continue only if any subtrack items are collected
|
||||
if len(_sub_track_items) < 1:
|
||||
continue
|
||||
|
||||
enabled_sti = list()
|
||||
# loop all found subtrack items and check if they are enabled
|
||||
for _sti in _sub_track_items:
|
||||
# checking if not enabled
|
||||
if not _sti.isEnabled():
|
||||
continue
|
||||
if isinstance(_sti, hiero.core.Annotation):
|
||||
continue
|
||||
# collect the subtrack item
|
||||
enabled_sti.append(_sti)
|
||||
|
||||
# continue only if any subtrack items are collected
|
||||
if len(enabled_sti) < 1:
|
||||
continue
|
||||
|
||||
# add collection of subtrackitems to dict
|
||||
sub_track_items[track_index] = enabled_sti
|
||||
|
||||
return sub_track_items
|
||||
74
pype/hosts/hiero/plugins/publish/precollect_workfile.py
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from pype.hosts.hiero import api as phiero
|
||||
from avalon import api as avalon
|
||||
|
||||
|
||||
class PreCollectWorkfile(pyblish.api.ContextPlugin):
|
||||
"""Inject the current working file into context"""
|
||||
|
||||
label = "Pre-collect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.51
|
||||
|
||||
def process(self, context):
|
||||
asset = avalon.Session["AVALON_ASSET"]
|
||||
subset = "workfile"
|
||||
|
||||
project = phiero.get_current_project()
|
||||
active_sequence = phiero.get_current_sequence()
|
||||
video_tracks = active_sequence.videoTracks()
|
||||
audio_tracks = active_sequence.audioTracks()
|
||||
current_file = project.path()
|
||||
staging_dir = os.path.dirname(current_file)
|
||||
base_name = os.path.basename(current_file)
|
||||
|
||||
# get workfile's colorspace properties
|
||||
_clrs = {}
|
||||
_clrs["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() # noqa
|
||||
_clrs["lutSetting16Bit"] = project.lutSetting16Bit()
|
||||
_clrs["lutSetting8Bit"] = project.lutSetting8Bit()
|
||||
_clrs["lutSettingFloat"] = project.lutSettingFloat()
|
||||
_clrs["lutSettingLog"] = project.lutSettingLog()
|
||||
_clrs["lutSettingViewer"] = project.lutSettingViewer()
|
||||
_clrs["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace()
|
||||
_clrs["lutUseOCIOForExport"] = project.lutUseOCIOForExport()
|
||||
_clrs["ocioConfigName"] = project.ocioConfigName()
|
||||
_clrs["ocioConfigPath"] = project.ocioConfigPath()
|
||||
|
||||
# set main project attributes to context
|
||||
context.data["activeProject"] = project
|
||||
context.data["activeSequence"] = active_sequence
|
||||
context.data["videoTracks"] = video_tracks
|
||||
context.data["audioTracks"] = audio_tracks
|
||||
context.data["currentFile"] = current_file
|
||||
context.data["colorspace"] = _clrs
|
||||
|
||||
self.log.info("currentFile: {}".format(current_file))
|
||||
|
||||
# creating workfile representation
|
||||
representation = {
|
||||
'name': 'hrox',
|
||||
'ext': 'hrox',
|
||||
'files': base_name,
|
||||
"stagingDir": staging_dir,
|
||||
}
|
||||
|
||||
instance_data = {
|
||||
"name": "{}_{}".format(asset, subset),
|
||||
"asset": asset,
|
||||
"subset": "{}{}".format(asset, subset.capitalize()),
|
||||
"item": project,
|
||||
"family": "workfile",
|
||||
|
||||
# version data
|
||||
"versionData": {
|
||||
"colorspace": _clrs
|
||||
},
|
||||
|
||||
# source attribute
|
||||
"sourcePath": current_file,
|
||||
"representations": [representation]
|
||||
}
|
||||
|
||||
instance = context.create_instance(**instance_data)
|
||||
self.log.info("Creating instance: {}".format(instance))
|
||||
25
pype/hosts/hiero/plugins/publish/validate_audio.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import pyblish
|
||||
from pype.hosts.hiero.api import is_overlapping
|
||||
|
||||
|
||||
class ValidateAudioFile(pyblish.api.InstancePlugin):
|
||||
"""Validate audio subset has avilable audio track clips"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Audio Tracks"
|
||||
hosts = ["hiero"]
|
||||
families = ["audio"]
|
||||
|
||||
def process(self, instance):
|
||||
clip = instance.data["item"]
|
||||
audio_tracks = instance.context.data["audioTracks"]
|
||||
audio_clip = None
|
||||
|
||||
for a_track in audio_tracks:
|
||||
for item in a_track.items():
|
||||
if is_overlapping(item, clip):
|
||||
audio_clip = item
|
||||
|
||||
assert audio_clip, "Missing relative audio clip for clip {}".format(
|
||||
clip.name()
|
||||
)
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
pype/hosts/hiero/startup/Icons/pype_icon.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
|
@ -2,11 +2,11 @@ import traceback
|
|||
|
||||
# activate hiero from pype
|
||||
import avalon.api
|
||||
import pype.hosts.hiero
|
||||
avalon.api.install(pype.hosts.hiero)
|
||||
import pype.hosts.hiero.api as phiero
|
||||
avalon.api.install(phiero)
|
||||
|
||||
try:
|
||||
__import__("pype.hosts.hiero")
|
||||
__import__("pype.hosts.hiero.api")
|
||||
__import__("pyblish")
|
||||
|
||||
except ImportError as e:
|
||||
|
|
@ -15,5 +15,5 @@ except ImportError as e:
|
|||
|
||||
else:
|
||||
# Setup integration
|
||||
import pype.hosts.hiero.lib
|
||||
pype.hosts.hiero.lib.setup()
|
||||
import pype.hosts.hiero.api as phiero
|
||||
phiero.lib.setup()
|
||||