mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
🔀 Merge remote-tracking branch 'origin/develop' into maya_new_publisher
This commit is contained in:
commit
4b12f49dd6
58 changed files with 1612 additions and 298 deletions
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.25"
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.26"
|
||||
ExtensionBundleName="com.openpype.AE.panel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.AE.panel" Version="1.0" />
|
||||
|
|
|
|||
|
|
@ -104,6 +104,39 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#create-placeholder-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.create_placeholder_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#update-placeholder-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.update_placeholder_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#build-workfile-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.build_workfile_template_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#experimental-button").bind("click", function() {
|
||||
|
|
@ -127,9 +160,15 @@
|
|||
<div><a href=# id=loader-button><button class="hostFontSize">Load...</button></a></div>
|
||||
<div><a href=# id=publish-button><button class="hostFontSize">Publish...</button></a></div>
|
||||
<div><a href=# id=sceneinventory-button><button class="hostFontSize">Manage...</button></a></div>
|
||||
<div><a href=# id=separator0><button class="hostFontSize"> </button></a></div>
|
||||
<div><a href=# id=setresolution-button><button class="hostFontSize">Set Resolution</button></a></div>
|
||||
<div><a href=# id=setframes-button><button class="hostFontSize">Set Frame Range</button></a></div>
|
||||
<div><a href=# id=setall-button><button class="hostFontSize">Apply All Settings</button></a></div>
|
||||
<div><a href=# id=separator1><button class="hostFontSize"> </button></a></div>
|
||||
<div><a href=# id=create-placeholder-button><button class="hostFontSize">Create placeholder</button></a></div>
|
||||
<div><a href=# id=update-placeholder-button><button class="hostFontSize">Update placeholder</button></a></div>
|
||||
<div><a href=# id=build-workfile-button><button class="hostFontSize">Build Workfile from template</button></a></div>
|
||||
<div><a href=# id=separator3><button class="hostFontSize"> </button></a></div>
|
||||
<div><a href=# id=experimental-button><button class="hostFontSize">Experimental Tools...</button></a></div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,17 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.add_item', function (data) {
|
||||
log.warn('Server called client route "add_item":', data);
|
||||
var escapedName = EscapeStringForJSX(data.name);
|
||||
return runEvalScript("addItem('" + escapedName +"', " +
|
||||
"'" + data.item_type + "')")
|
||||
.then(function(result){
|
||||
log.warn("get_items: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.get_items', function (data) {
|
||||
log.warn('Server called client route "get_items":', data);
|
||||
return runEvalScript("getItems(" + data.comps + "," +
|
||||
|
|
@ -118,6 +129,15 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.select_items', function (data) {
|
||||
log.warn('Server called client route "select_items":', data);
|
||||
return runEvalScript("selectItems(" + JSON.stringify(data.items) + ")")
|
||||
.then(function(result){
|
||||
log.warn("select_items: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
RPC.addRoute('AfterEffects.get_selected_items', function (data) {
|
||||
log.warn('Server called client route "get_selected_items":', data);
|
||||
|
|
@ -280,7 +300,7 @@ function main(websocket_url){
|
|||
RPC.addRoute('AfterEffects.add_item_as_layer', function (data) {
|
||||
log.warn('Server called client route "add_item_as_layer":', data);
|
||||
return runEvalScript("addItemAsLayerToComp(" + data.comp_id + ", " +
|
||||
data.item_id + "," +
|
||||
data.item_id + "," +
|
||||
" null )")
|
||||
.then(function(result){
|
||||
log.warn("addItemAsLayerToComp: " + result);
|
||||
|
|
@ -288,6 +308,16 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.add_item_instead_placeholder', function (data) {
|
||||
log.warn('Server called client route "add_item_instead_placeholder":', data);
|
||||
return runEvalScript("addItemInstead(" + data.placeholder_item_id + ", " +
|
||||
data.item_id + ")")
|
||||
.then(function(result){
|
||||
log.warn("add_item_instead_placeholder: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.render', function (data) {
|
||||
log.warn('Server called client route "render":', data);
|
||||
var escapedPath = EscapeStringForJSX(data.folder_url);
|
||||
|
|
@ -312,6 +342,20 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.add_placeholder', function (data) {
|
||||
log.warn('Server called client route "add_placeholder":', data);
|
||||
var escapedName = EscapeStringForJSX(data.name);
|
||||
return runEvalScript("addPlaceholder('" + escapedName +"',"+
|
||||
data.width + ',' +
|
||||
data.height + ',' +
|
||||
data.fps + ',' +
|
||||
data.duration + ")")
|
||||
.then(function(result){
|
||||
log.warn("add_placeholder: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.close', function (data) {
|
||||
log.warn('Server called client route "close":', data);
|
||||
return runEvalScript("close()");
|
||||
|
|
|
|||
|
|
@ -112,6 +112,32 @@ function getActiveDocumentFullName(){
|
|||
return _prepareError("No file open currently");
|
||||
}
|
||||
|
||||
|
||||
function addItem(name, item_type){
|
||||
/**
|
||||
* Adds comp or folder to project items.
|
||||
*
|
||||
* Could be called when creating publishable instance to prepare
|
||||
* composition (and render queue).
|
||||
*
|
||||
* Args:
|
||||
* name (str): composition name
|
||||
* item_type (str): COMP|FOLDER
|
||||
* Returns:
|
||||
* SingleItemValue: eg {"result": VALUE}
|
||||
*/
|
||||
if (item_type == "COMP"){
|
||||
// dummy values, will be rewritten later
|
||||
item = app.project.items.addComp(name, 1920, 1060, 1, 10, 25);
|
||||
}else if (item_type == "FOLDER"){
|
||||
item = app.project.items.addFolder(name);
|
||||
}else{
|
||||
return _prepareError("Only 'COMP' or 'FOLDER' can be created");
|
||||
}
|
||||
return _prepareSingleValue(item.id);
|
||||
|
||||
}
|
||||
|
||||
function getItems(comps, folders, footages){
|
||||
/**
|
||||
* Returns JSON representation of compositions and
|
||||
|
|
@ -139,6 +165,24 @@ function getItems(comps, folders, footages){
|
|||
|
||||
}
|
||||
|
||||
function selectItems(items){
|
||||
/**
|
||||
* Select all items from `items`, deselect other.
|
||||
*
|
||||
* Args:
|
||||
* items (list)
|
||||
*/
|
||||
for (i = 1; i <= app.project.items.length; ++i){
|
||||
item = app.project.items[i];
|
||||
if (items.indexOf(item.id) > -1){
|
||||
item.selected = true;
|
||||
}else{
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getSelectedItems(comps, folders, footages){
|
||||
/**
|
||||
* Returns list of selected items from Project menu
|
||||
|
|
@ -280,12 +324,12 @@ function setLabelColor(comp_id, color_idx){
|
|||
}
|
||||
}
|
||||
|
||||
function replaceItem(comp_id, path, item_name){
|
||||
function replaceItem(item_id, path, item_name){
|
||||
/**
|
||||
* Replaces loaded file with new file and updates name
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of composition, not a index!
|
||||
* item_id (int): id of composition, not a index!
|
||||
* path (string): absolute path to new file
|
||||
* item_name (string): new composition name
|
||||
*/
|
||||
|
|
@ -295,7 +339,7 @@ function replaceItem(comp_id, path, item_name){
|
|||
if (!fp.exists){
|
||||
return _prepareError("File " + path + " not found.");
|
||||
}
|
||||
var item = app.project.itemByID(comp_id);
|
||||
var item = app.project.itemByID(item_id);
|
||||
if (item){
|
||||
try{
|
||||
if (isFileSequence(item)) {
|
||||
|
|
@ -311,7 +355,7 @@ function replaceItem(comp_id, path, item_name){
|
|||
fp.close();
|
||||
}
|
||||
}else{
|
||||
return _prepareError("There is no composition with "+ comp_id);
|
||||
return _prepareError("There is no item with "+ item_id);
|
||||
}
|
||||
app.endUndoGroup();
|
||||
}
|
||||
|
|
@ -821,6 +865,67 @@ function printMsg(msg){
|
|||
alert(msg);
|
||||
}
|
||||
|
||||
function addPlaceholder(name, width, height, fps, duration){
|
||||
/** Add AE PlaceholderItem to Project list.
|
||||
*
|
||||
* PlaceholderItem chosen as it doesn't require existing file and
|
||||
* might potentially allow nice functionality in the future.
|
||||
*
|
||||
*/
|
||||
app.beginUndoGroup('change comp properties');
|
||||
try{
|
||||
item = app.project.importPlaceholder(name, width, height,
|
||||
fps, duration);
|
||||
|
||||
return _prepareSingleValue(item.id);
|
||||
}catch (error) {
|
||||
writeLn(_prepareError("Cannot add placeholder " + error.toString()));
|
||||
}
|
||||
app.endUndoGroup();
|
||||
}
|
||||
|
||||
function addItemInstead(placeholder_item_id, item_id){
|
||||
/** Add new loaded item in place of load placeholder.
|
||||
*
|
||||
* Each placeholder could be placed multiple times into multiple
|
||||
* composition. This loops through all compositions and
|
||||
* places loaded item under placeholder.
|
||||
* Placeholder item gets deleted later separately according
|
||||
* to configuration in Settings.
|
||||
*
|
||||
* Args:
|
||||
* placeholder_item_id (int)
|
||||
* item_id (int)
|
||||
*/
|
||||
var item = app.project.itemByID(item_id);
|
||||
if (!item){
|
||||
return _prepareError("There is no item with "+ item_id);
|
||||
}
|
||||
|
||||
app.beginUndoGroup('Add loaded items');
|
||||
for (i = 1; i <= app.project.items.length; ++i){
|
||||
var comp = app.project.items[i];
|
||||
if (!(comp instanceof CompItem)){
|
||||
continue
|
||||
}
|
||||
|
||||
var i = 1;
|
||||
while (i <= comp.numLayers) {
|
||||
var layer = comp.layer(i);
|
||||
var layer_source = layer.source;
|
||||
if (layer_source && layer_source.id == placeholder_item_id){
|
||||
var new_layer = comp.layers.add(item);
|
||||
new_layer.moveAfter(layer);
|
||||
// copy all(?) properties to new layer
|
||||
layer.property("ADBE Transform Group").copyToComp(new_layer);
|
||||
i = i + 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
app.endUndoGroup();
|
||||
}
|
||||
|
||||
function _prepareSingleValue(value){
|
||||
return JSON.stringify({"result": value})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -357,3 +357,33 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
||||
def create_placeholder_route(self):
|
||||
from openpype.hosts.aftereffects.api.workfile_template_builder import \
|
||||
create_placeholder
|
||||
partial_method = functools.partial(create_placeholder)
|
||||
|
||||
ProcessLauncher.execute_in_main_thread(partial_method)
|
||||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
||||
def update_placeholder_route(self):
|
||||
from openpype.hosts.aftereffects.api.workfile_template_builder import \
|
||||
update_placeholder
|
||||
partial_method = functools.partial(update_placeholder)
|
||||
|
||||
ProcessLauncher.execute_in_main_thread(partial_method)
|
||||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
||||
def build_workfile_template_route(self):
|
||||
from openpype.hosts.aftereffects.api.workfile_template_builder import \
|
||||
build_workfile_template
|
||||
partial_method = functools.partial(build_workfile_template)
|
||||
|
||||
ProcessLauncher.execute_in_main_thread(partial_method)
|
||||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ from openpype.pipeline import (
|
|||
register_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.workfile_template_builder import (
|
||||
AEPlaceholderLoadPlugin,
|
||||
AEPlaceholderCreatePlugin
|
||||
)
|
||||
from openpype.pipeline.load import any_outdated_containers
|
||||
import openpype.hosts.aftereffects
|
||||
|
||||
|
|
@ -116,6 +120,12 @@ class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
item["id"] = "publish_context"
|
||||
self.stub.imprint(item["id"], item)
|
||||
|
||||
def get_workfile_build_placeholder_plugins(self):
|
||||
return [
|
||||
AEPlaceholderLoadPlugin,
|
||||
AEPlaceholderCreatePlugin
|
||||
]
|
||||
|
||||
# created instances section
|
||||
def list_instances(self):
|
||||
"""List all created instances from current workfile which
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import six
|
||||
from abc import ABCMeta
|
||||
|
||||
from openpype.pipeline import LoaderPlugin
|
||||
from .launch_logic import get_stub
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class AfterEffectsLoader(LoaderPlugin):
|
||||
@staticmethod
|
||||
def get_stub():
|
||||
|
|
|
|||
271
openpype/hosts/aftereffects/api/workfile_template_builder.py
Normal file
271
openpype/hosts/aftereffects/api/workfile_template_builder.py
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
import os.path
|
||||
import uuid
|
||||
import shutil
|
||||
|
||||
from openpype.pipeline import registered_host
|
||||
from openpype.tools.workfile_template_build import (
|
||||
WorkfileBuildPlaceholderDialog,
|
||||
)
|
||||
from openpype.pipeline.workfile.workfile_template_builder import (
|
||||
AbstractTemplateBuilder,
|
||||
PlaceholderPlugin,
|
||||
LoadPlaceholderItem,
|
||||
CreatePlaceholderItem,
|
||||
PlaceholderLoadMixin,
|
||||
PlaceholderCreateMixin
|
||||
)
|
||||
from openpype.hosts.aftereffects.api import get_stub
|
||||
from openpype.hosts.aftereffects.api.lib import set_settings
|
||||
|
||||
PLACEHOLDER_SET = "PLACEHOLDERS_SET"
|
||||
PLACEHOLDER_ID = "openpype.placeholder"
|
||||
|
||||
|
||||
class AETemplateBuilder(AbstractTemplateBuilder):
|
||||
"""Concrete implementation of AbstractTemplateBuilder for AE"""
|
||||
|
||||
def import_template(self, path):
|
||||
"""Import template into current scene.
|
||||
Block if a template is already loaded.
|
||||
|
||||
Args:
|
||||
path (str): A path to current template (usually given by
|
||||
get_template_preset implementation)
|
||||
|
||||
Returns:
|
||||
bool: Whether the template was successfully imported or not
|
||||
"""
|
||||
stub = get_stub()
|
||||
if not os.path.exists(path):
|
||||
stub.print_msg(f"Template file on {path} doesn't exist.")
|
||||
return
|
||||
|
||||
stub.save()
|
||||
workfile_path = stub.get_active_document_full_name()
|
||||
shutil.copy2(path, workfile_path)
|
||||
stub.open(workfile_path)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class AEPlaceholderPlugin(PlaceholderPlugin):
|
||||
"""Contains generic methods for all PlaceholderPlugins."""
|
||||
|
||||
def collect_placeholders(self):
|
||||
"""Collect info from file metadata about created placeholders.
|
||||
|
||||
Returns:
|
||||
(list) (LoadPlaceholderItem)
|
||||
"""
|
||||
output = []
|
||||
scene_placeholders = self._collect_scene_placeholders()
|
||||
for item in scene_placeholders:
|
||||
if item.get("plugin_identifier") != self.identifier:
|
||||
continue
|
||||
|
||||
if isinstance(self, AEPlaceholderLoadPlugin):
|
||||
item = LoadPlaceholderItem(item["uuid"],
|
||||
item["data"],
|
||||
self)
|
||||
elif isinstance(self, AEPlaceholderCreatePlugin):
|
||||
item = CreatePlaceholderItem(item["uuid"],
|
||||
item["data"],
|
||||
self)
|
||||
else:
|
||||
raise NotImplementedError(f"Not implemented for {type(self)}")
|
||||
|
||||
output.append(item)
|
||||
|
||||
return output
|
||||
|
||||
def update_placeholder(self, placeholder_item, placeholder_data):
|
||||
"""Resave changed properties for placeholders"""
|
||||
item_id, metadata_item = self._get_item(placeholder_item)
|
||||
stub = get_stub()
|
||||
if not item_id:
|
||||
stub.print_msg("Cannot find item for "
|
||||
f"{placeholder_item.scene_identifier}")
|
||||
return
|
||||
metadata_item["data"] = placeholder_data
|
||||
stub.imprint(item_id, metadata_item)
|
||||
|
||||
def _get_item(self, placeholder_item):
|
||||
"""Returns item id and item metadata for placeholder from file meta"""
|
||||
stub = get_stub()
|
||||
placeholder_uuid = placeholder_item.scene_identifier
|
||||
for metadata_item in stub.get_metadata():
|
||||
if not metadata_item.get("is_placeholder"):
|
||||
continue
|
||||
if placeholder_uuid in metadata_item.get("uuid"):
|
||||
return metadata_item["members"][0], metadata_item
|
||||
return None, None
|
||||
|
||||
def _collect_scene_placeholders(self):
|
||||
"""" Cache placeholder data to shared data.
|
||||
Returns:
|
||||
(list) of dicts
|
||||
"""
|
||||
placeholder_items = self.builder.get_shared_populate_data(
|
||||
"placeholder_items"
|
||||
)
|
||||
if not placeholder_items:
|
||||
placeholder_items = []
|
||||
for item in get_stub().get_metadata():
|
||||
if not item.get("is_placeholder"):
|
||||
continue
|
||||
placeholder_items.append(item)
|
||||
|
||||
self.builder.set_shared_populate_data(
|
||||
"placeholder_items", placeholder_items
|
||||
)
|
||||
return placeholder_items
|
||||
|
||||
def _imprint_item(self, item_id, name, placeholder_data, stub):
|
||||
if not item_id:
|
||||
raise ValueError("Couldn't create a placeholder")
|
||||
container_data = {
|
||||
"id": "openpype.placeholder",
|
||||
"name": name,
|
||||
"is_placeholder": True,
|
||||
"plugin_identifier": self.identifier,
|
||||
"uuid": str(uuid.uuid4()), # scene_identifier
|
||||
"data": placeholder_data,
|
||||
"members": [item_id]
|
||||
}
|
||||
stub.imprint(item_id, container_data)
|
||||
|
||||
|
||||
class AEPlaceholderCreatePlugin(AEPlaceholderPlugin, PlaceholderCreateMixin):
|
||||
"""Adds Create placeholder.
|
||||
|
||||
This adds composition and runs Create
|
||||
"""
|
||||
identifier = "aftereffects.create"
|
||||
label = "AfterEffects create"
|
||||
|
||||
def create_placeholder(self, placeholder_data):
|
||||
stub = get_stub()
|
||||
name = "CREATEPLACEHOLDER"
|
||||
item_id = stub.add_item(name, "COMP")
|
||||
|
||||
self._imprint_item(item_id, name, placeholder_data, stub)
|
||||
|
||||
def populate_placeholder(self, placeholder):
|
||||
"""Replace 'placeholder' with publishable instance.
|
||||
|
||||
Renames prepared composition name, creates publishable instance, sets
|
||||
frame/duration settings according to DB.
|
||||
"""
|
||||
pre_create_data = {"use_selection": True}
|
||||
item_id, item = self._get_item(placeholder)
|
||||
get_stub().select_items([item_id])
|
||||
self.populate_create_placeholder(placeholder, pre_create_data)
|
||||
|
||||
# apply settings for populated composition
|
||||
item_id, metadata_item = self._get_item(placeholder)
|
||||
set_settings(True, True, [item_id])
|
||||
|
||||
def get_placeholder_options(self, options=None):
|
||||
return self.get_create_plugin_options(options)
|
||||
|
||||
|
||||
class AEPlaceholderLoadPlugin(AEPlaceholderPlugin, PlaceholderLoadMixin):
|
||||
identifier = "aftereffects.load"
|
||||
label = "AfterEffects load"
|
||||
|
||||
def create_placeholder(self, placeholder_data):
|
||||
"""Creates AE's Placeholder item in Project items list.
|
||||
|
||||
Sets dummy resolution/duration/fps settings, will be replaced when
|
||||
populated.
|
||||
"""
|
||||
stub = get_stub()
|
||||
name = "LOADERPLACEHOLDER"
|
||||
item_id = stub.add_placeholder(name, 1920, 1060, 25, 10)
|
||||
|
||||
self._imprint_item(item_id, name, placeholder_data, stub)
|
||||
|
||||
def populate_placeholder(self, placeholder):
|
||||
"""Use Openpype Loader from `placeholder` to create new FootageItems
|
||||
|
||||
New FootageItems are created, files are imported.
|
||||
"""
|
||||
self.populate_load_placeholder(placeholder)
|
||||
errors = placeholder.get_errors()
|
||||
stub = get_stub()
|
||||
if errors:
|
||||
stub.print_msg("\n".join(errors))
|
||||
else:
|
||||
if not placeholder.data["keep_placeholder"]:
|
||||
metadata = stub.get_metadata()
|
||||
for item in metadata:
|
||||
if not item.get("is_placeholder"):
|
||||
continue
|
||||
scene_identifier = item.get("uuid")
|
||||
if (scene_identifier and
|
||||
scene_identifier == placeholder.scene_identifier):
|
||||
stub.delete_item(item["members"][0])
|
||||
stub.remove_instance(placeholder.scene_identifier, metadata)
|
||||
|
||||
def get_placeholder_options(self, options=None):
|
||||
return self.get_load_plugin_options(options)
|
||||
|
||||
def load_succeed(self, placeholder, container):
|
||||
placeholder_item_id, _ = self._get_item(placeholder)
|
||||
item_id = container.id
|
||||
get_stub().add_item_instead_placeholder(placeholder_item_id, item_id)
|
||||
|
||||
|
||||
def build_workfile_template(*args, **kwargs):
|
||||
builder = AETemplateBuilder(registered_host())
|
||||
builder.build_template(*args, **kwargs)
|
||||
|
||||
|
||||
def update_workfile_template(*args):
|
||||
builder = AETemplateBuilder(registered_host())
|
||||
builder.rebuild_template()
|
||||
|
||||
|
||||
def create_placeholder(*args):
|
||||
"""Called when new workile placeholder should be created."""
|
||||
host = registered_host()
|
||||
builder = AETemplateBuilder(host)
|
||||
window = WorkfileBuildPlaceholderDialog(host, builder)
|
||||
window.exec_()
|
||||
|
||||
|
||||
def update_placeholder(*args):
|
||||
"""Called after placeholder item is selected to modify it."""
|
||||
host = registered_host()
|
||||
builder = AETemplateBuilder(host)
|
||||
|
||||
stub = get_stub()
|
||||
selected_items = stub.get_selected_items(True, True, True)
|
||||
|
||||
if len(selected_items) != 1:
|
||||
stub.print_msg("Please select just 1 placeholder")
|
||||
return
|
||||
|
||||
selected_id = selected_items[0].id
|
||||
placeholder_item = None
|
||||
|
||||
placeholder_items_by_id = {
|
||||
placeholder_item.scene_identifier: placeholder_item
|
||||
for placeholder_item in builder.get_placeholders()
|
||||
}
|
||||
for metadata_item in stub.get_metadata():
|
||||
if not metadata_item.get("is_placeholder"):
|
||||
continue
|
||||
if selected_id in metadata_item.get("members"):
|
||||
placeholder_item = placeholder_items_by_id.get(
|
||||
metadata_item["uuid"])
|
||||
break
|
||||
|
||||
if not placeholder_item:
|
||||
stub.print_msg("Didn't find placeholder metadata. "
|
||||
"Remove and re-create placeholder.")
|
||||
return
|
||||
|
||||
window = WorkfileBuildPlaceholderDialog(host, builder)
|
||||
window.set_update_mode(placeholder_item)
|
||||
window.exec_()
|
||||
|
|
@ -35,6 +35,8 @@ class AEItem(object):
|
|||
instance_id = attr.ib(default=None) # New Publisher
|
||||
width = attr.ib(default=None)
|
||||
height = attr.ib(default=None)
|
||||
is_placeholder = attr.ib(default=False)
|
||||
uuid = attr.ib(default=False)
|
||||
|
||||
|
||||
class AfterEffectsServerStub():
|
||||
|
|
@ -220,6 +222,16 @@ class AfterEffectsServerStub():
|
|||
)
|
||||
return self._to_records(self._handle_return(res))
|
||||
|
||||
def select_items(self, items):
|
||||
"""
|
||||
Select items in Project list
|
||||
Args:
|
||||
items (list): of int item ids
|
||||
"""
|
||||
self.websocketserver.call(
|
||||
self.client.call('AfterEffects.select_items', items=items))
|
||||
|
||||
|
||||
def get_selected_items(self, comps, folders=False, footages=False):
|
||||
"""
|
||||
Same as get_items but using selected items only
|
||||
|
|
@ -240,6 +252,21 @@ class AfterEffectsServerStub():
|
|||
)
|
||||
return self._to_records(self._handle_return(res))
|
||||
|
||||
def add_item(self, name, item_type):
|
||||
"""
|
||||
Adds either composition or folder to project item list.
|
||||
|
||||
Args:
|
||||
name (str)
|
||||
item_type (str): COMP|FOLDER
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.add_item',
|
||||
name=name,
|
||||
item_type=item_type))
|
||||
|
||||
return self._handle_return(res)
|
||||
|
||||
def get_item(self, item_id):
|
||||
"""
|
||||
Returns metadata for particular 'item_id' or None
|
||||
|
|
@ -316,7 +343,7 @@ class AfterEffectsServerStub():
|
|||
|
||||
return self._handle_return(res)
|
||||
|
||||
def remove_instance(self, instance_id):
|
||||
def remove_instance(self, instance_id, metadata=None):
|
||||
"""
|
||||
Removes instance with 'instance_id' from file's metadata and
|
||||
saves them.
|
||||
|
|
@ -328,7 +355,10 @@ class AfterEffectsServerStub():
|
|||
"""
|
||||
cleaned_data = []
|
||||
|
||||
for instance in self.get_metadata():
|
||||
if metadata is None:
|
||||
metadata = self.get_metadata()
|
||||
|
||||
for instance in metadata:
|
||||
inst_id = instance.get("instance_id") or instance.get("uuid")
|
||||
if inst_id != instance_id:
|
||||
cleaned_data.append(instance)
|
||||
|
|
@ -534,6 +564,47 @@ class AfterEffectsServerStub():
|
|||
if records:
|
||||
return records.pop()
|
||||
|
||||
def add_item_instead_placeholder(self, placeholder_item_id, item_id):
|
||||
"""
|
||||
Adds item_id to layers where plaeholder_item_id is present.
|
||||
|
||||
1 placeholder could result in multiple loaded containers (eg items)
|
||||
|
||||
Args:
|
||||
placeholder_item_id (int): id of placeholder item
|
||||
item_id (int): loaded FootageItem id
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.add_item_instead_placeholder', # noqa
|
||||
placeholder_item_id=placeholder_item_id, # noqa
|
||||
item_id=item_id))
|
||||
|
||||
return self._handle_return(res)
|
||||
|
||||
def add_placeholder(self, name, width, height, fps, duration):
|
||||
"""
|
||||
Adds new FootageItem as a placeholder for workfile builder
|
||||
|
||||
Placeholder requires width etc, currently probably only hardcoded
|
||||
values.
|
||||
|
||||
Args:
|
||||
name (str)
|
||||
width (int)
|
||||
height (int)
|
||||
fps (float)
|
||||
duration (int)
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.add_placeholder',
|
||||
name=name,
|
||||
width=width,
|
||||
height=height,
|
||||
fps=fps,
|
||||
duration=duration))
|
||||
|
||||
return self._handle_return(res)
|
||||
|
||||
def render(self, folder_url, comp_id):
|
||||
"""
|
||||
Render all renderqueueitem to 'folder_url'
|
||||
|
|
@ -632,7 +703,8 @@ class AfterEffectsServerStub():
|
|||
d.get('file_name'),
|
||||
d.get("instance_id"),
|
||||
d.get("width"),
|
||||
d.get("height"))
|
||||
d.get("height"),
|
||||
d.get("is_placeholder"))
|
||||
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
import re
|
||||
|
||||
from openpype.pipeline import get_representation_path
|
||||
from openpype.hosts.aftereffects.api import (
|
||||
AfterEffectsLoader,
|
||||
containerise
|
||||
)
|
||||
from openpype.hosts.aftereffects import api
|
||||
|
||||
from openpype.hosts.aftereffects.api.lib import (
|
||||
get_background_layers,
|
||||
get_unique_layer_name,
|
||||
)
|
||||
|
||||
|
||||
class BackgroundLoader(AfterEffectsLoader):
|
||||
class BackgroundLoader(api.AfterEffectsLoader):
|
||||
"""
|
||||
Load images from Background family
|
||||
Creates for each background separate folder with all imported images
|
||||
|
|
@ -21,6 +19,7 @@ class BackgroundLoader(AfterEffectsLoader):
|
|||
For each load container is created and stored in project (.aep)
|
||||
metadata
|
||||
"""
|
||||
label = "Load JSON Background"
|
||||
families = ["background"]
|
||||
representations = ["json"]
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ class BackgroundLoader(AfterEffectsLoader):
|
|||
self[:] = [comp]
|
||||
namespace = namespace or comp_name
|
||||
|
||||
return containerise(
|
||||
return api.containerise(
|
||||
name,
|
||||
namespace,
|
||||
comp,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import re
|
||||
|
||||
from openpype.pipeline import get_representation_path
|
||||
from openpype.hosts.aftereffects.api import (
|
||||
AfterEffectsLoader,
|
||||
containerise
|
||||
)
|
||||
from openpype.hosts.aftereffects import api
|
||||
from openpype.hosts.aftereffects.api.lib import get_unique_layer_name
|
||||
|
||||
|
||||
class FileLoader(AfterEffectsLoader):
|
||||
class FileLoader(api.AfterEffectsLoader):
|
||||
"""Load images
|
||||
|
||||
Stores the imported asset in a container named after the asset.
|
||||
|
|
@ -64,7 +61,7 @@ class FileLoader(AfterEffectsLoader):
|
|||
self[:] = [comp]
|
||||
namespace = namespace or comp_name
|
||||
|
||||
return containerise(
|
||||
return api.containerise(
|
||||
name,
|
||||
namespace,
|
||||
comp,
|
||||
|
|
|
|||
|
|
@ -21,8 +21,13 @@ from .pipeline import (
|
|||
reset_selection
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
OPENPYPE_TAG_NAME,
|
||||
DEFAULT_SEQUENCE_NAME,
|
||||
DEFAULT_BIN_NAME
|
||||
)
|
||||
|
||||
from .lib import (
|
||||
pype_tag_name,
|
||||
flatten,
|
||||
get_track_items,
|
||||
get_current_project,
|
||||
|
|
@ -82,8 +87,12 @@ __all__ = [
|
|||
"file_extensions",
|
||||
"work_root",
|
||||
|
||||
# Constants
|
||||
"OPENPYPE_TAG_NAME",
|
||||
"DEFAULT_SEQUENCE_NAME",
|
||||
"DEFAULT_BIN_NAME",
|
||||
|
||||
# Lib functions
|
||||
"pype_tag_name",
|
||||
"flatten",
|
||||
"get_track_items",
|
||||
"get_current_project",
|
||||
|
|
|
|||
3
openpype/hosts/hiero/api/constants.py
Normal file
3
openpype/hosts/hiero/api/constants.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
OPENPYPE_TAG_NAME = "openpypeData"
|
||||
DEFAULT_SEQUENCE_NAME = "openpypeSequence"
|
||||
DEFAULT_BIN_NAME = "openpypeBin"
|
||||
|
|
@ -5,7 +5,6 @@ Host specific functions where host api is connected
|
|||
from copy import deepcopy
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import platform
|
||||
import functools
|
||||
import warnings
|
||||
|
|
@ -29,12 +28,22 @@ from openpype.pipeline import (
|
|||
from openpype.pipeline.load import filter_containers
|
||||
from openpype.lib import Logger
|
||||
from . import tags
|
||||
|
||||
from .constants import (
|
||||
OPENPYPE_TAG_NAME,
|
||||
DEFAULT_SEQUENCE_NAME,
|
||||
DEFAULT_BIN_NAME
|
||||
)
|
||||
from openpype.pipeline.colorspace import (
|
||||
get_imageio_config
|
||||
)
|
||||
|
||||
|
||||
class _CTX:
|
||||
has_been_setup = False
|
||||
has_menu = False
|
||||
parent_gui = None
|
||||
|
||||
|
||||
class DeprecatedWarning(DeprecationWarning):
|
||||
pass
|
||||
|
||||
|
|
@ -82,23 +91,14 @@ def deprecated(new_destination):
|
|||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self._has_been_setup = False
|
||||
self._has_menu = False
|
||||
self._registered_gui = None
|
||||
self._parent = None
|
||||
self.pype_tag_name = "openpypeData"
|
||||
self.default_sequence_name = "openpypeSequence"
|
||||
self.default_bin_name = "openpypeBin"
|
||||
|
||||
|
||||
def flatten(_list):
|
||||
for item in _list:
|
||||
if isinstance(item, (list, tuple)):
|
||||
for sub_item in flatten(item):
|
||||
def flatten(list_):
|
||||
for item_ in list_:
|
||||
if isinstance(item_, (list, tuple)):
|
||||
for sub_item in flatten(item_):
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
yield item_
|
||||
|
||||
|
||||
def get_current_project(remove_untitled=False):
|
||||
|
|
@ -131,7 +131,7 @@ def get_current_sequence(name=None, new=False):
|
|||
|
||||
if new:
|
||||
# create new
|
||||
name = name or self.default_sequence_name
|
||||
name = name or DEFAULT_SEQUENCE_NAME
|
||||
sequence = hiero.core.Sequence(name)
|
||||
root_bin.addItem(hiero.core.BinItem(sequence))
|
||||
elif name:
|
||||
|
|
@ -345,7 +345,7 @@ def get_track_item_tags(track_item):
|
|||
# collect all tags which are not openpype tag
|
||||
returning_tag_data.extend(
|
||||
tag for tag in _tags
|
||||
if tag.name() != self.pype_tag_name
|
||||
if tag.name() != OPENPYPE_TAG_NAME
|
||||
)
|
||||
|
||||
return returning_tag_data
|
||||
|
|
@ -385,7 +385,7 @@ def set_track_openpype_tag(track, data=None):
|
|||
# if pype tag available then update with input data
|
||||
tag = tags.create_tag(
|
||||
"{}_{}".format(
|
||||
self.pype_tag_name,
|
||||
OPENPYPE_TAG_NAME,
|
||||
_get_tag_unique_hash()
|
||||
),
|
||||
tag_data
|
||||
|
|
@ -412,7 +412,7 @@ def get_track_openpype_tag(track):
|
|||
return None
|
||||
for tag in _tags:
|
||||
# return only correct tag defined by global name
|
||||
if self.pype_tag_name in tag.name():
|
||||
if OPENPYPE_TAG_NAME in tag.name():
|
||||
return tag
|
||||
|
||||
|
||||
|
|
@ -484,7 +484,7 @@ def get_trackitem_openpype_tag(track_item):
|
|||
return None
|
||||
for tag in _tags:
|
||||
# return only correct tag defined by global name
|
||||
if self.pype_tag_name in tag.name():
|
||||
if OPENPYPE_TAG_NAME in tag.name():
|
||||
return tag
|
||||
|
||||
|
||||
|
|
@ -516,7 +516,7 @@ def set_trackitem_openpype_tag(track_item, data=None):
|
|||
# if pype tag available then update with input data
|
||||
tag = tags.create_tag(
|
||||
"{}_{}".format(
|
||||
self.pype_tag_name,
|
||||
OPENPYPE_TAG_NAME,
|
||||
_get_tag_unique_hash()
|
||||
),
|
||||
tag_data
|
||||
|
|
@ -698,29 +698,29 @@ def setup(console=False, port=None, menu=True):
|
|||
menu (bool, optional): Display file menu in Hiero.
|
||||
"""
|
||||
|
||||
if self._has_been_setup:
|
||||
if _CTX.has_been_setup:
|
||||
teardown()
|
||||
|
||||
add_submission()
|
||||
|
||||
if menu:
|
||||
add_to_filemenu()
|
||||
self._has_menu = True
|
||||
_CTX.has_menu = True
|
||||
|
||||
self._has_been_setup = True
|
||||
_CTX.has_been_setup = True
|
||||
log.debug("pyblish: Loaded successfully.")
|
||||
|
||||
|
||||
def teardown():
|
||||
"""Remove integration"""
|
||||
if not self._has_been_setup:
|
||||
if not _CTX.has_been_setup:
|
||||
return
|
||||
|
||||
if self._has_menu:
|
||||
if _CTX.has_menu:
|
||||
remove_from_filemenu()
|
||||
self._has_menu = False
|
||||
_CTX.has_menu = False
|
||||
|
||||
self._has_been_setup = False
|
||||
_CTX.has_been_setup = False
|
||||
log.debug("pyblish: Integration torn down successfully")
|
||||
|
||||
|
||||
|
|
@ -928,7 +928,7 @@ def create_bin(path=None, project=None):
|
|||
# get the first loaded project
|
||||
project = project or get_current_project()
|
||||
|
||||
path = path or self.default_bin_name
|
||||
path = path or DEFAULT_BIN_NAME
|
||||
|
||||
path = path.replace("\\", "/").split("/")
|
||||
|
||||
|
|
@ -1311,11 +1311,11 @@ def before_project_save(event):
|
|||
|
||||
def get_main_window():
|
||||
"""Acquire Nuke's main window"""
|
||||
if self._parent is None:
|
||||
if _CTX.parent_gui is None:
|
||||
top_widgets = QtWidgets.QApplication.topLevelWidgets()
|
||||
name = "Foundry::UI::DockMainWindow"
|
||||
main_window = next(widget for widget in top_widgets if
|
||||
widget.inherits("QMainWindow") and
|
||||
widget.metaObject().className() == name)
|
||||
self._parent = main_window
|
||||
return self._parent
|
||||
_CTX.parent_gui = main_window
|
||||
return _CTX.parent_gui
|
||||
|
|
|
|||
|
|
@ -3,20 +3,18 @@
|
|||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import ast
|
||||
import opentimelineio as otio
|
||||
from . import utils
|
||||
import hiero.core
|
||||
import hiero.ui
|
||||
|
||||
self = sys.modules[__name__]
|
||||
self.track_types = {
|
||||
|
||||
TRACK_TYPE_MAP = {
|
||||
hiero.core.VideoTrack: otio.schema.TrackKind.Video,
|
||||
hiero.core.AudioTrack: otio.schema.TrackKind.Audio
|
||||
}
|
||||
self.project_fps = None
|
||||
self.marker_color_map = {
|
||||
MARKER_COLOR_MAP = {
|
||||
"magenta": otio.schema.MarkerColor.MAGENTA,
|
||||
"red": otio.schema.MarkerColor.RED,
|
||||
"yellow": otio.schema.MarkerColor.YELLOW,
|
||||
|
|
@ -24,30 +22,21 @@ self.marker_color_map = {
|
|||
"cyan": otio.schema.MarkerColor.CYAN,
|
||||
"blue": otio.schema.MarkerColor.BLUE,
|
||||
}
|
||||
self.timeline = None
|
||||
self.include_tags = True
|
||||
|
||||
|
||||
def flatten(_list):
|
||||
for item in _list:
|
||||
if isinstance(item, (list, tuple)):
|
||||
for sub_item in flatten(item):
|
||||
class CTX:
|
||||
project_fps = None
|
||||
timeline = None
|
||||
include_tags = True
|
||||
|
||||
|
||||
def flatten(list_):
|
||||
for item_ in list_:
|
||||
if isinstance(item_, (list, tuple)):
|
||||
for sub_item in flatten(item_):
|
||||
yield sub_item
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
def get_current_hiero_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
|
||||
yield item_
|
||||
|
||||
|
||||
def create_otio_rational_time(frame, fps):
|
||||
|
|
@ -152,7 +141,7 @@ def create_otio_reference(clip):
|
|||
file_head = media_source.filenameHead()
|
||||
is_sequence = not media_source.singleFile()
|
||||
frame_duration = media_source.duration()
|
||||
fps = utils.get_rate(clip) or self.project_fps
|
||||
fps = utils.get_rate(clip) or CTX.project_fps
|
||||
extension = os.path.splitext(path)[-1]
|
||||
|
||||
if is_sequence:
|
||||
|
|
@ -217,8 +206,8 @@ def get_marker_color(tag):
|
|||
res = re.search(pat, icon)
|
||||
if res:
|
||||
color = res.groupdict().get('color')
|
||||
if color.lower() in self.marker_color_map:
|
||||
return self.marker_color_map[color.lower()]
|
||||
if color.lower() in MARKER_COLOR_MAP:
|
||||
return MARKER_COLOR_MAP[color.lower()]
|
||||
|
||||
return otio.schema.MarkerColor.RED
|
||||
|
||||
|
|
@ -232,7 +221,7 @@ def create_otio_markers(otio_item, item):
|
|||
# Hiero adds this tag to a lot of clips
|
||||
continue
|
||||
|
||||
frame_rate = utils.get_rate(item) or self.project_fps
|
||||
frame_rate = utils.get_rate(item) or CTX.project_fps
|
||||
|
||||
marked_range = otio.opentime.TimeRange(
|
||||
start_time=otio.opentime.RationalTime(
|
||||
|
|
@ -279,7 +268,7 @@ def create_otio_clip(track_item):
|
|||
|
||||
duration = int(track_item.duration())
|
||||
|
||||
fps = utils.get_rate(track_item) or self.project_fps
|
||||
fps = utils.get_rate(track_item) or CTX.project_fps
|
||||
name = track_item.name()
|
||||
|
||||
media_reference = create_otio_reference(clip)
|
||||
|
|
@ -296,7 +285,7 @@ def create_otio_clip(track_item):
|
|||
)
|
||||
|
||||
# Add tags as markers
|
||||
if self.include_tags:
|
||||
if CTX.include_tags:
|
||||
create_otio_markers(otio_clip, track_item)
|
||||
create_otio_markers(otio_clip, track_item.source())
|
||||
|
||||
|
|
@ -319,13 +308,13 @@ def create_otio_gap(gap_start, clip_start, tl_start_frame, fps):
|
|||
|
||||
|
||||
def _create_otio_timeline():
|
||||
project = get_current_hiero_project(remove_untitled=False)
|
||||
metadata = _get_metadata(self.timeline)
|
||||
project = CTX.timeline.project()
|
||||
metadata = _get_metadata(CTX.timeline)
|
||||
|
||||
metadata.update({
|
||||
"openpype.timeline.width": int(self.timeline.format().width()),
|
||||
"openpype.timeline.height": int(self.timeline.format().height()),
|
||||
"openpype.timeline.pixelAspect": int(self.timeline.format().pixelAspect()), # noqa
|
||||
"openpype.timeline.width": int(CTX.timeline.format().width()),
|
||||
"openpype.timeline.height": int(CTX.timeline.format().height()),
|
||||
"openpype.timeline.pixelAspect": int(CTX.timeline.format().pixelAspect()), # noqa
|
||||
"openpype.project.useOCIOEnvironmentOverride": project.useOCIOEnvironmentOverride(), # noqa
|
||||
"openpype.project.lutSetting16Bit": project.lutSetting16Bit(),
|
||||
"openpype.project.lutSetting8Bit": project.lutSetting8Bit(),
|
||||
|
|
@ -339,10 +328,10 @@ def _create_otio_timeline():
|
|||
})
|
||||
|
||||
start_time = create_otio_rational_time(
|
||||
self.timeline.timecodeStart(), self.project_fps)
|
||||
CTX.timeline.timecodeStart(), CTX.project_fps)
|
||||
|
||||
return otio.schema.Timeline(
|
||||
name=self.timeline.name(),
|
||||
name=CTX.timeline.name(),
|
||||
global_start_time=start_time,
|
||||
metadata=metadata
|
||||
)
|
||||
|
|
@ -351,7 +340,7 @@ def _create_otio_timeline():
|
|||
def create_otio_track(track_type, track_name):
|
||||
return otio.schema.Track(
|
||||
name=track_name,
|
||||
kind=self.track_types[track_type]
|
||||
kind=TRACK_TYPE_MAP[track_type]
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -363,7 +352,7 @@ def add_otio_gap(track_item, otio_track, prev_out):
|
|||
gap = otio.opentime.TimeRange(
|
||||
duration=otio.opentime.RationalTime(
|
||||
gap_length,
|
||||
self.project_fps
|
||||
CTX.project_fps
|
||||
)
|
||||
)
|
||||
otio_gap = otio.schema.Gap(source_range=gap)
|
||||
|
|
@ -396,14 +385,14 @@ def create_otio_timeline():
|
|||
return track_item.parent().items()[itemindex - 1]
|
||||
|
||||
# get current timeline
|
||||
self.timeline = hiero.ui.activeSequence()
|
||||
self.project_fps = self.timeline.framerate().toFloat()
|
||||
CTX.timeline = hiero.ui.activeSequence()
|
||||
CTX.project_fps = CTX.timeline.framerate().toFloat()
|
||||
|
||||
# convert timeline to otio
|
||||
otio_timeline = _create_otio_timeline()
|
||||
|
||||
# loop all defined track types
|
||||
for track in self.timeline.items():
|
||||
for track in CTX.timeline.items():
|
||||
# skip if track is disabled
|
||||
if not track.isEnabled():
|
||||
continue
|
||||
|
|
@ -441,7 +430,7 @@ def create_otio_timeline():
|
|||
otio_track.append(otio_clip)
|
||||
|
||||
# Add tags as markers
|
||||
if self.include_tags:
|
||||
if CTX.include_tags:
|
||||
create_otio_markers(otio_track, track)
|
||||
|
||||
# add track to otio timeline
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
# add pypedata marker to otio_clip metadata
|
||||
for marker in otio_clip.markers:
|
||||
if phiero.pype_tag_name in marker.name:
|
||||
if phiero.OPENPYPE_TAG_NAME in marker.name:
|
||||
otio_clip.metadata.update(marker.metadata)
|
||||
return {"otioClip": otio_clip}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from qtpy.QtGui import QPixmap
|
|||
import hiero.ui
|
||||
|
||||
from openpype.pipeline import legacy_io
|
||||
from openpype.hosts.hiero import api as phiero
|
||||
from openpype.hosts.hiero.api.otio import hiero_export
|
||||
|
||||
|
||||
|
|
@ -22,8 +21,8 @@ class PrecollectWorkfile(pyblish.api.ContextPlugin):
|
|||
|
||||
asset = legacy_io.Session["AVALON_ASSET"]
|
||||
subset = "workfile"
|
||||
project = phiero.get_current_project()
|
||||
active_timeline = hiero.ui.activeSequence()
|
||||
project = active_timeline.project()
|
||||
fps = active_timeline.framerate().toFloat()
|
||||
|
||||
# adding otio timeline to context
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
import hou # noqa
|
||||
|
||||
from openpype.hosts.houdini.api import plugin
|
||||
from openpype.pipeline import CreatedInstance
|
||||
from openpype.lib import EnumDef
|
||||
|
||||
|
||||
class CreateRedshiftROP(plugin.HoudiniCreator):
|
||||
"""Redshift ROP"""
|
||||
|
||||
identifier = "io.openpype.creators.houdini.redshift_rop"
|
||||
label = "Redshift ROP"
|
||||
family = "redshift_rop"
|
||||
|
|
@ -28,7 +28,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
instance = super(CreateRedshiftROP, self).create(
|
||||
subset_name,
|
||||
instance_data,
|
||||
pre_create_data) # type: CreatedInstance
|
||||
pre_create_data)
|
||||
|
||||
instance_node = hou.node(instance.get("instance_node"))
|
||||
|
||||
|
|
@ -57,6 +57,8 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext)
|
||||
)
|
||||
|
||||
ext_format_index = {"exr": 0, "tif": 1, "jpg": 2, "png": 3}
|
||||
|
||||
parms = {
|
||||
# Render frame range
|
||||
"trange": 1,
|
||||
|
|
@ -64,6 +66,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
"RS_outputFileNamePrefix": filepath,
|
||||
"RS_outputMultilayerMode": "1", # no multi-layered exr
|
||||
"RS_outputBeautyAOVSuffix": "beauty",
|
||||
"RS_outputFileFormat": ext_format_index[ext],
|
||||
}
|
||||
|
||||
if self.selected_nodes:
|
||||
|
|
@ -93,8 +96,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
def get_pre_create_attr_defs(self):
|
||||
attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
|
||||
image_format_enum = [
|
||||
"bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
|
||||
"rad", "rat", "rta", "sgi", "tga", "tif",
|
||||
"exr", "tif", "jpg", "png",
|
||||
]
|
||||
|
||||
return attrs + [
|
||||
|
|
|
|||
|
|
@ -250,10 +250,7 @@ def reset_frame_range(fps: bool = True):
|
|||
frame_range["handleStart"]
|
||||
)
|
||||
frame_end_handle = frame_range["frameEnd"] + int(frame_range["handleEnd"])
|
||||
frange_cmd = (
|
||||
f"animationRange = interval {frame_start_handle} {frame_end_handle}"
|
||||
)
|
||||
rt.Execute(frange_cmd)
|
||||
set_timeline(frame_start_handle, frame_end_handle)
|
||||
set_render_frame_range(frame_start_handle, frame_end_handle)
|
||||
|
||||
|
||||
|
|
@ -285,3 +282,10 @@ def get_max_version():
|
|||
"""
|
||||
max_info = rt.MaxVersion()
|
||||
return max_info[7]
|
||||
|
||||
|
||||
def set_timeline(frameStart, frameEnd):
|
||||
"""Set frame range for timeline editor in Max
|
||||
"""
|
||||
rt.animationRange = rt.interval(frameStart, frameEnd)
|
||||
return rt.animationRange
|
||||
|
|
|
|||
|
|
@ -2320,8 +2320,8 @@ def reset_frame_range(playback=True, render=True, fps=True):
|
|||
cmds.currentTime(frame_start)
|
||||
|
||||
if render:
|
||||
cmds.setAttr("defaultRenderGlobals.startFrame", frame_start)
|
||||
cmds.setAttr("defaultRenderGlobals.endFrame", frame_end)
|
||||
cmds.setAttr("defaultRenderGlobals.startFrame", animation_start)
|
||||
cmds.setAttr("defaultRenderGlobals.endFrame", animation_end)
|
||||
|
||||
|
||||
def reset_scene_resolution():
|
||||
|
|
@ -3989,6 +3989,71 @@ def get_capture_preset(task_name, task_type, subset, project_settings, log):
|
|||
return capture_preset or {}
|
||||
|
||||
|
||||
def get_reference_node(members, log=None):
|
||||
"""Get the reference node from the container members
|
||||
Args:
|
||||
members: list of node names
|
||||
|
||||
Returns:
|
||||
str: Reference node name.
|
||||
|
||||
"""
|
||||
|
||||
# Collect the references without .placeHolderList[] attributes as
|
||||
# unique entries (objects only) and skipping the sharedReferenceNode.
|
||||
references = set()
|
||||
for ref in cmds.ls(members, exactType="reference", objectsOnly=True):
|
||||
|
||||
# Ignore any `:sharedReferenceNode`
|
||||
if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"):
|
||||
continue
|
||||
|
||||
# Ignore _UNKNOWN_REF_NODE_ (PLN-160)
|
||||
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
|
||||
continue
|
||||
|
||||
references.add(ref)
|
||||
|
||||
assert references, "No reference node found in container"
|
||||
|
||||
# Get highest reference node (least parents)
|
||||
highest = min(references,
|
||||
key=lambda x: len(get_reference_node_parents(x)))
|
||||
|
||||
# Warn the user when we're taking the highest reference node
|
||||
if len(references) > 1:
|
||||
if not log:
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
log.warning("More than one reference node found in "
|
||||
"container, using highest reference node: "
|
||||
"%s (in: %s)", highest, list(references))
|
||||
|
||||
return highest
|
||||
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
parent = cmds.referenceQuery(ref,
|
||||
referenceNode=True,
|
||||
parent=True)
|
||||
parents = []
|
||||
while parent:
|
||||
parents.append(parent)
|
||||
parent = cmds.referenceQuery(parent,
|
||||
referenceNode=True,
|
||||
parent=True)
|
||||
return parents
|
||||
|
||||
|
||||
def create_rig_animation_instance(
|
||||
nodes, context, namespace, options=None, log=None
|
||||
):
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
register_event_callback("taskChanged", on_task_changed)
|
||||
register_event_callback("workfile.open.before", before_workfile_open)
|
||||
register_event_callback("workfile.save.before", before_workfile_save)
|
||||
register_event_callback("workfile.save.before", after_workfile_save)
|
||||
register_event_callback("workfile.save.after", after_workfile_save)
|
||||
|
||||
def open_workfile(self, filepath):
|
||||
return open_file(filepath)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ from .pipeline import containerise
|
|||
from . import lib
|
||||
|
||||
|
||||
log = Logger.get_logger()
|
||||
|
||||
|
||||
def _get_attr(node, attr, default=None):
|
||||
"""Helper to get attribute which allows attribute to not exist."""
|
||||
if not cmds.attributeQuery(attr, node=node, exists=True):
|
||||
|
|
@ -39,69 +42,28 @@ def _get_attr(node, attr, default=None):
|
|||
return cmds.getAttr("{}.{}".format(node, attr))
|
||||
|
||||
|
||||
def get_reference_node(members, log=None):
|
||||
# Backwards compatibility: these functions has been moved to lib.
|
||||
def get_reference_node(*args, **kwargs):
|
||||
"""Get the reference node from the container members
|
||||
Args:
|
||||
members: list of node names
|
||||
|
||||
Returns:
|
||||
str: Reference node name.
|
||||
|
||||
Deprecated:
|
||||
This function was moved and will be removed in 3.16.x.
|
||||
"""
|
||||
|
||||
# Collect the references without .placeHolderList[] attributes as
|
||||
# unique entries (objects only) and skipping the sharedReferenceNode.
|
||||
references = set()
|
||||
for ref in cmds.ls(members, exactType="reference", objectsOnly=True):
|
||||
|
||||
# Ignore any `:sharedReferenceNode`
|
||||
if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"):
|
||||
continue
|
||||
|
||||
# Ignore _UNKNOWN_REF_NODE_ (PLN-160)
|
||||
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
|
||||
continue
|
||||
|
||||
references.add(ref)
|
||||
|
||||
assert references, "No reference node found in container"
|
||||
|
||||
# Get highest reference node (least parents)
|
||||
highest = min(references,
|
||||
key=lambda x: len(get_reference_node_parents(x)))
|
||||
|
||||
# Warn the user when we're taking the highest reference node
|
||||
if len(references) > 1:
|
||||
if not log:
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
log.warning("More than one reference node found in "
|
||||
"container, using highest reference node: "
|
||||
"%s (in: %s)", highest, list(references))
|
||||
|
||||
return highest
|
||||
msg = "Function 'get_reference_node' has been moved."
|
||||
log.warning(msg)
|
||||
cmds.warning(msg)
|
||||
return lib.get_reference_node(*args, **kwargs)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
def get_reference_node_parents(*args, **kwargs):
|
||||
"""
|
||||
parent = cmds.referenceQuery(ref,
|
||||
referenceNode=True,
|
||||
parent=True)
|
||||
parents = []
|
||||
while parent:
|
||||
parents.append(parent)
|
||||
parent = cmds.referenceQuery(parent,
|
||||
referenceNode=True,
|
||||
parent=True)
|
||||
return parents
|
||||
Deprecated:
|
||||
This function was moved and will be removed in 3.16.x.
|
||||
"""
|
||||
msg = "Function 'get_reference_node_parents' has been moved."
|
||||
log.warning(msg)
|
||||
cmds.warning(msg)
|
||||
return lib.get_reference_node_parents(*args, **kwargs)
|
||||
|
||||
|
||||
class Creator(LegacyCreator):
|
||||
|
|
@ -598,7 +560,7 @@ class ReferenceLoader(Loader):
|
|||
if not nodes:
|
||||
return
|
||||
|
||||
ref_node = get_reference_node(nodes, self.log)
|
||||
ref_node = lib.get_reference_node(nodes, self.log)
|
||||
container = containerise(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
|
|
@ -627,7 +589,7 @@ class ReferenceLoader(Loader):
|
|||
|
||||
# Get reference node from container members
|
||||
members = get_container_members(node)
|
||||
reference_node = get_reference_node(members, self.log)
|
||||
reference_node = lib.get_reference_node(members, self.log)
|
||||
namespace = cmds.referenceQuery(reference_node, namespace=True)
|
||||
|
||||
file_type = {
|
||||
|
|
@ -775,7 +737,7 @@ class ReferenceLoader(Loader):
|
|||
|
||||
# Assume asset has been referenced
|
||||
members = cmds.sets(node, query=True)
|
||||
reference_node = get_reference_node(members, self.log)
|
||||
reference_node = lib.get_reference_node(members, self.log)
|
||||
|
||||
assert reference_node, ("Imported container not supported; "
|
||||
"container must be referenced.")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from openpype.tools.workfile_template_build import (
|
|||
WorkfileBuildPlaceholderDialog,
|
||||
)
|
||||
|
||||
from .lib import read, imprint, get_main_window
|
||||
from .lib import read, imprint, get_reference_node, get_main_window
|
||||
|
||||
PLACEHOLDER_SET = "PLACEHOLDERS_SET"
|
||||
|
||||
|
|
@ -243,15 +243,19 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
|
|||
def get_placeholder_options(self, options=None):
|
||||
return self.get_load_plugin_options(options)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
def post_placeholder_process(self, placeholder, failed):
|
||||
"""Hide placeholder, add them to placeholder set
|
||||
"""
|
||||
node = placeholder._scene_identifier
|
||||
node = placeholder.scene_identifier
|
||||
|
||||
cmds.sets(node, addElement=PLACEHOLDER_SET)
|
||||
cmds.hide(node)
|
||||
cmds.setAttr(node + ".hiddenInOutliner", True)
|
||||
|
||||
def delete_placeholder(self, placeholder):
|
||||
"""Remove placeholder if building was successful"""
|
||||
cmds.delete(placeholder.scene_identifier)
|
||||
|
||||
def load_succeed(self, placeholder, container):
|
||||
self._parent_in_hierarchy(placeholder, container)
|
||||
|
||||
|
|
@ -268,9 +272,19 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
|
|||
return
|
||||
|
||||
roots = cmds.sets(container, q=True)
|
||||
ref_node = get_reference_node(roots)
|
||||
nodes_to_parent = []
|
||||
for root in roots:
|
||||
if ref_node:
|
||||
ref_root = cmds.referenceQuery(root, nodes=True)[0]
|
||||
ref_root = (
|
||||
cmds.listRelatives(ref_root, parent=True, path=True) or
|
||||
[ref_root]
|
||||
)
|
||||
nodes_to_parent.extend(ref_root)
|
||||
continue
|
||||
if root.endswith("_RN"):
|
||||
# Backwards compatibility for hardcoded reference names.
|
||||
refRoot = cmds.referenceQuery(root, n=True)[0]
|
||||
refRoot = cmds.listRelatives(refRoot, parent=True) or [refRoot]
|
||||
nodes_to_parent.extend(refRoot)
|
||||
|
|
@ -287,10 +301,17 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
|
|||
matrix=True,
|
||||
worldSpace=True
|
||||
)
|
||||
scene_parent = cmds.listRelatives(
|
||||
placeholder.scene_identifier, parent=True, fullPath=True
|
||||
)
|
||||
for node in set(nodes_to_parent):
|
||||
cmds.reorder(node, front=True)
|
||||
cmds.reorder(node, relative=placeholder.data["index"])
|
||||
cmds.xform(node, matrix=placeholder_form, ws=True)
|
||||
if scene_parent:
|
||||
cmds.parent(node, scene_parent)
|
||||
else:
|
||||
cmds.parent(node, world=True)
|
||||
|
||||
holding_sets = cmds.listSets(object=placeholder.scene_identifier)
|
||||
if not holding_sets:
|
||||
|
|
|
|||
|
|
@ -2250,16 +2250,15 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
log.warning(msg)
|
||||
nuke.message(msg)
|
||||
return
|
||||
data = self._asset_entity["data"]
|
||||
|
||||
log.debug("__ asset data: `{}`".format(data))
|
||||
asset_data = self._asset_entity["data"]
|
||||
|
||||
missing_cols = []
|
||||
check_cols = ["fps", "frameStart", "frameEnd",
|
||||
"handleStart", "handleEnd"]
|
||||
|
||||
for col in check_cols:
|
||||
if col not in data:
|
||||
if col not in asset_data:
|
||||
missing_cols.append(col)
|
||||
|
||||
if len(missing_cols) > 0:
|
||||
|
|
@ -2271,12 +2270,12 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
return
|
||||
|
||||
# get handles values
|
||||
handle_start = data["handleStart"]
|
||||
handle_end = data["handleEnd"]
|
||||
handle_start = asset_data["handleStart"]
|
||||
handle_end = asset_data["handleEnd"]
|
||||
|
||||
fps = float(data["fps"])
|
||||
frame_start_handle = int(data["frameStart"]) - handle_start
|
||||
frame_end_handle = int(data["frameEnd"]) + handle_end
|
||||
fps = float(asset_data["fps"])
|
||||
frame_start_handle = int(asset_data["frameStart"]) - handle_start
|
||||
frame_end_handle = int(asset_data["frameEnd"]) + handle_end
|
||||
|
||||
self._root_node["lock_range"].setValue(False)
|
||||
self._root_node["fps"].setValue(fps)
|
||||
|
|
@ -2284,21 +2283,18 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
self._root_node["last_frame"].setValue(frame_end_handle)
|
||||
self._root_node["lock_range"].setValue(True)
|
||||
|
||||
# setting active viewers
|
||||
try:
|
||||
nuke.frame(int(data["frameStart"]))
|
||||
except Exception as e:
|
||||
log.warning("no viewer in scene: `{}`".format(e))
|
||||
# update node graph so knobs are updated
|
||||
update_node_graph()
|
||||
|
||||
range = '{0}-{1}'.format(
|
||||
int(data["frameStart"]),
|
||||
int(data["frameEnd"])
|
||||
frame_range = '{0}-{1}'.format(
|
||||
int(asset_data["frameStart"]),
|
||||
int(asset_data["frameEnd"])
|
||||
)
|
||||
|
||||
for node in nuke.allNodes(filter="Viewer"):
|
||||
node['frame_range'].setValue(range)
|
||||
node['frame_range'].setValue(frame_range)
|
||||
node['frame_range_lock'].setValue(True)
|
||||
node['frame_range'].setValue(range)
|
||||
node['frame_range'].setValue(frame_range)
|
||||
node['frame_range_lock'].setValue(True)
|
||||
|
||||
if not ASSIST:
|
||||
|
|
@ -2320,12 +2316,9 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
"""Set resolution to project resolution."""
|
||||
log.info("Resetting resolution")
|
||||
project_name = legacy_io.active_project()
|
||||
project = get_project(project_name)
|
||||
asset_name = legacy_io.Session["AVALON_ASSET"]
|
||||
asset = get_asset_by_name(project_name, asset_name)
|
||||
asset_data = asset.get('data', {})
|
||||
asset_data = self._asset_entity["data"]
|
||||
|
||||
data = {
|
||||
format_data = {
|
||||
"width": int(asset_data.get(
|
||||
'resolutionWidth',
|
||||
asset_data.get('resolution_width'))),
|
||||
|
|
@ -2335,37 +2328,40 @@ Reopening Nuke should synchronize these paths and resolve any discrepancies.
|
|||
"pixel_aspect": asset_data.get(
|
||||
'pixelAspect',
|
||||
asset_data.get('pixel_aspect', 1)),
|
||||
"name": project["name"]
|
||||
"name": project_name
|
||||
}
|
||||
|
||||
if any(x for x in data.values() if x is None):
|
||||
if any(x_ for x_ in format_data.values() if x_ is None):
|
||||
msg = ("Missing set shot attributes in DB."
|
||||
"\nContact your supervisor!."
|
||||
"\n\nWidth: `{width}`"
|
||||
"\nHeight: `{height}`"
|
||||
"\nPixel Asspect: `{pixel_aspect}`").format(**data)
|
||||
"\nPixel Aspect: `{pixel_aspect}`").format(**format_data)
|
||||
log.error(msg)
|
||||
nuke.message(msg)
|
||||
|
||||
existing_format = None
|
||||
for format in nuke.formats():
|
||||
if data["name"] == format.name():
|
||||
if format_data["name"] == format.name():
|
||||
existing_format = format
|
||||
break
|
||||
|
||||
if existing_format:
|
||||
# Enforce existing format to be correct.
|
||||
existing_format.setWidth(data["width"])
|
||||
existing_format.setHeight(data["height"])
|
||||
existing_format.setPixelAspect(data["pixel_aspect"])
|
||||
existing_format.setWidth(format_data["width"])
|
||||
existing_format.setHeight(format_data["height"])
|
||||
existing_format.setPixelAspect(format_data["pixel_aspect"])
|
||||
else:
|
||||
format_string = self.make_format_string(**data)
|
||||
format_string = self.make_format_string(**format_data)
|
||||
log.info("Creating new format: {}".format(format_string))
|
||||
nuke.addFormat(format_string)
|
||||
|
||||
nuke.root()["format"].setValue(data["name"])
|
||||
nuke.root()["format"].setValue(format_data["name"])
|
||||
log.info("Format is set.")
|
||||
|
||||
# update node graph so knobs are updated
|
||||
update_node_graph()
|
||||
|
||||
def make_format_string(self, **kwargs):
|
||||
if kwargs.get("r"):
|
||||
return (
|
||||
|
|
@ -2484,6 +2480,20 @@ def get_dependent_nodes(nodes):
|
|||
return connections_in, connections_out
|
||||
|
||||
|
||||
def update_node_graph():
|
||||
# Resetting frame will update knob values
|
||||
try:
|
||||
root_node_lock = nuke.root()["lock_range"].value()
|
||||
nuke.root()["lock_range"].setValue(not root_node_lock)
|
||||
nuke.root()["lock_range"].setValue(root_node_lock)
|
||||
|
||||
current_frame = nuke.frame()
|
||||
nuke.frame(1)
|
||||
nuke.frame(int(current_frame))
|
||||
except Exception as error:
|
||||
log.warning(error)
|
||||
|
||||
|
||||
def find_free_space_to_paste_nodes(
|
||||
nodes,
|
||||
group=nuke.root(),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import os
|
|||
import nuke
|
||||
|
||||
from openpype import resources
|
||||
from .lib import maintained_selection
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
||||
def set_context_favorites(favorites=None):
|
||||
|
|
@ -55,6 +55,7 @@ def bake_gizmos_recursively(in_group=None):
|
|||
Arguments:
|
||||
is_group (nuke.Node)[optonal]: group node or all nodes
|
||||
"""
|
||||
from .lib import maintained_selection
|
||||
if in_group is None:
|
||||
in_group = nuke.Root()
|
||||
# preserve selection after all is done
|
||||
|
|
@ -129,3 +130,11 @@ def get_colorspace_list(colorspace_knob):
|
|||
reduced_clrs.append(clrs)
|
||||
|
||||
return reduced_clrs
|
||||
|
||||
|
||||
def is_headless():
|
||||
"""
|
||||
Returns:
|
||||
bool: headless
|
||||
"""
|
||||
return QtWidgets.QApplication.instance() is None
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ class NukePlaceholderLoadPlugin(NukePlaceholderPlugin, PlaceholderLoadMixin):
|
|||
def get_placeholder_options(self, options=None):
|
||||
return self.get_load_plugin_options(options)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
def post_placeholder_process(self, placeholder, failed):
|
||||
# deselect all selected nodes
|
||||
placeholder_node = nuke.toNode(placeholder.scene_identifier)
|
||||
|
||||
|
|
@ -604,7 +604,7 @@ class NukePlaceholderCreatePlugin(
|
|||
def get_placeholder_options(self, options=None):
|
||||
return self.get_create_plugin_options(options)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
def post_placeholder_process(self, placeholder, failed):
|
||||
# deselect all selected nodes
|
||||
placeholder_node = nuke.toNode(placeholder.scene_identifier)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"""Host API required Work Files tool"""
|
||||
import os
|
||||
import nuke
|
||||
from .utils import is_headless
|
||||
|
||||
|
||||
def file_extensions():
|
||||
|
|
@ -25,6 +26,12 @@ def open_file(filepath):
|
|||
# To remain in the same window, we have to clear the script and read
|
||||
# in the contents of the workfile.
|
||||
nuke.scriptClear()
|
||||
if not is_headless():
|
||||
autosave = nuke.toNode("preferences")["AutoSaveName"].evaluate()
|
||||
autosave_prmpt = "Autosave detected.\nWould you like to load the autosave file?" # noqa
|
||||
if os.path.isfile(autosave) and nuke.ask(autosave_prmpt):
|
||||
filepath = autosave
|
||||
|
||||
nuke.scriptReadFile(filepath)
|
||||
nuke.Root()["name"].setValue(filepath)
|
||||
nuke.Root()["project_directory"].setValue(os.path.dirname(filepath))
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ log = Logger.get_logger(__name__)
|
|||
self = sys.modules[__name__]
|
||||
self.project_manager = None
|
||||
self.media_storage = None
|
||||
self.current_project = None
|
||||
|
||||
# OpenPype sequential rename variables
|
||||
self.rename_index = 0
|
||||
|
|
@ -88,10 +87,7 @@ def get_media_storage():
|
|||
def get_current_project():
|
||||
"""Get current project object.
|
||||
"""
|
||||
if not self.current_project:
|
||||
self.current_project = get_project_manager().GetCurrentProject()
|
||||
|
||||
return self.current_project
|
||||
return get_project_manager().GetCurrentProject()
|
||||
|
||||
|
||||
def get_current_timeline(new=False):
|
||||
|
|
|
|||
|
|
@ -4,18 +4,15 @@ import os
|
|||
from openpype.lib import Logger
|
||||
from .lib import (
|
||||
get_project_manager,
|
||||
get_current_project,
|
||||
set_project_manager_to_folder_name
|
||||
get_current_project
|
||||
)
|
||||
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
exported_projet_ext = ".drp"
|
||||
|
||||
|
||||
def file_extensions():
|
||||
return [exported_projet_ext]
|
||||
return [".drp"]
|
||||
|
||||
|
||||
def has_unsaved_changes():
|
||||
|
|
@ -30,13 +27,17 @@ def save_file(filepath):
|
|||
project = get_current_project()
|
||||
name = project.GetName()
|
||||
|
||||
if "Untitled Project" not in name:
|
||||
log.info("Saving project: `{}` as '{}'".format(name, file))
|
||||
pm.ExportProject(name, filepath)
|
||||
else:
|
||||
log.info("Creating new project...")
|
||||
pm.CreateProject(fname)
|
||||
pm.ExportProject(name, filepath)
|
||||
response = False
|
||||
if name == "Untitled Project":
|
||||
response = pm.CreateProject(fname)
|
||||
log.info("New project created: {}".format(response))
|
||||
pm.SaveProject()
|
||||
elif name != fname:
|
||||
response = project.SetName(fname)
|
||||
log.info("Project renamed: {}".format(response))
|
||||
|
||||
exported = pm.ExportProject(fname, filepath)
|
||||
log.info("Project exported: {}".format(exported))
|
||||
|
||||
|
||||
def open_file(filepath):
|
||||
|
|
@ -57,10 +58,8 @@ def open_file(filepath):
|
|||
|
||||
file = os.path.basename(filepath)
|
||||
fname, _ = os.path.splitext(file)
|
||||
dname, _ = fname.split("_v")
|
||||
|
||||
try:
|
||||
if not set_project_manager_to_folder_name(dname):
|
||||
raise
|
||||
# load project from input path
|
||||
project = pm.LoadProject(fname)
|
||||
log.info(f"Project {project.GetName()} opened...")
|
||||
|
|
@ -79,14 +78,18 @@ def open_file(filepath):
|
|||
|
||||
def current_file():
|
||||
pm = get_project_manager()
|
||||
current_dir = os.getenv("AVALON_WORKDIR")
|
||||
file_ext = file_extensions()[0]
|
||||
workdir_path = os.getenv("AVALON_WORKDIR")
|
||||
project = pm.GetCurrentProject()
|
||||
name = project.GetName()
|
||||
fname = name + exported_projet_ext
|
||||
current_file = os.path.join(current_dir, fname)
|
||||
if not current_file:
|
||||
return None
|
||||
return os.path.normpath(current_file)
|
||||
project_name = project.GetName()
|
||||
file_name = project_name + file_ext
|
||||
|
||||
# create current file path
|
||||
current_file_path = os.path.join(workdir_path, file_name)
|
||||
|
||||
# return current file path if it exists
|
||||
if os.path.exists(current_file_path):
|
||||
return os.path.normpath(current_file_path)
|
||||
|
||||
|
||||
def work_root(session):
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import os
|
||||
|
||||
from openpype.lib import PreLaunchHook
|
||||
import openpype.hosts.resolve
|
||||
|
||||
|
||||
class ResolveLaunchLastWorkfile(PreLaunchHook):
|
||||
class PreLaunchResolveLastWorkfile(PreLaunchHook):
|
||||
"""Special hook to open last workfile for Resolve.
|
||||
|
||||
Checks 'start_last_workfile', if set to False, it will not open last
|
||||
workfile. This property is set explicitly in Launcher.
|
||||
"""
|
||||
|
||||
# Execute after workfile template copy
|
||||
order = 10
|
||||
app_groups = ["resolve"]
|
||||
|
||||
|
|
@ -30,16 +26,9 @@ class ResolveLaunchLastWorkfile(PreLaunchHook):
|
|||
return
|
||||
|
||||
# Add path to launch environment for the startup script to pick up
|
||||
self.log.info(f"Setting OPENPYPE_RESOLVE_OPEN_ON_LAUNCH to launch "
|
||||
f"last workfile: {last_workfile}")
|
||||
self.log.info(
|
||||
"Setting OPENPYPE_RESOLVE_OPEN_ON_LAUNCH to launch "
|
||||
f"last workfile: {last_workfile}"
|
||||
)
|
||||
key = "OPENPYPE_RESOLVE_OPEN_ON_LAUNCH"
|
||||
self.launch_context.env[key] = last_workfile
|
||||
|
||||
# Set the openpype prelaunch startup script path for easy access
|
||||
# in the LUA .scriptlib code
|
||||
op_resolve_root = os.path.dirname(openpype.hosts.resolve.__file__)
|
||||
script_path = os.path.join(op_resolve_root, "startup.py")
|
||||
key = "OPENPYPE_RESOLVE_STARTUP_SCRIPT"
|
||||
self.launch_context.env[key] = script_path
|
||||
self.log.info("Setting OPENPYPE_RESOLVE_STARTUP_SCRIPT to: "
|
||||
f"{script_path}")
|
||||
|
|
@ -5,7 +5,7 @@ from openpype.lib import PreLaunchHook
|
|||
from openpype.hosts.resolve.utils import setup
|
||||
|
||||
|
||||
class ResolvePrelaunch(PreLaunchHook):
|
||||
class PreLaunchResolveSetup(PreLaunchHook):
|
||||
"""
|
||||
This hook will set up the Resolve scripting environment as described in
|
||||
Resolve's documentation found with the installed application at
|
||||
|
|
|
|||
24
openpype/hosts/resolve/hooks/pre_resolve_startup.py
Normal file
24
openpype/hosts/resolve/hooks/pre_resolve_startup.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import os
|
||||
|
||||
from openpype.lib import PreLaunchHook
|
||||
import openpype.hosts.resolve
|
||||
|
||||
|
||||
class PreLaunchResolveStartup(PreLaunchHook):
|
||||
"""Special hook to configure startup script.
|
||||
|
||||
"""
|
||||
order = 11
|
||||
app_groups = ["resolve"]
|
||||
|
||||
def execute(self):
|
||||
# Set the openpype prelaunch startup script path for easy access
|
||||
# in the LUA .scriptlib code
|
||||
op_resolve_root = os.path.dirname(openpype.hosts.resolve.__file__)
|
||||
script_path = os.path.join(op_resolve_root, "startup.py")
|
||||
key = "OPENPYPE_RESOLVE_STARTUP_SCRIPT"
|
||||
self.launch_context.env[key] = script_path
|
||||
|
||||
self.log.info(
|
||||
f"Setting OPENPYPE_RESOLVE_STARTUP_SCRIPT to: {script_path}"
|
||||
)
|
||||
|
|
@ -10,9 +10,11 @@ This code runs in a separate process to the main Resolve process.
|
|||
|
||||
"""
|
||||
import os
|
||||
|
||||
from openpype.lib import Logger
|
||||
import openpype.hosts.resolve.api
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def ensure_installed_host():
|
||||
"""Install resolve host with openpype and return the registered host.
|
||||
|
|
@ -44,17 +46,22 @@ def open_file(path):
|
|||
def main():
|
||||
# Open last workfile
|
||||
workfile_path = os.environ.get("OPENPYPE_RESOLVE_OPEN_ON_LAUNCH")
|
||||
if workfile_path:
|
||||
|
||||
if workfile_path and os.path.exists(workfile_path):
|
||||
log.info(f"Opening last workfile: {workfile_path}")
|
||||
open_file(workfile_path)
|
||||
else:
|
||||
print("No last workfile set to open. Skipping..")
|
||||
log.info("No last workfile set to open. Skipping..")
|
||||
|
||||
# Launch OpenPype menu
|
||||
from openpype.settings import get_project_settings
|
||||
from openpype.pipeline.context_tools import get_current_project_name
|
||||
project_name = get_current_project_name()
|
||||
log.info(f"Current project name in context: {project_name}")
|
||||
|
||||
settings = get_project_settings(project_name)
|
||||
if settings.get("resolve", {}).get("launch_openpype_menu_on_start", True):
|
||||
log.info("Launching OpenPype menu..")
|
||||
launch_menu()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
#! python3
|
||||
from openpype.hosts.resolve.startup import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import pyblish.api
|
||||
from openpype.pipeline import registered_host
|
||||
from openpype.pipeline import publish
|
||||
from openpype.lib import EnumDef
|
||||
from openpype.pipeline import colorspace
|
||||
|
||||
|
||||
class CollectColorspace(pyblish.api.InstancePlugin,
|
||||
publish.OpenPypePyblishPluginMixin,
|
||||
publish.ColormanagedPyblishPluginMixin):
|
||||
"""Collect explicit user defined representation colorspaces"""
|
||||
|
||||
label = "Choose representation colorspace"
|
||||
order = pyblish.api.CollectorOrder + 0.49
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
colorspace_items = [
|
||||
(None, "Don't override")
|
||||
]
|
||||
colorspace_attr_show = False
|
||||
|
||||
def process(self, instance):
|
||||
values = self.get_attr_values_from_data(instance.data)
|
||||
colorspace = values.get("colorspace", None)
|
||||
if colorspace is None:
|
||||
return
|
||||
|
||||
self.log.debug("Explicit colorspace set to: {}".format(colorspace))
|
||||
|
||||
context = instance.context
|
||||
for repre in instance.data.get("representations", {}):
|
||||
self.set_representation_colorspace(
|
||||
representation=repre,
|
||||
context=context,
|
||||
colorspace=colorspace
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
host = registered_host()
|
||||
host_name = host.name
|
||||
project_name = host.get_current_project_name()
|
||||
config_data = colorspace.get_imageio_config(
|
||||
project_name, host_name,
|
||||
project_settings=project_settings
|
||||
)
|
||||
|
||||
if config_data:
|
||||
filepath = config_data["path"]
|
||||
config_items = colorspace.get_ocio_config_colorspaces(filepath)
|
||||
cls.colorspace_items.extend((
|
||||
(name, name) for name in config_items.keys()
|
||||
))
|
||||
cls.colorspace_attr_show = True
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
return [
|
||||
EnumDef(
|
||||
"colorspace",
|
||||
cls.colorspace_items,
|
||||
default="Don't override",
|
||||
label="Override Colorspace",
|
||||
hidden=not cls.colorspace_attr_show
|
||||
)
|
||||
]
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import (
|
||||
publish,
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
from openpype.pipeline.colorspace import (
|
||||
get_ocio_config_colorspaces
|
||||
)
|
||||
|
||||
|
||||
class ValidateColorspace(pyblish.api.InstancePlugin,
|
||||
publish.OpenPypePyblishPluginMixin,
|
||||
publish.ColormanagedPyblishPluginMixin):
|
||||
"""Validate representation colorspaces"""
|
||||
|
||||
label = "Validate representation colorspace"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
config_colorspaces = {} # cache of colorspaces per config path
|
||||
for repre in instance.data.get("representations", {}):
|
||||
|
||||
colorspace_data = repre.get("colorspaceData", {})
|
||||
if not colorspace_data:
|
||||
# Nothing to validate
|
||||
continue
|
||||
|
||||
config_path = colorspace_data["config"]["path"]
|
||||
if config_path not in config_colorspaces:
|
||||
colorspaces = get_ocio_config_colorspaces(config_path)
|
||||
config_colorspaces[config_path] = set(colorspaces)
|
||||
|
||||
colorspace = colorspace_data["colorspace"]
|
||||
self.log.debug(
|
||||
f"Validating representation '{repre['name']}' "
|
||||
f"colorspace '{colorspace}'"
|
||||
)
|
||||
if colorspace not in config_colorspaces[config_path]:
|
||||
message = (
|
||||
f"Representation '{repre['name']}' colorspace "
|
||||
f"'{colorspace}' does not exist in OCIO config: "
|
||||
f"{config_path}"
|
||||
)
|
||||
|
||||
raise PublishValidationError(
|
||||
title="Representation colorspace",
|
||||
message=message,
|
||||
description=message
|
||||
)
|
||||
|
|
@ -167,7 +167,7 @@ def filter_profiles(profiles_data, key_values, keys_order=None, logger=None):
|
|||
for item in key_values.items()
|
||||
])
|
||||
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"Looking for matching profile for: {}".format(log_parts)
|
||||
)
|
||||
|
||||
|
|
@ -209,19 +209,19 @@ def filter_profiles(profiles_data, key_values, keys_order=None, logger=None):
|
|||
matching_profiles.append((profile, profile_scores))
|
||||
|
||||
if not matching_profiles:
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"None of profiles match your setup. {}".format(log_parts)
|
||||
)
|
||||
return None
|
||||
|
||||
if len(matching_profiles) > 1:
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"More than one profile match your setup. {}".format(log_parts)
|
||||
)
|
||||
|
||||
profile = _profile_exclusion(matching_profiles, logger)
|
||||
if profile:
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"Profile selected: {}".format(profile)
|
||||
)
|
||||
return profile
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class CollectFtrackFamily(pyblish.api.InstancePlugin):
|
|||
if "ftrack" not in families:
|
||||
families.append("ftrack")
|
||||
|
||||
self.log.info("{} 'ftrack' family for instance with '{}'".format(
|
||||
self.log.debug("{} 'ftrack' family for instance with '{}'".format(
|
||||
result_str, family
|
||||
))
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class CollectFtrackTaskStatuses(pyblish.api.ContextPlugin):
|
|||
}
|
||||
context.data["ftrackTaskStatuses"] = task_statuses_by_type_id
|
||||
context.data["ftrackStatusByTaskId"] = {}
|
||||
self.log.info("Collected ftrack task statuses.")
|
||||
self.log.debug("Collected ftrack task statuses.")
|
||||
|
||||
|
||||
class IntegrateFtrackStatusBase(pyblish.api.InstancePlugin):
|
||||
|
|
@ -116,7 +116,7 @@ class IntegrateFtrackStatusBase(pyblish.api.InstancePlugin):
|
|||
profiles = self.get_status_profiles()
|
||||
if not profiles:
|
||||
project_name = context.data["projectName"]
|
||||
self.log.info((
|
||||
self.log.debug((
|
||||
"Status profiles are not filled for project \"{}\". Skipping"
|
||||
).format(project_name))
|
||||
return
|
||||
|
|
@ -124,7 +124,7 @@ class IntegrateFtrackStatusBase(pyblish.api.InstancePlugin):
|
|||
# Task statuses were not collected -> skip
|
||||
task_statuses_by_type_id = context.data.get("ftrackTaskStatuses")
|
||||
if not task_statuses_by_type_id:
|
||||
self.log.info(
|
||||
self.log.debug(
|
||||
"Ftrack task statuses are not collected. Skipping.")
|
||||
return
|
||||
|
||||
|
|
@ -364,12 +364,12 @@ class IntegrateFtrackTaskStatus(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
task_statuses_by_type_id = context.data.get("ftrackTaskStatuses")
|
||||
if not task_statuses_by_type_id:
|
||||
self.log.info("Ftrack task statuses are not collected. Skipping.")
|
||||
self.log.debug("Ftrack task statuses are not collected. Skipping.")
|
||||
return
|
||||
|
||||
status_by_task_id = self._get_status_by_task_id(context)
|
||||
if not status_by_task_id:
|
||||
self.log.info("No statuses to set. Skipping.")
|
||||
self.log.debug("No statuses to set. Skipping.")
|
||||
return
|
||||
|
||||
ftrack_session = context.data["ftrackSession"]
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ def get_data_subprocess(config_path, data_type):
|
|||
def compatible_python():
|
||||
"""Only 3.9 or higher can directly use PyOpenColorIO in ocio_wrapper"""
|
||||
compatible = False
|
||||
if sys.version[0] == 3 and sys.version[1] >= 9:
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 9:
|
||||
compatible = True
|
||||
return compatible
|
||||
|
||||
|
|
|
|||
|
|
@ -233,36 +233,40 @@ class RepairAction(pyblish.api.Action):
|
|||
raise RuntimeError("Plug-in does not have repair method.")
|
||||
|
||||
# Get the errored instances
|
||||
self.log.info("Finding failed instances..")
|
||||
self.log.debug("Finding failed instances..")
|
||||
errored_instances = get_errored_instances_from_context(context)
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
instances = pyblish.api.instances_by_plugin(errored_instances, plugin)
|
||||
for instance in instances:
|
||||
self.log.debug(
|
||||
"Attempting repair for instance: {} ...".format(instance)
|
||||
)
|
||||
plugin.repair(instance)
|
||||
|
||||
|
||||
class RepairContextAction(pyblish.api.Action):
|
||||
"""Repairs the action
|
||||
|
||||
To process the repairing this requires a static `repair(instance)` method
|
||||
To process the repairing this requires a static `repair(context)` method
|
||||
is available on the plugin.
|
||||
"""
|
||||
|
||||
label = "Repair"
|
||||
on = "failed" # This action is only available on a failed plug-in
|
||||
icon = "wrench" # Icon from Awesome Icon
|
||||
|
||||
def process(self, context, plugin):
|
||||
if not hasattr(plugin, "repair"):
|
||||
raise RuntimeError("Plug-in does not have repair method.")
|
||||
|
||||
# Get the failed instances
|
||||
self.log.info("Finding failed instances..")
|
||||
self.log.debug("Finding failed plug-ins..")
|
||||
failed_plugins = get_errored_plugins_from_context(context)
|
||||
|
||||
# Apply pyblish.logic to get the instances for the plug-in
|
||||
if plugin in failed_plugins:
|
||||
self.log.info("Attempting fix ...")
|
||||
self.log.debug("Attempting repair ...")
|
||||
plugin.repair(context)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -478,7 +478,9 @@ class AbstractTemplateBuilder(object):
|
|||
create_first_version = template_preset["create_first_version"]
|
||||
|
||||
# check if first version is created
|
||||
created_version_workfile = self.create_first_workfile_version()
|
||||
created_version_workfile = False
|
||||
if create_first_version:
|
||||
created_version_workfile = self.create_first_workfile_version()
|
||||
|
||||
# if first version is created, import template
|
||||
# and populate placeholders
|
||||
|
|
@ -1564,7 +1566,16 @@ class PlaceholderLoadMixin(object):
|
|||
else:
|
||||
failed = False
|
||||
self.load_succeed(placeholder, container)
|
||||
self.cleanup_placeholder(placeholder, failed)
|
||||
self.post_placeholder_process(placeholder, failed)
|
||||
|
||||
if failed:
|
||||
self.log.debug(
|
||||
"Placeholder cleanup skipped due to failed placeholder "
|
||||
"population."
|
||||
)
|
||||
return
|
||||
if not placeholder.data.get("keep_placeholder", True):
|
||||
self.delete_placeholder(placeholder)
|
||||
|
||||
def load_failed(self, placeholder, representation):
|
||||
if hasattr(placeholder, "load_failed"):
|
||||
|
|
@ -1574,7 +1585,7 @@ class PlaceholderLoadMixin(object):
|
|||
if hasattr(placeholder, "load_succeed"):
|
||||
placeholder.load_succeed(container)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
def post_placeholder_process(self, placeholder, failed):
|
||||
"""Cleanup placeholder after load of single representation.
|
||||
|
||||
Can be called multiple times during placeholder item populating and is
|
||||
|
|
@ -1588,6 +1599,10 @@ class PlaceholderLoadMixin(object):
|
|||
|
||||
pass
|
||||
|
||||
def delete_placeholder(self, placeholder, failed):
|
||||
"""Called when all item population is done."""
|
||||
self.log.debug("Clean up of placeholder is not implemented.")
|
||||
|
||||
|
||||
class PlaceholderCreateMixin(object):
|
||||
"""Mixin prepared for creating placeholder plugins.
|
||||
|
|
@ -1673,12 +1688,14 @@ class PlaceholderCreateMixin(object):
|
|||
)
|
||||
]
|
||||
|
||||
def populate_create_placeholder(self, placeholder):
|
||||
def populate_create_placeholder(self, placeholder, pre_create_data=None):
|
||||
"""Create placeholder is going to create matching publishabe instance.
|
||||
|
||||
Args:
|
||||
placeholder (PlaceholderItem): Placeholder item with information
|
||||
about requested publishable instance.
|
||||
pre_create_data (dict): dictionary of configuration from Creator
|
||||
configuration in UI
|
||||
"""
|
||||
|
||||
legacy_create = self.builder.use_legacy_creators
|
||||
|
|
@ -1736,7 +1753,8 @@ class PlaceholderCreateMixin(object):
|
|||
creator_plugin.identifier,
|
||||
create_variant,
|
||||
asset_doc,
|
||||
task_name=task_name
|
||||
task_name=task_name,
|
||||
pre_create_data=pre_create_data
|
||||
)
|
||||
|
||||
except: # noqa: E722
|
||||
|
|
@ -1747,7 +1765,7 @@ class PlaceholderCreateMixin(object):
|
|||
failed = False
|
||||
self.create_succeed(placeholder, creator_instance)
|
||||
|
||||
self.cleanup_placeholder(placeholder, failed)
|
||||
self.post_placeholder_process(placeholder, failed)
|
||||
|
||||
def create_failed(self, placeholder, creator_data):
|
||||
if hasattr(placeholder, "create_failed"):
|
||||
|
|
@ -1757,7 +1775,7 @@ class PlaceholderCreateMixin(object):
|
|||
if hasattr(placeholder, "create_succeed"):
|
||||
placeholder.create_succeed(creator_instance)
|
||||
|
||||
def cleanup_placeholder(self, placeholder, failed):
|
||||
def post_placeholder_process(self, placeholder, failed):
|
||||
"""Cleanup placeholder after load of single representation.
|
||||
|
||||
Can be called multiple times during placeholder item populating and is
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ class CleanUp(pyblish.api.InstancePlugin):
|
|||
return
|
||||
|
||||
if not os.path.normpath(staging_dir).startswith(temp_root):
|
||||
self.log.info("Skipping cleanup. Staging directory is not in the "
|
||||
"temp folder: %s" % staging_dir)
|
||||
self.log.debug("Skipping cleanup. Staging directory is not in the "
|
||||
"temp folder: %s" % staging_dir)
|
||||
return
|
||||
|
||||
if not os.path.exists(staging_dir):
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
|
|||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp:
|
||||
temp.write(filter_string)
|
||||
filters_path = temp.name
|
||||
filters = '-filter_script "{}"'.format(filters_path)
|
||||
filters = '-filter_script:v "{}"'.format(filters_path)
|
||||
print("Filters:", filter_string)
|
||||
self.cleanup_paths.append(filters_path)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,18 @@
|
|||
"skip_timelines_check": [
|
||||
".*"
|
||||
]
|
||||
},
|
||||
"ValidateContainers": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
}
|
||||
},
|
||||
"workfile_builder": {
|
||||
"create_first_version": false,
|
||||
"custom_templates": []
|
||||
},
|
||||
"templated_workfile_build": {
|
||||
"profiles": []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,17 @@
|
|||
"label": "Skip Timeline Check for Tasks"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_publish_plugin",
|
||||
"template_data": [
|
||||
{
|
||||
"docstring": "Check if loaded container in scene are latest versions.",
|
||||
"key": "ValidateContainers",
|
||||
"label": "ValidateContainers"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -117,6 +128,10 @@
|
|||
"workfile_builder/builder_on_start",
|
||||
"workfile_builder/profiles"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_templated_workfile_build"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -443,29 +443,29 @@ class PublishPluginsProxy:
|
|||
|
||||
def __init__(self, plugins):
|
||||
plugins_by_id = {}
|
||||
actions_by_id = {}
|
||||
actions_by_plugin_id = {}
|
||||
action_ids_by_plugin_id = {}
|
||||
for plugin in plugins:
|
||||
plugin_id = plugin.id
|
||||
plugins_by_id[plugin_id] = plugin
|
||||
|
||||
action_ids = []
|
||||
actions_by_id = {}
|
||||
action_ids_by_plugin_id[plugin_id] = action_ids
|
||||
actions_by_plugin_id[plugin_id] = actions_by_id
|
||||
|
||||
actions = getattr(plugin, "actions", None) or []
|
||||
for action in actions:
|
||||
action_id = action.id
|
||||
if action_id in actions_by_id:
|
||||
continue
|
||||
action_ids.append(action_id)
|
||||
actions_by_id[action_id] = action
|
||||
|
||||
self._plugins_by_id = plugins_by_id
|
||||
self._actions_by_id = actions_by_id
|
||||
self._actions_by_plugin_id = actions_by_plugin_id
|
||||
self._action_ids_by_plugin_id = action_ids_by_plugin_id
|
||||
|
||||
def get_action(self, action_id):
|
||||
return self._actions_by_id[action_id]
|
||||
def get_action(self, plugin_id, action_id):
|
||||
return self._actions_by_plugin_id[plugin_id][action_id]
|
||||
|
||||
def get_plugin(self, plugin_id):
|
||||
return self._plugins_by_id[plugin_id]
|
||||
|
|
@ -497,7 +497,9 @@ class PublishPluginsProxy:
|
|||
"""
|
||||
|
||||
return [
|
||||
self._create_action_item(self._actions_by_id[action_id], plugin_id)
|
||||
self._create_action_item(
|
||||
self.get_action(plugin_id, action_id), plugin_id
|
||||
)
|
||||
for action_id in self._action_ids_by_plugin_id[plugin_id]
|
||||
]
|
||||
|
||||
|
|
@ -2308,7 +2310,7 @@ class PublisherController(BasePublisherController):
|
|||
def run_action(self, plugin_id, action_id):
|
||||
# TODO handle result in UI
|
||||
plugin = self._publish_plugins_proxy.get_plugin(plugin_id)
|
||||
action = self._publish_plugins_proxy.get_action(action_id)
|
||||
action = self._publish_plugins_proxy.get_action(plugin_id, action_id)
|
||||
|
||||
result = pyblish.plugin.process(
|
||||
plugin, self._publish_context, None, action.id
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.15.11-nightly.4"
|
||||
__version__ = "3.15.12-nightly.1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue