mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Merge branch '2.x/develop' into develop
This commit is contained in:
commit
7e07bf2892
48 changed files with 1129 additions and 292 deletions
|
|
@ -83,6 +83,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
"textures",
|
||||
"action",
|
||||
"harmony.template",
|
||||
"harmony.palette",
|
||||
"editorial"
|
||||
]
|
||||
exclude_families = ["clip"]
|
||||
|
|
@ -605,7 +606,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
"type": "subset",
|
||||
"name": subset_name,
|
||||
"data": {
|
||||
"families": instance.data.get('families')
|
||||
"families": instance.data.get("families", [])
|
||||
},
|
||||
"parent": asset["_id"]
|
||||
}).inserted_id
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class ValidateContainers(pyblish.api.ContextPlugin):
|
|||
|
||||
label = "Validate Containers"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
hosts = ["maya", "houdini", "nuke"]
|
||||
hosts = ["maya", "houdini", "nuke", "harmony", "photoshop"]
|
||||
optional = True
|
||||
actions = [ShowInventory]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import os
|
||||
import uuid
|
||||
|
||||
import clique
|
||||
|
||||
from avalon import api, harmony
|
||||
import pype.lib
|
||||
|
||||
copy_files = """function copyFile(srcFilename, dstFilename)
|
||||
{
|
||||
|
|
@ -98,33 +100,63 @@ function import_files(args)
|
|||
transparencyModeAttr.setValue(SGITransparencyMode);
|
||||
if (extension == "psd")
|
||||
transparencyModeAttr.setValue(FlatPSDTransparencyMode);
|
||||
if (extension == "jpg")
|
||||
transparencyModeAttr.setValue(LayeredPSDTransparencyMode);
|
||||
|
||||
node.linkAttr(read, "DRAWING.ELEMENT", uniqueColumnName);
|
||||
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
if (files.length == 1)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
Drawing.create(elemId, 1, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
var drawingFilePath = Drawing.filename(elemId, "1");
|
||||
copyFile(files[0], drawingFilePath);
|
||||
// Expose the image for the entire frame range.
|
||||
for( var i =0; i <= frame.numberOf() - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
column.setEntry(uniqueColumnName, 1, timing, "1");
|
||||
}
|
||||
} else {
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(uniqueColumnName, 1, timing, timing.toString());
|
||||
column.setEntry(uniqueColumnName, 1, timing, timing.toString());
|
||||
}
|
||||
}
|
||||
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
node.setColor(read, green_color);
|
||||
|
||||
return read;
|
||||
}
|
||||
import_files
|
||||
"""
|
||||
|
||||
replace_files = """function replace_files(args)
|
||||
replace_files = """var PNGTransparencyMode = 0; //Premultiplied wih Black
|
||||
var TGATransparencyMode = 0; //Premultiplied wih Black
|
||||
var SGITransparencyMode = 0; //Premultiplied wih Black
|
||||
var LayeredPSDTransparencyMode = 1; //Straight
|
||||
var FlatPSDTransparencyMode = 2; //Premultiplied wih White
|
||||
|
||||
function replace_files(args)
|
||||
{
|
||||
var files = args[0];
|
||||
MessageLog.trace(files);
|
||||
MessageLog.trace(files.length);
|
||||
var _node = args[1];
|
||||
var start_frame = args[2];
|
||||
|
||||
var _column = node.linkedColumn(_node, "DRAWING.ELEMENT");
|
||||
var elemId = column.getElementIdOfDrawing(_column);
|
||||
|
||||
// Delete existing drawings.
|
||||
var timings = column.getDrawingTimings(_column);
|
||||
|
|
@ -133,20 +165,62 @@ replace_files = """function replace_files(args)
|
|||
column.deleteDrawingAt(_column, parseInt(timings[i]));
|
||||
}
|
||||
|
||||
// Create new drawings.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(node.getElementId(_node), timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(
|
||||
node.getElementId(_node), timing.toString()
|
||||
);
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(_column, 1, timing, timing.toString());
|
||||
var filename = files[0];
|
||||
var pos = filename.lastIndexOf(".");
|
||||
if( pos < 0 )
|
||||
return null;
|
||||
var extension = filename.substr(pos+1).toLowerCase();
|
||||
|
||||
if(extension == "jpeg")
|
||||
extension = "jpg";
|
||||
|
||||
var transparencyModeAttr = node.getAttr(
|
||||
_node, frame.current(), "applyMatteToColor"
|
||||
);
|
||||
if (extension == "png")
|
||||
transparencyModeAttr.setValue(PNGTransparencyMode);
|
||||
if (extension == "tga")
|
||||
transparencyModeAttr.setValue(TGATransparencyMode);
|
||||
if (extension == "sgi")
|
||||
transparencyModeAttr.setValue(SGITransparencyMode);
|
||||
if (extension == "psd")
|
||||
transparencyModeAttr.setValue(FlatPSDTransparencyMode);
|
||||
if (extension == "jpg")
|
||||
transparencyModeAttr.setValue(LayeredPSDTransparencyMode);
|
||||
|
||||
if (files.length == 1)
|
||||
{
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, 1, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, "1");
|
||||
copyFile(files[0], drawingFilePath);
|
||||
MessageLog.trace(files[0]);
|
||||
MessageLog.trace(drawingFilePath);
|
||||
// Expose the image for the entire frame range.
|
||||
for( var i =0; i <= frame.numberOf() - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
column.setEntry(_column, 1, timing, "1");
|
||||
}
|
||||
} else {
|
||||
// Create a drawing for each file.
|
||||
for( var i =0; i <= files.length - 1; ++i)
|
||||
{
|
||||
timing = start_frame + i
|
||||
// Create a drawing drawing, 'true' indicate that the file exists.
|
||||
Drawing.create(elemId, timing, true);
|
||||
// Get the actual path, in tmp folder.
|
||||
var drawingFilePath = Drawing.filename(elemId, timing.toString());
|
||||
copyFile( files[i], drawingFilePath );
|
||||
|
||||
column.setEntry(_column, 1, timing, timing.toString());
|
||||
}
|
||||
}
|
||||
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
node.setColor(_node, green_color);
|
||||
}
|
||||
replace_files
|
||||
"""
|
||||
|
|
@ -156,8 +230,8 @@ class ImageSequenceLoader(api.Loader):
|
|||
"""Load images
|
||||
Stores the imported asset in a container named after the asset.
|
||||
"""
|
||||
families = ["shot", "render"]
|
||||
representations = ["jpeg", "png"]
|
||||
families = ["shot", "render", "image"]
|
||||
representations = ["jpeg", "png", "jpg"]
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
|
|
@ -165,20 +239,29 @@ class ImageSequenceLoader(api.Loader):
|
|||
os.listdir(os.path.dirname(self.fname))
|
||||
)
|
||||
files = []
|
||||
for f in list(collections[0]):
|
||||
if collections:
|
||||
for f in list(collections[0]):
|
||||
files.append(
|
||||
os.path.join(
|
||||
os.path.dirname(self.fname), f
|
||||
).replace("\\", "/")
|
||||
)
|
||||
else:
|
||||
files.append(
|
||||
os.path.join(os.path.dirname(self.fname), f).replace("\\", "/")
|
||||
os.path.join(
|
||||
os.path.dirname(self.fname), remainder[0]
|
||||
).replace("\\", "/")
|
||||
)
|
||||
|
||||
name = context["subset"]["name"]
|
||||
name += "_{}".format(uuid.uuid4())
|
||||
read_node = harmony.send(
|
||||
{
|
||||
"function": copy_files + import_files,
|
||||
"args": ["Top", files, context["subset"]["name"], 1]
|
||||
"args": ["Top", files, name, 1]
|
||||
}
|
||||
)["result"]
|
||||
|
||||
self[:] = [read_node]
|
||||
|
||||
return harmony.containerise(
|
||||
name,
|
||||
namespace,
|
||||
|
|
@ -188,17 +271,25 @@ class ImageSequenceLoader(api.Loader):
|
|||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
node = container.pop("node")
|
||||
node = harmony.find_node_by_name(container["name"], "READ")
|
||||
|
||||
path = api.get_representation_path(representation)
|
||||
collections, remainder = clique.assemble(
|
||||
os.listdir(
|
||||
os.path.dirname(api.get_representation_path(representation))
|
||||
)
|
||||
os.listdir(os.path.dirname(path))
|
||||
)
|
||||
files = []
|
||||
for f in list(collections[0]):
|
||||
if collections:
|
||||
for f in list(collections[0]):
|
||||
files.append(
|
||||
os.path.join(
|
||||
os.path.dirname(path), f
|
||||
).replace("\\", "/")
|
||||
)
|
||||
else:
|
||||
files.append(
|
||||
os.path.join(os.path.dirname(self.fname), f).replace("\\", "/")
|
||||
os.path.join(
|
||||
os.path.dirname(path), remainder[0]
|
||||
).replace("\\", "/")
|
||||
)
|
||||
|
||||
harmony.send(
|
||||
|
|
@ -208,12 +299,34 @@ class ImageSequenceLoader(api.Loader):
|
|||
}
|
||||
)
|
||||
|
||||
# Colour node.
|
||||
func = """function func(args){
|
||||
for( var i =0; i <= args[0].length - 1; ++i)
|
||||
{
|
||||
var red_color = new ColorRGBA(255, 0, 0, 255);
|
||||
var green_color = new ColorRGBA(0, 255, 0, 255);
|
||||
if (args[1] == "red"){
|
||||
node.setColor(args[0], red_color);
|
||||
}
|
||||
if (args[1] == "green"){
|
||||
node.setColor(args[0], green_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
func
|
||||
"""
|
||||
if pype.lib.is_latest(representation):
|
||||
harmony.send({"function": func, "args": [node, "green"]})
|
||||
else:
|
||||
harmony.send({"function": func, "args": [node, "red"]})
|
||||
|
||||
harmony.imprint(
|
||||
node, {"representation": str(representation["_id"])}
|
||||
)
|
||||
|
||||
def remove(self, container):
|
||||
node = container.pop("node")
|
||||
node = harmony.find_node_by_name(container["name"], "READ")
|
||||
|
||||
func = """function deleteNode(_node)
|
||||
{
|
||||
node.deleteNode(_node, true, true);
|
||||
|
|
|
|||
66
pype/plugins/harmony/load/load_palette.py
Normal file
66
pype/plugins/harmony/load/load_palette.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from avalon import api, harmony
|
||||
from avalon.vendor import Qt
|
||||
|
||||
|
||||
class ImportPaletteLoader(api.Loader):
|
||||
"""Import palettes."""
|
||||
|
||||
families = ["harmony.palette"]
|
||||
representations = ["plt"]
|
||||
label = "Import Palette"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
name = self.load_palette(context["representation"])
|
||||
|
||||
return harmony.containerise(
|
||||
name,
|
||||
namespace,
|
||||
name,
|
||||
context,
|
||||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def load_palette(self, representation):
|
||||
subset_name = representation["context"]["subset"]
|
||||
name = subset_name.replace("palette", "")
|
||||
|
||||
# Overwrite palette on disk.
|
||||
scene_path = harmony.send(
|
||||
{"function": "scene.currentProjectPath"}
|
||||
)["result"]
|
||||
src = api.get_representation_path(representation)
|
||||
dst = os.path.join(
|
||||
scene_path,
|
||||
"palette-library",
|
||||
"{}.plt".format(name)
|
||||
)
|
||||
shutil.copy(src, dst)
|
||||
|
||||
harmony.save_scene()
|
||||
|
||||
# Dont allow instances with the same name.
|
||||
message_box = Qt.QtWidgets.QMessageBox()
|
||||
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg = "Updated {}.".format(subset_name)
|
||||
msg += " You need to reload the scene to see the changes."
|
||||
message_box.setText(msg)
|
||||
message_box.exec_()
|
||||
|
||||
return name
|
||||
|
||||
def remove(self, container):
|
||||
harmony.remove(container["name"])
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def update(self, container, representation):
|
||||
self.remove(container)
|
||||
name = self.load_palette(representation)
|
||||
|
||||
container["representation"] = str(representation["_id"])
|
||||
container["name"] = name
|
||||
harmony.imprint(name, container)
|
||||
|
|
@ -40,5 +40,5 @@ class ImportWorkfileLoader(ImportTemplateLoader):
|
|||
"""Import workfiles."""
|
||||
|
||||
families = ["workfile"]
|
||||
representations = ["*"]
|
||||
representations = ["zip"]
|
||||
label = "Import Workfile"
|
||||
|
|
|
|||
45
pype/plugins/harmony/publish/collect_palettes.py
Normal file
45
pype/plugins/harmony/publish/collect_palettes.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
import pyblish.api
|
||||
from avalon import harmony
|
||||
|
||||
|
||||
class CollectPalettes(pyblish.api.ContextPlugin):
|
||||
"""Gather palettes from scene when publishing templates."""
|
||||
|
||||
label = "Palettes"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["harmony"]
|
||||
|
||||
def process(self, context):
|
||||
func = """function func()
|
||||
{
|
||||
var palette_list = PaletteObjectManager.getScenePaletteList();
|
||||
|
||||
var palettes = {};
|
||||
for(var i=0; i < palette_list.numPalettes; ++i)
|
||||
{
|
||||
var palette = palette_list.getPaletteByIndex(i);
|
||||
palettes[palette.getName()] = palette.id;
|
||||
}
|
||||
|
||||
return palettes;
|
||||
}
|
||||
func
|
||||
"""
|
||||
palettes = harmony.send({"function": func})["result"]
|
||||
|
||||
for name, id in palettes.items():
|
||||
instance = context.create_instance(name)
|
||||
instance.data.update({
|
||||
"id": id,
|
||||
"family": "harmony.palette",
|
||||
"asset": os.environ["AVALON_ASSET"],
|
||||
"subset": "palette" + name
|
||||
})
|
||||
self.log.info(
|
||||
"Created instance:\n" + json.dumps(
|
||||
instance.data, sort_keys=True, indent=4
|
||||
)
|
||||
)
|
||||
34
pype/plugins/harmony/publish/extract_palette.py
Normal file
34
pype/plugins/harmony/publish/extract_palette.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import os
|
||||
|
||||
from avalon import harmony
|
||||
import pype.api
|
||||
import pype.hosts.harmony
|
||||
|
||||
|
||||
class ExtractPalette(pype.api.Extractor):
|
||||
"""Extract palette."""
|
||||
|
||||
label = "Extract Palette"
|
||||
hosts = ["harmony"]
|
||||
families = ["harmony.palette"]
|
||||
|
||||
def process(self, instance):
|
||||
func = """function func(args)
|
||||
{
|
||||
var palette_list = PaletteObjectManager.getScenePaletteList();
|
||||
var palette = palette_list.getPaletteById(args[0]);
|
||||
return (palette.getPath() + "/" + palette.getName() + ".plt");
|
||||
}
|
||||
func
|
||||
"""
|
||||
palette_file = harmony.send(
|
||||
{"function": func, "args": [instance.data["id"]]}
|
||||
)["result"]
|
||||
|
||||
representation = {
|
||||
"name": "plt",
|
||||
"ext": "plt",
|
||||
"files": os.path.basename(palette_file),
|
||||
"stagingDir": os.path.dirname(palette_file)
|
||||
}
|
||||
instance.data["representations"] = [representation]
|
||||
|
|
@ -50,8 +50,11 @@ class ImagePlaneLoader(api.Loader):
|
|||
|
||||
camera = selection[0]
|
||||
|
||||
camera.displayResolution.set(1)
|
||||
camera.farClipPlane.set(image_plane_depth * 10)
|
||||
try:
|
||||
camera.displayResolution.set(1)
|
||||
camera.farClipPlane.set(image_plane_depth * 10)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Create image plane
|
||||
image_plane_transform, image_plane_shape = pc.imagePlane(
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import pyblish.api
|
|||
import pype.api
|
||||
|
||||
|
||||
class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
||||
class ValidateKnobs(pyblish.api.ContextPlugin):
|
||||
"""Ensure knobs are consistent.
|
||||
|
||||
Knobs to validate and their values comes from the
|
||||
|
||||
Example for presets in config:
|
||||
"presets/plugins/nuke/publish.json" preset, which needs this structure:
|
||||
"ValidateNukeWriteKnobs": {
|
||||
"ValidateKnobs": {
|
||||
"enabled": true,
|
||||
"knobs": {
|
||||
"family": {
|
||||
|
|
@ -22,22 +22,31 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = "Validate Write Knobs"
|
||||
label = "Validate Knobs"
|
||||
hosts = ["nuke"]
|
||||
actions = [pype.api.RepairContextAction]
|
||||
optional = True
|
||||
|
||||
def process(self, context):
|
||||
# Check for preset existence.
|
||||
if not getattr(self, "knobs"):
|
||||
nuke_presets = context.data["presets"].get("nuke")
|
||||
|
||||
if not nuke_presets:
|
||||
return
|
||||
|
||||
publish_presets = nuke_presets.get("publish")
|
||||
|
||||
if not publish_presets:
|
||||
return
|
||||
|
||||
plugin_preset = publish_presets.get("ValidateKnobs")
|
||||
|
||||
if not plugin_preset:
|
||||
return
|
||||
|
||||
self.log.debug("__ self.knobs: {}".format(self.knobs))
|
||||
|
||||
invalid = self.get_invalid(context, compute=True)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Found knobs with invalid values: {}".format(invalid)
|
||||
"Found knobs with invalid values:\n{}".format(invalid)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
@ -51,6 +60,8 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
@classmethod
|
||||
def get_invalid_knobs(cls, context):
|
||||
invalid_knobs = []
|
||||
publish_presets = context.data["presets"]["nuke"]["publish"]
|
||||
knobs_preset = publish_presets["ValidateKnobs"]["knobs"]
|
||||
for instance in context:
|
||||
# Filter publisable instances.
|
||||
if not instance.data["publish"]:
|
||||
|
|
@ -59,15 +70,15 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
families += instance.data.get("families", [])
|
||||
families = list(set(families) & set(cls.knobs.keys()))
|
||||
families = list(set(families) & set(knobs_preset.keys()))
|
||||
if not families:
|
||||
continue
|
||||
|
||||
# Get all knobs to validate.
|
||||
knobs = {}
|
||||
for family in families:
|
||||
for preset in cls.knobs[family]:
|
||||
knobs.update({preset: cls.knobs[family][preset]})
|
||||
for preset in knobs_preset[family]:
|
||||
knobs.update({preset: knobs_preset[family][preset]})
|
||||
|
||||
# Get invalid knobs.
|
||||
nodes = []
|
||||
|
|
@ -82,16 +93,20 @@ class ValidateNukeWriteKnobs(pyblish.api.ContextPlugin):
|
|||
|
||||
for node in nodes:
|
||||
for knob in node.knobs():
|
||||
if knob in knobs.keys():
|
||||
expected = knobs[knob]
|
||||
if node[knob].value() != expected:
|
||||
invalid_knobs.append(
|
||||
{
|
||||
"knob": node[knob],
|
||||
"expected": expected,
|
||||
"current": node[knob].value()
|
||||
}
|
||||
)
|
||||
if knob not in knobs.keys():
|
||||
continue
|
||||
|
||||
expected = knobs[knob]
|
||||
if node[knob].value() != expected:
|
||||
invalid_knobs.append(
|
||||
{
|
||||
"knob": node[knob],
|
||||
"name": node[knob].name(),
|
||||
"label": node[knob].label(),
|
||||
"expected": expected,
|
||||
"current": node[knob].value()
|
||||
}
|
||||
)
|
||||
|
||||
context.data["invalid_knobs"] = invalid_knobs
|
||||
return invalid_knobs
|
||||
|
|
@ -74,4 +74,5 @@ class CreateImage(api.Creator):
|
|||
groups.append(group)
|
||||
|
||||
for group in groups:
|
||||
self.data.update({"subset": "image" + group.Name})
|
||||
photoshop.imprint(group, self.data)
|
||||
|
|
|
|||
36
pype/plugins/photoshop/publish/collect_review.py
Normal file
36
pype/plugins/photoshop/publish/collect_review.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
|
||||
import pythoncom
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectReview(pyblish.api.ContextPlugin):
|
||||
"""Gather the active document as review instance."""
|
||||
|
||||
label = "Review"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["photoshop"]
|
||||
|
||||
def process(self, context):
|
||||
# Necessary call when running in a different thread which pyblish-qml
|
||||
# can be.
|
||||
pythoncom.CoInitialize()
|
||||
|
||||
family = "review"
|
||||
task = os.getenv("AVALON_TASK", None)
|
||||
subset = family + task.capitalize()
|
||||
|
||||
file_path = context.data["currentFile"]
|
||||
base_name = os.path.basename(file_path)
|
||||
|
||||
instance = context.create_instance(subset)
|
||||
instance.data.update({
|
||||
"subset": subset,
|
||||
"label": base_name,
|
||||
"name": base_name,
|
||||
"family": family,
|
||||
"families": ["ftrack"],
|
||||
"representations": [],
|
||||
"asset": os.environ["AVALON_ASSET"]
|
||||
})
|
||||
103
pype/plugins/photoshop/publish/extract_review.py
Normal file
103
pype/plugins/photoshop/publish/extract_review.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import os
|
||||
|
||||
import pype.api
|
||||
import pype.lib
|
||||
from avalon import photoshop
|
||||
|
||||
|
||||
class ExtractReview(pype.api.Extractor):
|
||||
"""Produce a flattened image file from all instances."""
|
||||
|
||||
label = "Extract Review"
|
||||
hosts = ["photoshop"]
|
||||
families = ["review"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Outputting image to {}".format(staging_dir))
|
||||
|
||||
layers = []
|
||||
for image_instance in instance.context:
|
||||
if image_instance.data["family"] != "image":
|
||||
continue
|
||||
layers.append(image_instance[0])
|
||||
|
||||
# Perform extraction
|
||||
output_image = "{} copy.jpg".format(
|
||||
os.path.splitext(photoshop.app().ActiveDocument.Name)[0]
|
||||
)
|
||||
with photoshop.maintained_visibility():
|
||||
# Hide all other layers.
|
||||
extract_ids = [
|
||||
x.id for x in photoshop.get_layers_in_layers(layers)
|
||||
]
|
||||
for layer in photoshop.get_layers_in_document():
|
||||
if layer.id in extract_ids:
|
||||
layer.Visible = True
|
||||
else:
|
||||
layer.Visible = False
|
||||
|
||||
photoshop.app().ActiveDocument.SaveAs(
|
||||
staging_dir, photoshop.com_objects.JPEGSaveOptions(), True
|
||||
)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": output_image,
|
||||
"stagingDir": staging_dir
|
||||
})
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
# Generate thumbnail.
|
||||
thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg")
|
||||
args = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", os.path.join(staging_dir, output_image),
|
||||
"-vf", "scale=300:-1",
|
||||
"-vframes", "1",
|
||||
thumbnail_path
|
||||
]
|
||||
output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"])
|
||||
|
||||
self.log.debug(output)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "thumbnail",
|
||||
"ext": "jpg",
|
||||
"files": os.path.basename(thumbnail_path),
|
||||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail"]
|
||||
})
|
||||
|
||||
# Generate mov.
|
||||
mov_path = os.path.join(staging_dir, "review.mov")
|
||||
args = [
|
||||
"ffmpeg", "-y",
|
||||
"-i", os.path.join(staging_dir, output_image),
|
||||
"-vframes", "1",
|
||||
mov_path
|
||||
]
|
||||
output = pype.lib._subprocess(args, cwd=os.environ["FFMPEG_PATH"])
|
||||
|
||||
self.log.debug(output)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "mov",
|
||||
"ext": "mov",
|
||||
"files": os.path.basename(mov_path),
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": 1,
|
||||
"frameEnd": 1,
|
||||
"fps": 25,
|
||||
"preview": True,
|
||||
"tags": ["review", "ftrackreview"]
|
||||
})
|
||||
|
||||
# Required for extract_review plugin (L222 onwards).
|
||||
instance.data["frameStart"] = 1
|
||||
instance.data["frameEnd"] = 1
|
||||
instance.data["fps"] = 25
|
||||
|
||||
self.log.info(f"Extracted {instance} to {staging_dir}")
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import pyblish.api
|
||||
import pype.api
|
||||
from avalon import photoshop
|
||||
|
||||
|
||||
class ValidateNamingRepair(pyblish.api.Action):
|
||||
|
|
@ -22,7 +23,11 @@ class ValidateNamingRepair(pyblish.api.Action):
|
|||
instances = pyblish.api.instances_by_plugin(failed, plugin)
|
||||
|
||||
for instance in instances:
|
||||
instance[0].Name = instance.data["name"].replace(" ", "_")
|
||||
name = instance.data["name"].replace(" ", "_")
|
||||
instance[0].Name = name
|
||||
data = photoshop.read(instance[0])
|
||||
data["subset"] = "image" + name
|
||||
photoshop.imprint(instance[0], data)
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -42,3 +47,6 @@ class ValidateNaming(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
msg = "Name \"{}\" is not allowed.".format(instance.data["name"])
|
||||
assert " " not in instance.data["name"], msg
|
||||
|
||||
msg = "Subset \"{}\" is not allowed.".format(instance.data["subset"])
|
||||
assert " " not in instance.data["subset"], msg
|
||||
|
|
|
|||
|
|
@ -56,12 +56,18 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
asset_entity = instance.context.data["assetEntity"]
|
||||
asset_name = asset_entity["name"]
|
||||
|
||||
# Ask user for sequence start. Usually 10:00:00:00.
|
||||
sequence_start_frame = 900000
|
||||
|
||||
# Project specific prefix naming. This needs to be replaced with some
|
||||
# options to be more flexible.
|
||||
asset_name = asset_name.split("_")[0]
|
||||
|
||||
instances = []
|
||||
for track in tracks:
|
||||
track_start_frame = (
|
||||
abs(track.source_range.start_time.value) - sequence_start_frame
|
||||
)
|
||||
for child in track.each_child():
|
||||
|
||||
# Transitions are ignored, because Clips have the full frame
|
||||
|
|
@ -69,12 +75,17 @@ class CollectShots(pyblish.api.InstancePlugin):
|
|||
if isinstance(child, otio.schema.transition.Transition):
|
||||
continue
|
||||
|
||||
if child.name is None:
|
||||
continue
|
||||
|
||||
# Hardcoded to expect a shot name of "[name].[extension]"
|
||||
child_name = os.path.splitext(child.name)[0].lower()
|
||||
name = f"{asset_name}_{child_name}"
|
||||
|
||||
frame_start = child.range_in_parent().start_time.value
|
||||
frame_end = child.range_in_parent().end_time_inclusive().value
|
||||
frame_start = track_start_frame
|
||||
frame_start += child.range_in_parent().start_time.value
|
||||
frame_end = track_start_frame
|
||||
frame_end += child.range_in_parent().end_time_inclusive().value
|
||||
|
||||
label = f"{name} (framerange: {frame_start}-{frame_end})"
|
||||
instances.append(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue