mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into enhancement/OP-2246-unreal_to_openpype
This commit is contained in:
commit
3b44a85541
535 changed files with 45917 additions and 4356 deletions
8
.gitmodules
vendored
8
.gitmodules
vendored
|
|
@ -3,10 +3,4 @@
|
|||
url = https://github.com/pypeclub/avalon-core.git
|
||||
[submodule "repos/avalon-unreal-integration"]
|
||||
path = repos/avalon-unreal-integration
|
||||
url = https://github.com/pypeclub/avalon-unreal-integration.git
|
||||
[submodule "openpype/modules/default_modules/ftrack/python2_vendor/arrow"]
|
||||
path = openpype/modules/default_modules/ftrack/python2_vendor/arrow
|
||||
url = https://github.com/arrow-py/arrow.git
|
||||
[submodule "openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api"]
|
||||
path = openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api
|
||||
url = https://bitbucket.org/ftrack/ftrack-python-api.git
|
||||
url = https://github.com/pypeclub/avalon-unreal-integration.git
|
||||
|
|
@ -42,6 +42,12 @@ def standalonepublisher():
|
|||
PypeCommands().launch_standalone_publisher()
|
||||
|
||||
|
||||
@main.command()
|
||||
def traypublisher():
|
||||
"""Show new OpenPype Standalone publisher UI."""
|
||||
PypeCommands().launch_traypublisher()
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.option("-d", "--debug",
|
||||
is_flag=True, help=("Run pype tray in debug mode"))
|
||||
|
|
@ -371,10 +377,15 @@ def run(script):
|
|||
"--app_variant",
|
||||
help="Provide specific app variant for test, empty for latest",
|
||||
default=None)
|
||||
def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant):
|
||||
@click.option("-t",
|
||||
"--timeout",
|
||||
help="Provide specific timeout value for test case",
|
||||
default=None)
|
||||
def runtests(folder, mark, pyargs, test_data_folder, persist, app_variant,
|
||||
timeout):
|
||||
"""Run all automatic tests after proper initialization via start.py"""
|
||||
PypeCommands().run_tests(folder, mark, pyargs, test_data_folder,
|
||||
persist, app_variant)
|
||||
persist, app_variant, timeout)
|
||||
|
||||
|
||||
@main.command()
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook):
|
|||
"nuke",
|
||||
"nukex",
|
||||
"hiero",
|
||||
"houdini",
|
||||
"nukestudio",
|
||||
"blender",
|
||||
"photoshop",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from openpype.api import Anatomy
|
|||
from openpype.lib import (
|
||||
PreLaunchHook,
|
||||
EnvironmentPrepData,
|
||||
prepare_host_environments,
|
||||
prepare_app_environments,
|
||||
prepare_context_environments
|
||||
)
|
||||
|
||||
|
|
@ -14,14 +14,6 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
|
||||
def execute(self):
|
||||
"""Prepare global objects to `data` that will be used for sure."""
|
||||
if not self.application.is_host:
|
||||
self.log.info(
|
||||
"Skipped hook {}. Application is not marked as host.".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.prepare_global_data()
|
||||
|
||||
if not self.data.get("asset_doc"):
|
||||
|
|
@ -49,7 +41,7 @@ class GlobalHostDataHook(PreLaunchHook):
|
|||
"log": self.log
|
||||
})
|
||||
|
||||
prepare_host_environments(temp_data, self.launch_context.env_group)
|
||||
prepare_app_environments(temp_data, self.launch_context.env_group)
|
||||
prepare_context_environments(temp_data)
|
||||
|
||||
temp_data.pop("log")
|
||||
|
|
|
|||
|
|
@ -527,6 +527,7 @@ def get_segment_attributes(segment):
|
|||
|
||||
# Add timeline segment to tree
|
||||
clip_data = {
|
||||
"shot_name": segment.shot_name.get_value(),
|
||||
"segment_name": segment.name.get_value(),
|
||||
"segment_comment": segment.comment.get_value(),
|
||||
"tape_name": segment.tape_name,
|
||||
|
|
|
|||
|
|
@ -361,6 +361,7 @@ class PublishableClip:
|
|||
vertical_sync_default = False
|
||||
driving_layer_default = ""
|
||||
index_from_segment_default = False
|
||||
use_shot_name_default = False
|
||||
|
||||
def __init__(self, segment, **kwargs):
|
||||
self.rename_index = kwargs["rename_index"]
|
||||
|
|
@ -376,6 +377,7 @@ class PublishableClip:
|
|||
# segment (clip) main attributes
|
||||
self.cs_name = self.clip_data["segment_name"]
|
||||
self.cs_index = int(self.clip_data["segment"])
|
||||
self.shot_name = self.clip_data["shot_name"]
|
||||
|
||||
# get track name and index
|
||||
self.track_index = int(self.clip_data["track"])
|
||||
|
|
@ -419,18 +421,21 @@ class PublishableClip:
|
|||
# deal with clip name
|
||||
new_name = self.marker_data.pop("newClipName")
|
||||
|
||||
if self.rename:
|
||||
if self.rename and not self.use_shot_name:
|
||||
# rename segment
|
||||
self.current_segment.name = str(new_name)
|
||||
self.marker_data["asset"] = str(new_name)
|
||||
elif self.use_shot_name:
|
||||
self.marker_data["asset"] = self.shot_name
|
||||
self.marker_data["hierarchyData"]["shot"] = self.shot_name
|
||||
else:
|
||||
self.marker_data["asset"] = self.cs_name
|
||||
self.marker_data["hierarchyData"]["shot"] = self.cs_name
|
||||
|
||||
if self.marker_data["heroTrack"] and self.review_layer:
|
||||
self.marker_data.update({"reviewTrack": self.review_layer})
|
||||
self.marker_data["reviewTrack"] = self.review_layer
|
||||
else:
|
||||
self.marker_data.update({"reviewTrack": None})
|
||||
self.marker_data["reviewTrack"] = None
|
||||
|
||||
# create pype tag on track_item and add data
|
||||
fpipeline.imprint(self.current_segment, self.marker_data)
|
||||
|
|
@ -463,6 +468,8 @@ class PublishableClip:
|
|||
# ui_inputs data or default values if gui was not used
|
||||
self.rename = self.ui_inputs.get(
|
||||
"clipRename", {}).get("value") or self.rename_default
|
||||
self.use_shot_name = self.ui_inputs.get(
|
||||
"useShotName", {}).get("value") or self.use_shot_name_default
|
||||
self.clip_name = self.ui_inputs.get(
|
||||
"clipName", {}).get("value") or self.clip_name_default
|
||||
self.hierarchy = self.ui_inputs.get(
|
||||
|
|
|
|||
|
|
@ -87,41 +87,48 @@ class CreateShotClip(opfapi.Creator):
|
|||
"target": "tag",
|
||||
"toolTip": "Parents folder for shot root folder, Template filled with `Hierarchy Data` section", # noqa
|
||||
"order": 0},
|
||||
"useShotName": {
|
||||
"value": True,
|
||||
"type": "QCheckBox",
|
||||
"label": "Use Shot Name",
|
||||
"target": "ui",
|
||||
"toolTip": "Use name form Shot name clip attribute", # noqa
|
||||
"order": 1},
|
||||
"clipRename": {
|
||||
"value": False,
|
||||
"type": "QCheckBox",
|
||||
"label": "Rename clips",
|
||||
"target": "ui",
|
||||
"toolTip": "Renaming selected clips on fly", # noqa
|
||||
"order": 1},
|
||||
"order": 2},
|
||||
"clipName": {
|
||||
"value": "{sequence}{shot}",
|
||||
"type": "QLineEdit",
|
||||
"label": "Clip Name Template",
|
||||
"target": "ui",
|
||||
"toolTip": "template for creating shot namespaused for renaming (use rename: on)", # noqa
|
||||
"order": 2},
|
||||
"order": 3},
|
||||
"segmentIndex": {
|
||||
"value": True,
|
||||
"type": "QCheckBox",
|
||||
"label": "Segment index",
|
||||
"target": "ui",
|
||||
"toolTip": "Take number from segment index", # noqa
|
||||
"order": 3},
|
||||
"order": 4},
|
||||
"countFrom": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Count sequence from",
|
||||
"target": "ui",
|
||||
"toolTip": "Set when the sequence number stafrom", # noqa
|
||||
"order": 4},
|
||||
"order": 5},
|
||||
"countSteps": {
|
||||
"value": 10,
|
||||
"type": "QSpinBox",
|
||||
"label": "Stepping number",
|
||||
"target": "ui",
|
||||
"toolTip": "What number is adding every new step", # noqa
|
||||
"order": 5},
|
||||
"order": 6},
|
||||
}
|
||||
},
|
||||
"hierarchyData": {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
<fileType>Jpeg</fileType>
|
||||
<codec>923688</codec>
|
||||
<codecProfile></codecProfile>
|
||||
<namePattern><segment name></namePattern>
|
||||
<namePattern><shot name></namePattern>
|
||||
<compressionQuality>100</compressionQuality>
|
||||
<transferCharacteristic>2</transferCharacteristic>
|
||||
<colorimetricSpecification>4</colorimetricSpecification>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
</sequence>
|
||||
<movie>
|
||||
<fileType>QuickTime</fileType>
|
||||
<namePattern><segment name></namePattern>
|
||||
<namePattern><shot name></namePattern>
|
||||
<yuvHeadroom>0</yuvHeadroom>
|
||||
<yuvColourSpace>PCS_709</yuvColourSpace>
|
||||
<operationalPattern>None</operationalPattern>
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
<targetVersion>2021</targetVersion>
|
||||
<pathSuffix>/profiles/.33622016/HDTV_720p_8Mbits.cdxprof</pathSuffix>
|
||||
</codecProfile>
|
||||
<namePattern><segment name>_<video codec></namePattern>
|
||||
<namePattern><shot name>_<video codec></namePattern>
|
||||
<compressionQuality>50</compressionQuality>
|
||||
<transferCharacteristic>2</transferCharacteristic>
|
||||
<colorimetricSpecification>4</colorimetricSpecification>
|
||||
|
|
@ -8,7 +8,7 @@ PLUGIN_DIR = os.path.dirname(os.path.dirname(__file__))
|
|||
EXPORT_PRESETS_DIR = os.path.join(PLUGIN_DIR, "export_preset")
|
||||
|
||||
CONFIG_DIR = os.path.join(os.path.expanduser(
|
||||
"~/.openpype"), "openpype_flame_to_ftrack")
|
||||
"~/.openpype"), "openpype_babypublisher")
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
|
@ -360,6 +360,8 @@ class FtrackComponentCreator:
|
|||
|
||||
|
||||
class FtrackEntityOperator:
|
||||
existing_tasks = []
|
||||
|
||||
def __init__(self, session, project_entity):
|
||||
self.session = session
|
||||
self.project_entity = project_entity
|
||||
|
|
@ -392,10 +394,7 @@ class FtrackEntityOperator:
|
|||
query = '{} where name is "{}" and project_id is "{}"'.format(
|
||||
type, name, self.project_entity["id"])
|
||||
|
||||
try:
|
||||
entity = session.query(query).one()
|
||||
except Exception:
|
||||
entity = None
|
||||
entity = session.query(query).first()
|
||||
|
||||
# if entity doesnt exist then create one
|
||||
if not entity:
|
||||
|
|
@ -430,10 +429,21 @@ class FtrackEntityOperator:
|
|||
return parents
|
||||
|
||||
def create_task(self, task_type, task_types, parent):
|
||||
existing_task = [
|
||||
_exising_tasks = [
|
||||
child for child in parent['children']
|
||||
if child.entity_type.lower() == 'task'
|
||||
if child['name'].lower() in task_type.lower()
|
||||
]
|
||||
|
||||
# add task into existing tasks if they are not already there
|
||||
for _t in _exising_tasks:
|
||||
if _t in self.existing_tasks:
|
||||
continue
|
||||
self.existing_tasks.append(_t)
|
||||
|
||||
existing_task = [
|
||||
task for task in self.existing_tasks
|
||||
if task['name'].lower() in task_type.lower()
|
||||
if task['parent'] == parent
|
||||
]
|
||||
|
||||
if existing_task:
|
||||
|
|
@ -445,4 +455,5 @@ class FtrackEntityOperator:
|
|||
})
|
||||
task["type"] = task_types[task_type]
|
||||
|
||||
self.existing_tasks.append(task)
|
||||
return task
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from PySide2 import QtWidgets, QtCore
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
import uiwidgets
|
||||
import app_utils
|
||||
|
|
@ -33,11 +33,12 @@ class MainWindow(QtWidgets.QWidget):
|
|||
self.panel_class.clear_temp_data()
|
||||
self.panel_class.close()
|
||||
clear_inner_modules()
|
||||
ftrack_lib.FtrackEntityOperator.existing_tasks = []
|
||||
# now the panel can be closed
|
||||
event.accept()
|
||||
|
||||
|
||||
class FlameToFtrackPanel(object):
|
||||
class FlameBabyPublisherPanel(object):
|
||||
session = None
|
||||
temp_data_dir = None
|
||||
processed_components = []
|
||||
|
|
@ -78,7 +79,7 @@ class FlameToFtrackPanel(object):
|
|||
|
||||
# creating ui
|
||||
self.window.setMinimumSize(1500, 600)
|
||||
self.window.setWindowTitle('Sequence Shots to Ftrack')
|
||||
self.window.setWindowTitle('OpenPype: Baby-publisher')
|
||||
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
self.window.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
|
@ -469,10 +470,14 @@ class FlameToFtrackPanel(object):
|
|||
for sequence in self.selection:
|
||||
frame_rate = float(str(sequence.frame_rate)[:-4])
|
||||
for ver in sequence.versions:
|
||||
for tracks in ver.tracks:
|
||||
for segment in tracks.segments:
|
||||
for track in ver.tracks:
|
||||
if len(track.segments) == 0 and track.hidden:
|
||||
continue
|
||||
for segment in track.segments:
|
||||
print(segment.attributes)
|
||||
if str(segment.name)[1:-1] == "":
|
||||
if segment.name.get_value() == "":
|
||||
continue
|
||||
if segment.hidden.get_value() is True:
|
||||
continue
|
||||
# get clip frame duration
|
||||
record_duration = str(segment.record_duration)[1:-1]
|
||||
|
|
@ -492,11 +497,11 @@ class FlameToFtrackPanel(object):
|
|||
|
||||
# Add timeline segment to tree
|
||||
QtWidgets.QTreeWidgetItem(self.tree, [
|
||||
str(sequence.name)[1:-1], # seq
|
||||
str(segment.name)[1:-1], # shot
|
||||
sequence.name.get_value(), # seq name
|
||||
segment.shot_name.get_value(), # shot name
|
||||
str(clip_duration), # clip duration
|
||||
shot_description, # shot description
|
||||
str(segment.comment)[1:-1] # task description
|
||||
segment.comment.get_value() # task description
|
||||
]).setFlags(
|
||||
QtCore.Qt.ItemIsEditable
|
||||
| QtCore.Qt.ItemIsEnabled
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from PySide2 import QtWidgets, QtCore
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class FlameLabel(QtWidgets.QLabel):
|
||||
|
|
@ -16,10 +16,11 @@ def flame_panel_executor(selection):
|
|||
if "panel_app" in sys.modules.keys():
|
||||
print("panel_app module is already loaded")
|
||||
del sys.modules["panel_app"]
|
||||
import panel_app
|
||||
reload(panel_app) # noqa
|
||||
print("panel_app module removed from sys.modules")
|
||||
|
||||
import panel_app
|
||||
panel_app.FlameToFtrackPanel(selection)
|
||||
panel_app.FlameBabyPublisherPanel(selection)
|
||||
|
||||
|
||||
def scope_sequence(selection):
|
||||
|
|
@ -30,7 +31,7 @@ def scope_sequence(selection):
|
|||
def get_media_panel_custom_ui_actions():
|
||||
return [
|
||||
{
|
||||
"name": "OpenPype: Ftrack",
|
||||
"name": "OpenPype: Baby-publisher",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Create Shots",
|
||||
|
|
@ -176,7 +176,7 @@ def update_frame_range(comp, representations):
|
|||
versions = list(versions)
|
||||
|
||||
versions = [v for v in versions
|
||||
if v["data"].get("startFrame", None) is not None]
|
||||
if v["data"].get("frameStart", None) is not None]
|
||||
|
||||
if not versions:
|
||||
log.warning("No versions loaded to match frame range to.\n")
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ from .lib import (
|
|||
lsattrs,
|
||||
read,
|
||||
|
||||
maintained_selection,
|
||||
unique_name
|
||||
maintained_selection
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -51,8 +50,7 @@ __all__ = [
|
|||
"lsattrs",
|
||||
"read",
|
||||
|
||||
"maintained_selection",
|
||||
"unique_name"
|
||||
"maintained_selection"
|
||||
]
|
||||
|
||||
# Backwards API compatibility
|
||||
|
|
|
|||
|
|
@ -99,65 +99,6 @@ def get_id_required_nodes():
|
|||
return list(nodes)
|
||||
|
||||
|
||||
def get_additional_data(container):
|
||||
"""Not implemented yet!"""
|
||||
return container
|
||||
|
||||
|
||||
def set_parameter_callback(node, parameter, language, callback):
|
||||
"""Link a callback to a parameter of a node
|
||||
|
||||
Args:
|
||||
node(hou.Node): instance of the nodee
|
||||
parameter(str): name of the parameter
|
||||
language(str): name of the language, e.g.: python
|
||||
callback(str): command which needs to be triggered
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
template_grp = node.parmTemplateGroup()
|
||||
template = template_grp.find(parameter)
|
||||
if not template:
|
||||
return
|
||||
|
||||
script_language = (hou.scriptLanguage.Python if language == "python" else
|
||||
hou.scriptLanguage.Hscript)
|
||||
|
||||
template.setScriptCallbackLanguage(script_language)
|
||||
template.setScriptCallback(callback)
|
||||
|
||||
template.setTags({"script_callback": callback,
|
||||
"script_callback_language": language.lower()})
|
||||
|
||||
# Replace the existing template with the adjusted one
|
||||
template_grp.replace(parameter, template)
|
||||
|
||||
node.setParmTemplateGroup(template_grp)
|
||||
|
||||
|
||||
def set_parameter_callbacks(node, parameter_callbacks):
|
||||
"""Set callbacks for multiple parameters of a node
|
||||
|
||||
Args:
|
||||
node(hou.Node): instance of a hou.Node
|
||||
parameter_callbacks(dict): collection of parameter and callback data
|
||||
example: {"active" :
|
||||
{"language": "python",
|
||||
"callback": "print('hello world)'"}
|
||||
}
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
for parameter, data in parameter_callbacks.items():
|
||||
language = data["language"]
|
||||
callback = data["callback"]
|
||||
|
||||
set_parameter_callback(node, parameter, language, callback)
|
||||
|
||||
|
||||
def get_output_parameter(node):
|
||||
"""Return the render output parameter name of the given node
|
||||
|
||||
|
|
@ -189,19 +130,6 @@ def get_output_parameter(node):
|
|||
raise TypeError("Node type '%s' not supported" % node_type)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def attribute_values(node, data):
|
||||
|
||||
previous_attrs = {key: node.parm(key).eval() for key in data.keys()}
|
||||
try:
|
||||
node.setParms(data)
|
||||
yield
|
||||
except Exception as exc:
|
||||
pass
|
||||
finally:
|
||||
node.setParms(previous_attrs)
|
||||
|
||||
|
||||
def set_scene_fps(fps):
|
||||
hou.setFps(fps)
|
||||
|
||||
|
|
@ -349,10 +277,6 @@ def render_rop(ropnode):
|
|||
raise RuntimeError("Render failed: {0}".format(exc))
|
||||
|
||||
|
||||
def children_as_string(node):
|
||||
return [c.name() for c in node.children()]
|
||||
|
||||
|
||||
def imprint(node, data):
|
||||
"""Store attributes with value on a node
|
||||
|
||||
|
|
@ -473,53 +397,6 @@ def read(node):
|
|||
parameter in node.spareParms()}
|
||||
|
||||
|
||||
def unique_name(name, format="%03d", namespace="", prefix="", suffix="",
|
||||
separator="_"):
|
||||
"""Return unique `name`
|
||||
|
||||
The function takes into consideration an optional `namespace`
|
||||
and `suffix`. The suffix is included in evaluating whether a
|
||||
name exists - such as `name` + "_GRP" - but isn't included
|
||||
in the returned value.
|
||||
|
||||
If a namespace is provided, only names within that namespace
|
||||
are considered when evaluating whether the name is unique.
|
||||
|
||||
Arguments:
|
||||
format (str, optional): The `name` is given a number, this determines
|
||||
how this number is formatted. Defaults to a padding of 2.
|
||||
E.g. my_name01, my_name02.
|
||||
namespace (str, optional): Only consider names within this namespace.
|
||||
suffix (str, optional): Only consider names with this suffix.
|
||||
|
||||
Example:
|
||||
>>> name = hou.node("/obj").createNode("geo", name="MyName")
|
||||
>>> assert hou.node("/obj/MyName")
|
||||
True
|
||||
>>> unique = unique_name(name)
|
||||
>>> assert hou.node("/obj/{}".format(unique))
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
iteration = 1
|
||||
|
||||
parts = [prefix, name, format % iteration, suffix]
|
||||
if namespace:
|
||||
parts.insert(0, namespace)
|
||||
|
||||
unique = separator.join(parts)
|
||||
children = children_as_string(hou.node("/obj"))
|
||||
while unique in children:
|
||||
iteration += 1
|
||||
unique = separator.join(parts)
|
||||
|
||||
if suffix:
|
||||
return unique[:-len(suffix)]
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
@contextmanager
|
||||
def maintained_selection():
|
||||
"""Maintain selection during context
|
||||
|
|
@ -542,3 +419,37 @@ def maintained_selection():
|
|||
if previous_selection:
|
||||
for node in previous_selection:
|
||||
node.setSelected(on=True)
|
||||
|
||||
|
||||
def reset_framerange():
|
||||
"""Set frame range to current asset"""
|
||||
|
||||
asset_name = api.Session["AVALON_ASSET"]
|
||||
asset = io.find_one({"name": asset_name, "type": "asset"})
|
||||
|
||||
frame_start = asset["data"].get("frameStart")
|
||||
frame_end = asset["data"].get("frameEnd")
|
||||
# Backwards compatibility
|
||||
if frame_start is None or frame_end is None:
|
||||
frame_start = asset["data"].get("edit_in")
|
||||
frame_end = asset["data"].get("edit_out")
|
||||
|
||||
if frame_start is None or frame_end is None:
|
||||
log.warning("No edit information found for %s" % asset_name)
|
||||
return
|
||||
|
||||
handles = asset["data"].get("handles") or 0
|
||||
handle_start = asset["data"].get("handleStart")
|
||||
if handle_start is None:
|
||||
handle_start = handles
|
||||
|
||||
handle_end = asset["data"].get("handleEnd")
|
||||
if handle_end is None:
|
||||
handle_end = handles
|
||||
|
||||
frame_start -= int(handle_start)
|
||||
frame_end += int(handle_end)
|
||||
|
||||
hou.playbar.setFrameRange(frame_start, frame_end)
|
||||
hou.playbar.setPlaybackRange(frame_start, frame_end)
|
||||
hou.setFrame(frame_start)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import logging
|
|||
import contextlib
|
||||
|
||||
import hou
|
||||
import hdefereval
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
|
|
@ -66,9 +67,10 @@ def install():
|
|||
|
||||
sys.path.append(hou_pythonpath)
|
||||
|
||||
# Set asset FPS for the empty scene directly after launch of Houdini
|
||||
# so it initializes into the correct scene FPS
|
||||
_set_asset_fps()
|
||||
# Set asset settings for the empty scene directly after launch of Houdini
|
||||
# so it initializes into the correct scene FPS, Frame Range, etc.
|
||||
# todo: make sure this doesn't trigger when opening with last workfile
|
||||
_set_context_settings()
|
||||
|
||||
|
||||
def uninstall():
|
||||
|
|
@ -279,18 +281,49 @@ def on_open(*args):
|
|||
|
||||
def on_new(_):
|
||||
"""Set project resolution and fps when create a new file"""
|
||||
|
||||
if hou.hipFile.isLoadingHipFile():
|
||||
# This event also triggers when Houdini opens a file due to the
|
||||
# new event being registered to 'afterClear'. As such we can skip
|
||||
# 'new' logic if the user is opening a file anyway
|
||||
log.debug("Skipping on new callback due to scene being opened.")
|
||||
return
|
||||
|
||||
log.info("Running callback on new..")
|
||||
_set_asset_fps()
|
||||
_set_context_settings()
|
||||
|
||||
# It seems that the current frame always gets reset to frame 1 on
|
||||
# new scene. So we enforce current frame to be at the start of the playbar
|
||||
# with execute deferred
|
||||
def _enforce_start_frame():
|
||||
start = hou.playbar.playbackRange()[0]
|
||||
hou.setFrame(start)
|
||||
|
||||
hdefereval.executeDeferred(_enforce_start_frame)
|
||||
|
||||
|
||||
def _set_asset_fps():
|
||||
"""Set Houdini scene FPS to the default required for current asset"""
|
||||
def _set_context_settings():
|
||||
"""Apply the project settings from the project definition
|
||||
|
||||
Settings can be overwritten by an asset if the asset.data contains
|
||||
any information regarding those settings.
|
||||
|
||||
Examples of settings:
|
||||
fps
|
||||
resolution
|
||||
renderer
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# Set new scene fps
|
||||
fps = get_asset_fps()
|
||||
print("Setting scene FPS to %i" % fps)
|
||||
lib.set_scene_fps(fps)
|
||||
|
||||
lib.reset_framerange()
|
||||
|
||||
|
||||
def on_pyblish_instance_toggled(instance, new_value, old_value):
|
||||
"""Toggle saver tool passthrough states on instance toggles."""
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ class SetFrameRangeLoader(api.Loader):
|
|||
version = context["version"]
|
||||
version_data = version.get("data", {})
|
||||
|
||||
start = version_data.get("startFrame", None)
|
||||
end = version_data.get("endFrame", None)
|
||||
start = version_data.get("frameStart", None)
|
||||
end = version_data.get("frameEnd", None)
|
||||
|
||||
if start is None or end is None:
|
||||
print(
|
||||
|
|
@ -67,8 +67,8 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
|
|||
version = context["version"]
|
||||
version_data = version.get("data", {})
|
||||
|
||||
start = version_data.get("startFrame", None)
|
||||
end = version_data.get("endFrame", None)
|
||||
start = version_data.get("frameStart", None)
|
||||
end = version_data.get("frameEnd", None)
|
||||
|
||||
if start is None or end is None:
|
||||
print(
|
||||
|
|
@ -78,9 +78,8 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
|
|||
return
|
||||
|
||||
# Include handles
|
||||
handles = version_data.get("handles", 0)
|
||||
start -= handles
|
||||
end += handles
|
||||
start -= version_data.get("handleStart", 0)
|
||||
end += version_data.get("handleEnd", 0)
|
||||
|
||||
hou.playbar.setFrameRange(start, end)
|
||||
hou.playbar.setPlaybackRange(start, end)
|
||||
|
|
|
|||
|
|
@ -2,26 +2,14 @@ import pyblish.api
|
|||
import avalon.api
|
||||
|
||||
|
||||
class SaveCurrentScene(pyblish.api.InstancePlugin):
|
||||
class SaveCurrentScene(pyblish.api.ContextPlugin):
|
||||
"""Save current scene"""
|
||||
|
||||
label = "Save current file"
|
||||
order = pyblish.api.IntegratorOrder - 0.49
|
||||
order = pyblish.api.ExtractorOrder - 0.49
|
||||
hosts = ["houdini"]
|
||||
families = ["usdrender",
|
||||
"redshift_rop"]
|
||||
targets = ["local"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
# This should be a ContextPlugin, but this is a workaround
|
||||
# for a bug in pyblish to run once for a family: issue #250
|
||||
context = instance.context
|
||||
key = "__hasRun{}".format(self.__class__.__name__)
|
||||
if context.data.get(key, False):
|
||||
return
|
||||
else:
|
||||
context.data[key] = True
|
||||
def process(self, context):
|
||||
|
||||
# Filename must not have changed since collecting
|
||||
host = avalon.api.registered_host()
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class SaveCurrentSceneDeadline(pyblish.api.ContextPlugin):
|
||||
"""Save current scene"""
|
||||
|
||||
label = "Save current file"
|
||||
order = pyblish.api.IntegratorOrder - 0.49
|
||||
hosts = ["houdini"]
|
||||
targets = ["deadline"]
|
||||
|
||||
def process(self, context):
|
||||
import hou
|
||||
|
||||
assert (
|
||||
context.data["currentFile"] == hou.hipFile.path()
|
||||
), "Collected filename from current scene name."
|
||||
|
||||
if hou.hipFile.hasUnsavedChanges():
|
||||
self.log.info("Saving current file..")
|
||||
hou.hipFile.save(save_to_recent_files=True)
|
||||
else:
|
||||
self.log.debug("No unsaved changes, skipping file save..")
|
||||
|
|
@ -65,7 +65,7 @@ class ValidateAbcPrimitiveToDetail(pyblish.api.InstancePlugin):
|
|||
cls.log.debug("Checking with path attribute: %s" % path_attr)
|
||||
|
||||
# Check if the primitive attribute exists
|
||||
frame = instance.data.get("startFrame", 0)
|
||||
frame = instance.data.get("frameStart", 0)
|
||||
geo = output.geometryAtFrame(frame)
|
||||
|
||||
# If there are no primitives on the start frame then it might be
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin):
|
|||
cls.log.warning("No geometry output node found, skipping check..")
|
||||
return
|
||||
|
||||
frame = instance.data.get("startFrame", 0)
|
||||
frame = instance.data.get("frameStart", 0)
|
||||
geo = node.geometryAtFrame(frame)
|
||||
|
||||
invalid = False
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class ValidateOutputNode(pyblish.api.InstancePlugin):
|
||||
"""Validate the instance SOP Output Node.
|
||||
|
||||
This will ensure:
|
||||
- The SOP Path is set.
|
||||
- The SOP Path refers to an existing object.
|
||||
- The SOP Path node is a SOP node.
|
||||
- The SOP Path node has at least one input connection (has an input)
|
||||
- The SOP Path has geometry data.
|
||||
|
||||
"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
families = ["pointcache", "vdbcache"]
|
||||
hosts = ["houdini"]
|
||||
label = "Validate Output Node"
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Output node(s) `%s` are incorrect. "
|
||||
"See plug-in log for details." % invalid
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
import hou
|
||||
|
||||
output_node = instance.data["output_node"]
|
||||
|
||||
if output_node is None:
|
||||
node = instance[0]
|
||||
cls.log.error(
|
||||
"SOP Output node in '%s' does not exist. "
|
||||
"Ensure a valid SOP output path is set." % node.path()
|
||||
)
|
||||
|
||||
return [node.path()]
|
||||
|
||||
# Output node must be a Sop node.
|
||||
if not isinstance(output_node, hou.SopNode):
|
||||
cls.log.error(
|
||||
"Output node %s is not a SOP node. "
|
||||
"SOP Path must point to a SOP node, "
|
||||
"instead found category type: %s"
|
||||
% (output_node.path(), output_node.type().category().name())
|
||||
)
|
||||
return [output_node.path()]
|
||||
|
||||
# For the sake of completeness also assert the category type
|
||||
# is Sop to avoid potential edge case scenarios even though
|
||||
# the isinstance check above should be stricter than this category
|
||||
assert output_node.type().category().name() == "Sop", (
|
||||
"Output node %s is not of category Sop. This is a bug.."
|
||||
% output_node.path()
|
||||
)
|
||||
|
||||
# Check if output node has incoming connections
|
||||
if not output_node.inputConnections():
|
||||
cls.log.error(
|
||||
"Output node `%s` has no incoming connections"
|
||||
% output_node.path()
|
||||
)
|
||||
return [output_node.path()]
|
||||
|
||||
# Ensure the output node has at least Geometry data
|
||||
if not output_node.geometry():
|
||||
cls.log.error(
|
||||
"Output node `%s` has no geometry data." % output_node.path()
|
||||
)
|
||||
return [output_node.path()]
|
||||
|
|
@ -51,7 +51,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin):
|
|||
cls.log.debug("Checking for attribute: %s" % path_attr)
|
||||
|
||||
# Check if the primitive attribute exists
|
||||
frame = instance.data.get("startFrame", 0)
|
||||
frame = instance.data.get("frameStart", 0)
|
||||
geo = output.geometryAtFrame(frame)
|
||||
|
||||
# If there are no primitives on the current frame then we can't
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ host_tools.show_workfiles(parent)
|
|||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
<scriptItem id="reset_frame_range">
|
||||
<label>Reset Frame Range</label>
|
||||
<scriptCode><![CDATA[
|
||||
import openpype.hosts.houdini.api.lib
|
||||
openpype.hosts.houdini.api.lib.reset_framerange()
|
||||
]]></scriptCode>
|
||||
</scriptItem>
|
||||
|
||||
<separatorItem/>
|
||||
<scriptItem id="experimental_tools">
|
||||
<label>Experimental tools...</label>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ from .pipeline import (
|
|||
|
||||
ls,
|
||||
containerise,
|
||||
|
||||
lock,
|
||||
unlock,
|
||||
is_locked,
|
||||
lock_ignored,
|
||||
|
||||
)
|
||||
from .plugin import (
|
||||
Creator,
|
||||
|
|
@ -38,11 +32,9 @@ from .lib import (
|
|||
read,
|
||||
|
||||
apply_shaders,
|
||||
without_extension,
|
||||
maintained_selection,
|
||||
suspended_refresh,
|
||||
|
||||
unique_name,
|
||||
unique_namespace,
|
||||
)
|
||||
|
||||
|
|
@ -54,11 +46,6 @@ __all__ = [
|
|||
"ls",
|
||||
"containerise",
|
||||
|
||||
"lock",
|
||||
"unlock",
|
||||
"is_locked",
|
||||
"lock_ignored",
|
||||
|
||||
"Creator",
|
||||
"Loader",
|
||||
|
||||
|
|
@ -76,11 +63,9 @@ __all__ = [
|
|||
"lsattrs",
|
||||
"read",
|
||||
|
||||
"unique_name",
|
||||
"unique_namespace",
|
||||
|
||||
"apply_shaders",
|
||||
"without_extension",
|
||||
"maintained_selection",
|
||||
"suspended_refresh",
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import logging
|
|||
|
||||
from functools import partial
|
||||
|
||||
import maya.cmds as mc
|
||||
import maya.cmds as cmds
|
||||
import maya.mel as mel
|
||||
|
||||
from openpype.api import resources
|
||||
|
|
@ -30,9 +30,9 @@ def override_component_mask_commands():
|
|||
log.info("Installing override_component_mask_commands..")
|
||||
|
||||
# Get all object mask buttons
|
||||
buttons = mc.formLayout("objectMaskIcons",
|
||||
query=True,
|
||||
childArray=True)
|
||||
buttons = cmds.formLayout("objectMaskIcons",
|
||||
query=True,
|
||||
childArray=True)
|
||||
# Skip the triangle list item
|
||||
buttons = [btn for btn in buttons if btn != "objPickMenuLayout"]
|
||||
|
||||
|
|
@ -43,14 +43,14 @@ def override_component_mask_commands():
|
|||
# toggle the others based on whether any of the buttons
|
||||
# was remaining active after the toggle, if not then
|
||||
# enable all
|
||||
if mc.getModifiers() == 4: # = CTRL
|
||||
if cmds.getModifiers() == 4: # = CTRL
|
||||
state = True
|
||||
active = [mc.iconTextCheckBox(btn, query=True, value=True) for btn
|
||||
in buttons]
|
||||
active = [cmds.iconTextCheckBox(btn, query=True, value=True)
|
||||
for btn in buttons]
|
||||
if any(active):
|
||||
mc.selectType(allObjects=False)
|
||||
cmds.selectType(allObjects=False)
|
||||
else:
|
||||
mc.selectType(allObjects=True)
|
||||
cmds.selectType(allObjects=True)
|
||||
|
||||
# Replace #1 with the current button state
|
||||
cmd = raw_command.replace(" #1", " {}".format(int(state)))
|
||||
|
|
@ -63,13 +63,13 @@ def override_component_mask_commands():
|
|||
# try to implement the fix. (This also allows us to
|
||||
# "uninstall" the behavior later)
|
||||
if btn not in COMPONENT_MASK_ORIGINAL:
|
||||
original = mc.iconTextCheckBox(btn, query=True, cc=True)
|
||||
original = cmds.iconTextCheckBox(btn, query=True, cc=True)
|
||||
COMPONENT_MASK_ORIGINAL[btn] = original
|
||||
|
||||
# Assign the special callback
|
||||
original = COMPONENT_MASK_ORIGINAL[btn]
|
||||
new_fn = partial(on_changed_callback, original)
|
||||
mc.iconTextCheckBox(btn, edit=True, cc=new_fn)
|
||||
cmds.iconTextCheckBox(btn, edit=True, cc=new_fn)
|
||||
|
||||
|
||||
def override_toolbox_ui():
|
||||
|
|
@ -78,25 +78,36 @@ def override_toolbox_ui():
|
|||
parent_widget = get_main_window()
|
||||
|
||||
# Ensure the maya web icon on toolbox exists
|
||||
web_button = "ToolBox|MainToolboxLayout|mayaWebButton"
|
||||
if not mc.iconTextButton(web_button, query=True, exists=True):
|
||||
button_names = [
|
||||
# Maya 2022.1+ with maya.cmds.iconTextStaticLabel
|
||||
"ToolBox|MainToolboxLayout|mayaHomeToolboxButton",
|
||||
# Older with maya.cmds.iconTextButton
|
||||
"ToolBox|MainToolboxLayout|mayaWebButton"
|
||||
]
|
||||
for name in button_names:
|
||||
if cmds.control(name, query=True, exists=True):
|
||||
web_button = name
|
||||
break
|
||||
else:
|
||||
# Button does not exist
|
||||
log.warning("Can't find Maya Home/Web button to override toolbox ui..")
|
||||
return
|
||||
|
||||
mc.iconTextButton(web_button, edit=True, visible=False)
|
||||
cmds.control(web_button, edit=True, visible=False)
|
||||
|
||||
# real = 32, but 36 with padding - according to toolbox mel script
|
||||
icon_size = 36
|
||||
parent = web_button.rsplit("|", 1)[0]
|
||||
|
||||
# Ensure the parent is a formLayout
|
||||
if not mc.objectTypeUI(parent) == "formLayout":
|
||||
if not cmds.objectTypeUI(parent) == "formLayout":
|
||||
return
|
||||
|
||||
# Create our controls
|
||||
controls = []
|
||||
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_lookmanager",
|
||||
annotation="Look Manager",
|
||||
label="Look Manager",
|
||||
|
|
@ -109,7 +120,7 @@ def override_toolbox_ui():
|
|||
)
|
||||
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_workfiles",
|
||||
annotation="Work Files",
|
||||
label="Work Files",
|
||||
|
|
@ -124,7 +135,7 @@ def override_toolbox_ui():
|
|||
)
|
||||
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_loader",
|
||||
annotation="Loader",
|
||||
label="Loader",
|
||||
|
|
@ -139,7 +150,7 @@ def override_toolbox_ui():
|
|||
)
|
||||
|
||||
controls.append(
|
||||
mc.iconTextButton(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_manager",
|
||||
annotation="Inventory",
|
||||
label="Inventory",
|
||||
|
|
@ -159,7 +170,7 @@ def override_toolbox_ui():
|
|||
for i, control in enumerate(controls):
|
||||
previous = controls[i - 1] if i > 0 else web_button
|
||||
|
||||
mc.formLayout(parent, edit=True,
|
||||
attachControl=[control, "bottom", 0, previous],
|
||||
attachForm=([control, "left", 1],
|
||||
[control, "right", 1]))
|
||||
cmds.formLayout(parent, edit=True,
|
||||
attachControl=[control, "bottom", 0, previous],
|
||||
attachForm=([control, "left", 1],
|
||||
[control, "right", 1]))
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import platform
|
||||
import uuid
|
||||
import math
|
||||
|
||||
import json
|
||||
import logging
|
||||
import itertools
|
||||
import contextlib
|
||||
from collections import OrderedDict, defaultdict
|
||||
from math import ceil
|
||||
|
|
@ -154,53 +152,9 @@ def maintained_selection():
|
|||
cmds.select(clear=True)
|
||||
|
||||
|
||||
def unique_name(name, format="%02d", namespace="", prefix="", suffix=""):
|
||||
"""Return unique `name`
|
||||
|
||||
The function takes into consideration an optional `namespace`
|
||||
and `suffix`. The suffix is included in evaluating whether a
|
||||
name exists - such as `name` + "_GRP" - but isn't included
|
||||
in the returned value.
|
||||
|
||||
If a namespace is provided, only names within that namespace
|
||||
are considered when evaluating whether the name is unique.
|
||||
|
||||
Arguments:
|
||||
format (str, optional): The `name` is given a number, this determines
|
||||
how this number is formatted. Defaults to a padding of 2.
|
||||
E.g. my_name01, my_name02.
|
||||
namespace (str, optional): Only consider names within this namespace.
|
||||
suffix (str, optional): Only consider names with this suffix.
|
||||
|
||||
Example:
|
||||
>>> name = cmds.createNode("transform", name="MyName")
|
||||
>>> cmds.objExists(name)
|
||||
True
|
||||
>>> unique = unique_name(name)
|
||||
>>> cmds.objExists(unique)
|
||||
False
|
||||
|
||||
"""
|
||||
|
||||
iteration = 1
|
||||
unique = prefix + (name + format % iteration) + suffix
|
||||
|
||||
while cmds.objExists(namespace + ":" + unique):
|
||||
iteration += 1
|
||||
unique = prefix + (name + format % iteration) + suffix
|
||||
|
||||
if suffix:
|
||||
return unique[:-len(suffix)]
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def unique_namespace(namespace, format="%02d", prefix="", suffix=""):
|
||||
"""Return unique namespace
|
||||
|
||||
Similar to :func:`unique_name` but evaluating namespaces
|
||||
as opposed to object names.
|
||||
|
||||
Arguments:
|
||||
namespace (str): Name of namespace to consider
|
||||
format (str, optional): Formatting of the given iteration number
|
||||
|
|
@ -312,155 +266,10 @@ def float_round(num, places=0, direction=ceil):
|
|||
|
||||
def pairwise(iterable):
|
||||
"""s -> (s0,s1), (s2,s3), (s4, s5), ..."""
|
||||
from six.moves import zip
|
||||
|
||||
a = iter(iterable)
|
||||
return itertools.izip(a, a)
|
||||
|
||||
|
||||
def unique(name):
|
||||
assert isinstance(name, string_types), "`name` must be string"
|
||||
|
||||
while cmds.objExists(name):
|
||||
matches = re.findall(r"\d+$", name)
|
||||
|
||||
if matches:
|
||||
match = matches[-1]
|
||||
name = name.rstrip(match)
|
||||
number = int(match) + 1
|
||||
else:
|
||||
number = 1
|
||||
|
||||
name = name + str(number)
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def uv_from_element(element):
|
||||
"""Return the UV coordinate of given 'element'
|
||||
|
||||
Supports components, meshes, nurbs.
|
||||
|
||||
"""
|
||||
|
||||
supported = ["mesh", "nurbsSurface"]
|
||||
|
||||
uv = [0.5, 0.5]
|
||||
|
||||
if "." not in element:
|
||||
type = cmds.nodeType(element)
|
||||
if type == "transform":
|
||||
geometry_shape = cmds.listRelatives(element, shapes=True)
|
||||
|
||||
if len(geometry_shape) >= 1:
|
||||
geometry_shape = geometry_shape[0]
|
||||
else:
|
||||
return
|
||||
|
||||
elif type in supported:
|
||||
geometry_shape = element
|
||||
|
||||
else:
|
||||
cmds.error("Could not do what you wanted..")
|
||||
return
|
||||
else:
|
||||
# If it is indeed a component - get the current Mesh
|
||||
try:
|
||||
parent = element.split(".", 1)[0]
|
||||
|
||||
# Maya is funny in that when the transform of the shape
|
||||
# of the component element has children, the name returned
|
||||
# by that elementection is the shape. Otherwise, it is
|
||||
# the transform. So lets see what type we're dealing with here.
|
||||
if cmds.nodeType(parent) in supported:
|
||||
geometry_shape = parent
|
||||
else:
|
||||
geometry_shape = cmds.listRelatives(parent, shapes=1)[0]
|
||||
|
||||
if not geometry_shape:
|
||||
cmds.error("Skipping %s: Could not find shape." % element)
|
||||
return
|
||||
|
||||
if len(cmds.ls(geometry_shape)) > 1:
|
||||
cmds.warning("Multiple shapes with identical "
|
||||
"names found. This might not work")
|
||||
|
||||
except TypeError as e:
|
||||
cmds.warning("Skipping %s: Didn't find a shape "
|
||||
"for component elementection. %s" % (element, e))
|
||||
return
|
||||
|
||||
try:
|
||||
type = cmds.nodeType(geometry_shape)
|
||||
|
||||
if type == "nurbsSurface":
|
||||
# If a surfacePoint is elementected on a nurbs surface
|
||||
root, u, v = element.rsplit("[", 2)
|
||||
uv = [float(u[:-1]), float(v[:-1])]
|
||||
|
||||
if type == "mesh":
|
||||
# -----------
|
||||
# Average the U and V values
|
||||
# ===========
|
||||
uvs = cmds.polyListComponentConversion(element, toUV=1)
|
||||
if not uvs:
|
||||
cmds.warning("Couldn't derive any UV's from "
|
||||
"component, reverting to default U and V")
|
||||
raise TypeError
|
||||
|
||||
# Flatten list of Uv's as sometimes it returns
|
||||
# neighbors like this [2:3] instead of [2], [3]
|
||||
flattened = []
|
||||
|
||||
for uv in uvs:
|
||||
flattened.extend(cmds.ls(uv, flatten=True))
|
||||
|
||||
uvs = flattened
|
||||
|
||||
sumU = 0
|
||||
sumV = 0
|
||||
for uv in uvs:
|
||||
try:
|
||||
u, v = cmds.polyEditUV(uv, query=True)
|
||||
except Exception:
|
||||
cmds.warning("Couldn't find any UV coordinated, "
|
||||
"reverting to default U and V")
|
||||
raise TypeError
|
||||
|
||||
sumU += u
|
||||
sumV += v
|
||||
|
||||
averagedU = sumU / len(uvs)
|
||||
averagedV = sumV / len(uvs)
|
||||
|
||||
uv = [averagedU, averagedV]
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
return uv
|
||||
|
||||
|
||||
def shape_from_element(element):
|
||||
"""Return shape of given 'element'
|
||||
|
||||
Supports components, meshes, and surfaces
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
# Get either shape or transform, based on element-type
|
||||
node = cmds.ls(element, objectsOnly=True)[0]
|
||||
except Exception:
|
||||
cmds.warning("Could not find node in %s" % element)
|
||||
return None
|
||||
|
||||
if cmds.nodeType(node) == 'transform':
|
||||
try:
|
||||
return cmds.listRelatives(node, shapes=True)[0]
|
||||
except Exception:
|
||||
cmds.warning("Could not find shape in %s" % element)
|
||||
return None
|
||||
|
||||
else:
|
||||
return node
|
||||
return zip(a, a)
|
||||
|
||||
|
||||
def export_alembic(nodes,
|
||||
|
|
@ -546,7 +355,8 @@ def collect_animation_data(fps=False):
|
|||
data = OrderedDict()
|
||||
data["frameStart"] = start
|
||||
data["frameEnd"] = end
|
||||
data["handles"] = 0
|
||||
data["handleStart"] = 0
|
||||
data["handleEnd"] = 0
|
||||
data["step"] = 1.0
|
||||
|
||||
if fps:
|
||||
|
|
@ -607,115 +417,6 @@ def imprint(node, data):
|
|||
cmds.setAttr(node + "." + key, value, **set_type)
|
||||
|
||||
|
||||
def serialise_shaders(nodes):
|
||||
"""Generate a shader set dictionary
|
||||
|
||||
Arguments:
|
||||
nodes (list): Absolute paths to nodes
|
||||
|
||||
Returns:
|
||||
dictionary of (shader: id) pairs
|
||||
|
||||
Schema:
|
||||
{
|
||||
"shader1": ["id1", "id2"],
|
||||
"shader2": ["id3", "id1"]
|
||||
}
|
||||
|
||||
Example:
|
||||
{
|
||||
"Bazooka_Brothers01_:blinn4SG": [
|
||||
"f9520572-ac1d-11e6-b39e-3085a99791c9.f[4922:5001]",
|
||||
"f9520572-ac1d-11e6-b39e-3085a99791c9.f[4587:4634]",
|
||||
"f9520572-ac1d-11e6-b39e-3085a99791c9.f[1120:1567]",
|
||||
"f9520572-ac1d-11e6-b39e-3085a99791c9.f[4251:4362]"
|
||||
],
|
||||
"lambert2SG": [
|
||||
"f9520571-ac1d-11e6-9dbb-3085a99791c9"
|
||||
]
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
valid_nodes = cmds.ls(
|
||||
nodes,
|
||||
long=True,
|
||||
recursive=True,
|
||||
showType=True,
|
||||
objectsOnly=True,
|
||||
type="transform"
|
||||
)
|
||||
|
||||
meshes_by_id = {}
|
||||
for mesh in valid_nodes:
|
||||
shapes = cmds.listRelatives(valid_nodes[0],
|
||||
shapes=True,
|
||||
fullPath=True) or list()
|
||||
|
||||
if shapes:
|
||||
shape = shapes[0]
|
||||
if not cmds.nodeType(shape):
|
||||
continue
|
||||
|
||||
try:
|
||||
id_ = cmds.getAttr(mesh + ".mbID")
|
||||
|
||||
if id_ not in meshes_by_id:
|
||||
meshes_by_id[id_] = list()
|
||||
|
||||
meshes_by_id[id_].append(mesh)
|
||||
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
meshes_by_shader = dict()
|
||||
for mesh in meshes_by_id.values():
|
||||
shape = cmds.listRelatives(mesh,
|
||||
shapes=True,
|
||||
fullPath=True) or list()
|
||||
|
||||
for shader in cmds.listConnections(shape,
|
||||
type="shadingEngine") or list():
|
||||
|
||||
# Objects in this group are those that haven't got
|
||||
# any shaders. These are expected to be managed
|
||||
# elsewhere, such as by the default model loader.
|
||||
if shader == "initialShadingGroup":
|
||||
continue
|
||||
|
||||
if shader not in meshes_by_shader:
|
||||
meshes_by_shader[shader] = list()
|
||||
|
||||
shaded = cmds.sets(shader, query=True) or list()
|
||||
meshes_by_shader[shader].extend(shaded)
|
||||
|
||||
shader_by_id = {}
|
||||
for shader, shaded in meshes_by_shader.items():
|
||||
|
||||
if shader not in shader_by_id:
|
||||
shader_by_id[shader] = list()
|
||||
|
||||
for mesh in shaded:
|
||||
|
||||
# Enable shader assignment to faces.
|
||||
name = mesh.split(".f[")[0]
|
||||
|
||||
transform = name
|
||||
if cmds.objectType(transform) == "mesh":
|
||||
transform = cmds.listRelatives(name, parent=True)[0]
|
||||
|
||||
try:
|
||||
id_ = cmds.getAttr(transform + ".mbID")
|
||||
shader_by_id[shader].append(mesh.replace(name, id_))
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
# Remove duplicates
|
||||
shader_by_id[shader] = list(set(shader_by_id[shader]))
|
||||
|
||||
return shader_by_id
|
||||
|
||||
|
||||
def lsattr(attr, value=None):
|
||||
"""Return nodes matching `key` and `value`
|
||||
|
||||
|
|
@ -794,17 +495,6 @@ def lsattrs(attrs):
|
|||
return list(matches)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def without_extension():
|
||||
"""Use cmds.file with defaultExtensions=False"""
|
||||
previous_setting = cmds.file(defaultExtensions=True, query=True)
|
||||
try:
|
||||
cmds.file(defaultExtensions=False)
|
||||
yield
|
||||
finally:
|
||||
cmds.file(defaultExtensions=previous_setting)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def attribute_values(attr_values):
|
||||
"""Remaps node attributes to values during context.
|
||||
|
|
@ -883,26 +573,6 @@ def evaluation(mode="off"):
|
|||
cmds.evaluationManager(mode=original)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def no_refresh():
|
||||
"""Temporarily disables Maya's UI updates
|
||||
|
||||
Note:
|
||||
This only disabled the main pane and will sometimes still
|
||||
trigger updates in torn off panels.
|
||||
|
||||
"""
|
||||
|
||||
pane = _get_mel_global('gMainPane')
|
||||
state = cmds.paneLayout(pane, query=True, manage=True)
|
||||
cmds.paneLayout(pane, edit=True, manage=False)
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
cmds.paneLayout(pane, edit=True, manage=state)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def empty_sets(sets, force=False):
|
||||
"""Remove all members of the sets during the context"""
|
||||
|
|
@ -1569,15 +1239,6 @@ def extract_alembic(file,
|
|||
return file
|
||||
|
||||
|
||||
def maya_temp_folder():
|
||||
scene_dir = os.path.dirname(cmds.file(query=True, sceneName=True))
|
||||
tmp_dir = os.path.abspath(os.path.join(scene_dir, "..", "tmp"))
|
||||
if not os.path.isdir(tmp_dir):
|
||||
os.makedirs(tmp_dir)
|
||||
|
||||
return tmp_dir
|
||||
|
||||
|
||||
# region ID
|
||||
def get_id_required_nodes(referenced_nodes=False, nodes=None):
|
||||
"""Filter out any node which are locked (reference) or readOnly
|
||||
|
|
@ -1762,22 +1423,6 @@ def set_id(node, unique_id, overwrite=False):
|
|||
cmds.setAttr(attr, unique_id, type="string")
|
||||
|
||||
|
||||
def remove_id(node):
|
||||
"""Remove the id attribute from the input node.
|
||||
|
||||
Args:
|
||||
node (str): The node name
|
||||
|
||||
Returns:
|
||||
bool: Whether an id attribute was deleted
|
||||
|
||||
"""
|
||||
if cmds.attributeQuery("cbId", node=node, exists=True):
|
||||
cmds.deleteAttr("{}.cbId".format(node))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# endregion ID
|
||||
def get_reference_node(path):
|
||||
"""
|
||||
|
|
@ -2453,6 +2098,7 @@ def reset_scene_resolution():
|
|||
|
||||
set_scene_resolution(width, height, pixelAspect)
|
||||
|
||||
|
||||
def set_context_settings():
|
||||
"""Apply the project settings from the project definition
|
||||
|
||||
|
|
@ -2911,7 +2557,7 @@ def get_attr_in_layer(attr, layer):
|
|||
|
||||
|
||||
def fix_incompatible_containers():
|
||||
"""Return whether the current scene has any outdated content"""
|
||||
"""Backwards compatibility: old containers to use new ReferenceLoader"""
|
||||
|
||||
host = api.registered_host()
|
||||
for container in host.ls():
|
||||
|
|
@ -3150,7 +2796,7 @@ class RenderSetupListObserver:
|
|||
cmds.delete(render_layer_set_name)
|
||||
|
||||
|
||||
class RenderSetupItemObserver():
|
||||
class RenderSetupItemObserver:
|
||||
"""Handle changes in render setup items."""
|
||||
|
||||
def __init__(self, item):
|
||||
|
|
@ -3342,7 +2988,27 @@ def set_colorspace():
|
|||
"""
|
||||
project_name = os.getenv("AVALON_PROJECT")
|
||||
imageio = get_anatomy_settings(project_name)["imageio"]["maya"]
|
||||
root_dict = imageio["colorManagementPreference"]
|
||||
|
||||
# Maya 2022+ introduces new OCIO v2 color management settings that
|
||||
# can override the old color managenement preferences. OpenPype has
|
||||
# separate settings for both so we fall back when necessary.
|
||||
use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"]
|
||||
required_maya_version = 2022
|
||||
maya_version = int(cmds.about(version=True))
|
||||
maya_supports_ocio_v2 = maya_version >= required_maya_version
|
||||
if use_ocio_v2 and not maya_supports_ocio_v2:
|
||||
# Fallback to legacy behavior with a warning
|
||||
log.warning("Color Management Preference v2 is enabled but not "
|
||||
"supported by current Maya version: {} (< {}). Falling "
|
||||
"back to legacy settings.".format(
|
||||
maya_version, required_maya_version)
|
||||
)
|
||||
use_ocio_v2 = False
|
||||
|
||||
if use_ocio_v2:
|
||||
root_dict = imageio["colorManagementPreference_v2"]
|
||||
else:
|
||||
root_dict = imageio["colorManagementPreference"]
|
||||
|
||||
if not isinstance(root_dict, dict):
|
||||
msg = "set_colorspace(): argument should be dictionary"
|
||||
|
|
@ -3350,11 +3016,12 @@ def set_colorspace():
|
|||
|
||||
log.debug(">> root_dict: {}".format(root_dict))
|
||||
|
||||
# first enable color management
|
||||
# enable color management
|
||||
cmds.colorManagementPrefs(e=True, cmEnabled=True)
|
||||
cmds.colorManagementPrefs(e=True, ocioRulesEnabled=True)
|
||||
|
||||
# second set config path
|
||||
# set config path
|
||||
custom_ocio_config = False
|
||||
if root_dict.get("configFilePath"):
|
||||
unresolved_path = root_dict["configFilePath"]
|
||||
ocio_paths = unresolved_path[platform.system().lower()]
|
||||
|
|
@ -3371,22 +3038,56 @@ def set_colorspace():
|
|||
cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=True)
|
||||
log.debug("maya '{}' changed to: {}".format(
|
||||
"configFilePath", resolved_path))
|
||||
root_dict.pop("configFilePath")
|
||||
custom_ocio_config = True
|
||||
else:
|
||||
cmds.colorManagementPrefs(e=True, cmConfigFileEnabled=False)
|
||||
cmds.colorManagementPrefs(e=True, configFilePath="" )
|
||||
cmds.colorManagementPrefs(e=True, configFilePath="")
|
||||
|
||||
# third set rendering space and view transform
|
||||
renderSpace = root_dict["renderSpace"]
|
||||
cmds.colorManagementPrefs(e=True, renderingSpaceName=renderSpace)
|
||||
viewTransform = root_dict["viewTransform"]
|
||||
cmds.colorManagementPrefs(e=True, viewTransformName=viewTransform)
|
||||
# If no custom OCIO config file was set we make sure that Maya 2022+
|
||||
# either chooses between Maya's newer default v2 or legacy config based
|
||||
# on OpenPype setting to use ocio v2 or not.
|
||||
if maya_supports_ocio_v2 and not custom_ocio_config:
|
||||
if use_ocio_v2:
|
||||
# Use Maya 2022+ default OCIO v2 config
|
||||
log.info("Setting default Maya OCIO v2 config")
|
||||
cmds.colorManagementPrefs(edit=True, configFilePath="")
|
||||
else:
|
||||
# Set the Maya default config file path
|
||||
log.info("Setting default Maya OCIO v1 legacy config")
|
||||
cmds.colorManagementPrefs(edit=True, configFilePath="legacy")
|
||||
|
||||
# set color spaces for rendering space and view transforms
|
||||
def _colormanage(**kwargs):
|
||||
"""Wrapper around `cmds.colorManagementPrefs`.
|
||||
|
||||
This logs errors instead of raising an error so color management
|
||||
settings get applied as much as possible.
|
||||
|
||||
"""
|
||||
assert len(kwargs) == 1, "Must receive one keyword argument"
|
||||
try:
|
||||
cmds.colorManagementPrefs(edit=True, **kwargs)
|
||||
log.debug("Setting Color Management Preference: {}".format(kwargs))
|
||||
except RuntimeError as exc:
|
||||
log.error(exc)
|
||||
|
||||
if use_ocio_v2:
|
||||
_colormanage(renderingSpaceName=root_dict["renderSpace"])
|
||||
_colormanage(displayName=root_dict["displayName"])
|
||||
_colormanage(viewName=root_dict["viewName"])
|
||||
else:
|
||||
_colormanage(renderingSpaceName=root_dict["renderSpace"])
|
||||
if maya_supports_ocio_v2:
|
||||
_colormanage(viewName=root_dict["viewTransform"])
|
||||
_colormanage(displayName="legacy")
|
||||
else:
|
||||
_colormanage(viewTransformName=root_dict["viewTransform"])
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def root_parent(nodes):
|
||||
# type: (list) -> list
|
||||
"""Context manager to un-parent provided nodes and return then back."""
|
||||
"""Context manager to un-parent provided nodes and return them back."""
|
||||
import pymel.core as pm # noqa
|
||||
|
||||
node_parents = []
|
||||
|
|
|
|||
|
|
@ -1,924 +0,0 @@
|
|||
[
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\others\\save_scene_incremental.py",
|
||||
"sourcetype": "file",
|
||||
"title": "# Version Up",
|
||||
"tooltip": "Incremental save with a specific format"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\others\\open_current_folder.py",
|
||||
"sourcetype": "file",
|
||||
"title": "Open working folder..",
|
||||
"tooltip": "Show current scene in Explorer"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\avalon\\launch_manager.py",
|
||||
"sourcetype": "file",
|
||||
"title": "# Project Manager",
|
||||
"tooltip": "Add assets to the project"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "from openpype.tools.assetcreator import app as assetcreator; assetcreator.show(context='maya')",
|
||||
"sourcetype": "python",
|
||||
"title": "Asset Creator",
|
||||
"tooltip": "Open the Asset Creator"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Modeling",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "import easyTreezSource; reload(easyTreezSource); easyTreezSource.easyTreez()",
|
||||
"sourcetype": "python",
|
||||
"tags": ["modeling", "trees", "generate", "create", "plants"],
|
||||
"title": "EasyTreez",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\modeling\\separateMeshPerShader.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["modeling", "separateMeshPerShader"],
|
||||
"title": "# Separate Mesh Per Shader",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\modeling\\polyDetachSeparate.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["modeling", "poly", "detach", "separate"],
|
||||
"title": "# Polygon Detach and Separate",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["modeling", "select", "nth", "edge", "ui"],
|
||||
"title": "# Select Every Nth Edge"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\modeling\\djPFXUVs.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["modeling", "djPFX", "UVs"],
|
||||
"title": "# dj PFX UVs",
|
||||
"tooltip": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Rigging",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\rigging\\advancedSkeleton.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"rigging",
|
||||
"autorigger",
|
||||
"advanced",
|
||||
"skeleton",
|
||||
"advancedskeleton",
|
||||
"file"
|
||||
],
|
||||
"title": "Advanced Skeleton"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Shading",
|
||||
"items": [
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# VRay",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Import Proxies",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayImportProxies.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "vray", "import", "proxies"],
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Select All GES",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGES.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "select All GES"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Select All GES Under Selection",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "select", "all", "GES"]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Selection To VRay Mesh",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "selection", "vraymesh"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add VRay Round Edges Attribute",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "round edges", "attribute"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add Gamma",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayAddGamma.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "add gamma"]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py",
|
||||
"sourcetype": "file",
|
||||
"title": "# Select Unconnected Shader Materials",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"select",
|
||||
"vraymesh",
|
||||
"materials",
|
||||
"unconnected shader slots"
|
||||
],
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py",
|
||||
"sourcetype": "file",
|
||||
"title": "# Merge Similar VRay Mesh Materials",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"Merge",
|
||||
"VRayMesh",
|
||||
"Materials"
|
||||
],
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Create Two Sided Material",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "Creates two sided material for selected material and renames it",
|
||||
"tags": ["shading", "vray", "two sided", "material"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Create Two Sided Material For Selected",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "Select material to create a two sided version from it",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"Create2SidedMtlForSelectedMtl.py"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add OpenSubdiv Attribute",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"add",
|
||||
"open subdiv",
|
||||
"attribute"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Remove OpenSubdiv Attribute",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"remove",
|
||||
"opensubdiv",
|
||||
"attributee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add Subdivision Attribute",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"addVraySubdivisionAttribute"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Remove Subdivision Attribute.py",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": [
|
||||
"shading",
|
||||
"vray",
|
||||
"remove",
|
||||
"subdivision",
|
||||
"attribute"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add Vray Object Ids",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayObjectIds.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "add", "object id"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Add Vray Material Ids",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "addVrayMaterialIds.py"]
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Set Physical DOF Depth",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "physical", "DOF ", "Depth"]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Magic Vray Proxy UI",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "magicVrayProxyUI"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"shading",
|
||||
"lookdev",
|
||||
"assign",
|
||||
"shaders",
|
||||
"prefix",
|
||||
"filename",
|
||||
"render"
|
||||
],
|
||||
"title": "# Set filename prefix",
|
||||
"tooltip": "Set the render file name prefix."
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "import mayalookassigner; mayalookassigner.show()",
|
||||
"sourcetype": "python",
|
||||
"tags": ["shading", "look", "assign", "shaders", "auto"],
|
||||
"title": "Look Manager",
|
||||
"tooltip": "Open the Look Manager UI for look assignment"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\LightLinkUi.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "light", "link", "ui"],
|
||||
"title": "# Light Link UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\vdviewer_ui.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"shading",
|
||||
"look",
|
||||
"vray",
|
||||
"displacement",
|
||||
"shaders",
|
||||
"auto"
|
||||
],
|
||||
"title": "# VRay Displ Viewer",
|
||||
"tooltip": "Open the VRay Displacement Viewer, select and control the content of the set"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "CLRImage", "textures", "preview"],
|
||||
"title": "# Set Texture Preview To CLRImage",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "fix", "DefaultShaderSet", "Behavior"],
|
||||
"title": "# Fix Default Shader Set Behavior",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"shading",
|
||||
"fix",
|
||||
"Selected",
|
||||
"Shapes",
|
||||
"Reference",
|
||||
"Assignments"
|
||||
],
|
||||
"title": "# Fix Shapes Reference Assignments",
|
||||
"tooltip": "Select shapes to fix the reference assignments"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\selectLambert1Members.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "selectLambert1Members"],
|
||||
"title": "# Select Lambert1 Members",
|
||||
"tooltip": "Selects all objects which have the Lambert1 shader assigned"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\selectShapesWithoutShader.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "selectShapesWithoutShader"],
|
||||
"title": "# Select Shapes Without Shader",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["shading", "fixRenderLayerOutAdjustmentErrors"],
|
||||
"title": "# Fix RenderLayer Out Adjustment Errors",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"shading",
|
||||
"renderlayer",
|
||||
"missing",
|
||||
"reference",
|
||||
"switch",
|
||||
"layer"
|
||||
],
|
||||
"title": "# Fix RenderLayer Missing Referenced Nodes Overrides",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"title": "# Image 2 Tiled EXR",
|
||||
"command": "$OPENPYPE_SCRIPTS\\shading\\open_img2exr.py",
|
||||
"sourcetype": "file",
|
||||
"tooltip": "",
|
||||
"tags": ["shading", "vray", "exr"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# Rendering",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\pyblish\\open_deadline_submission_settings.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["settings", "deadline", "globals", "render"],
|
||||
"title": "# DL Submission Settings UI",
|
||||
"tooltip": "Open the Deadline Submission Settings UI"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Animation",
|
||||
"items": [
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# Attributes",
|
||||
"tooltip": "",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyValues.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["animation", "copy", "attributes"],
|
||||
"title": "# Copy Values",
|
||||
"tooltip": "Copy attribute values"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyInConnections.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"connections",
|
||||
"incoming"
|
||||
],
|
||||
"title": "# Copy In Connections",
|
||||
"tooltip": "Copy incoming connections"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyOutConnections.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"connections",
|
||||
"out"
|
||||
],
|
||||
"title": "# Copy Out Connections",
|
||||
"tooltip": "Copy outcoming connections"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformLocal.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"transforms",
|
||||
"local"
|
||||
],
|
||||
"title": "# Copy Local Transforms",
|
||||
"tooltip": "Copy local transforms"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"transforms",
|
||||
"matrix"
|
||||
],
|
||||
"title": "# Copy Matrix Transforms",
|
||||
"tooltip": "Copy Matrix transforms"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\copyTransformUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"transforms",
|
||||
"UI"
|
||||
],
|
||||
"title": "# Copy Transforms UI",
|
||||
"tooltip": "Open the Copy Transforms UI"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\attributes\\simpleCopyUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"animation",
|
||||
"copy",
|
||||
"attributes",
|
||||
"transforms",
|
||||
"UI",
|
||||
"simple"
|
||||
],
|
||||
"title": "# Simple Copy UI",
|
||||
"tooltip": "Open the simple Copy Transforms UI"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# Optimize",
|
||||
"tooltip": "Optimization scripts",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["animation", "hierarchy", "toggle", "freeze"],
|
||||
"title": "# Toggle Freeze Hierarchy",
|
||||
"tooltip": "Freeze and unfreeze hierarchy"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["animation", "nucleus", "toggle", "parallel"],
|
||||
"title": "# Toggle Parallel Nucleus",
|
||||
"tooltip": "Toggle parallel nucleus"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py",
|
||||
"tags": ["animation", "bake", "selection", "worldspace.py"],
|
||||
"title": "# Bake Selected To Worldspace",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\timeStepper.py",
|
||||
"tags": ["animation", "time", "stepper"],
|
||||
"title": "# Time Stepper",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\capture_ui.py",
|
||||
"tags": [
|
||||
"animation",
|
||||
"capture",
|
||||
"ui",
|
||||
"screen",
|
||||
"movie",
|
||||
"image"
|
||||
],
|
||||
"title": "# Capture UI",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\simplePlayblastUI.py",
|
||||
"tags": ["animation", "simple", "playblast", "ui"],
|
||||
"title": "# Simple Playblast UI",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\tweenMachineUI.py",
|
||||
"tags": ["animation", "tween", "machine"],
|
||||
"title": "# Tween Machine UI",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\selectAllAnimationCurves.py",
|
||||
"tags": ["animation", "select", "curves"],
|
||||
"title": "# Select All Animation Curves",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\pathAnimation.py",
|
||||
"tags": ["animation", "path", "along"],
|
||||
"title": "# Path Animation",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\offsetSelectedObjectsUI.py",
|
||||
"tags": ["animation", "offsetSelectedObjectsUI.py"],
|
||||
"title": "# Offset Selected Objects UI",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\key_amplifier_ui.py",
|
||||
"tags": ["animation", "key", "amplifier"],
|
||||
"title": "# Key Amplifier UI",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\anim_scene_optimizer.py",
|
||||
"tags": ["animation", "anim_scene_optimizer.py"],
|
||||
"title": "# Anim_Scene_Optimizer",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\zvParentMaster.py",
|
||||
"tags": ["animation", "zvParentMaster.py"],
|
||||
"title": "# ZV Parent Master",
|
||||
"type": "action"
|
||||
},
|
||||
{
|
||||
"sourcetype": "file",
|
||||
"command": "$OPENPYPE_SCRIPTS\\animation\\animLibrary.py",
|
||||
"tags": ["animation", "studiolibrary.py"],
|
||||
"title": "Anim Library",
|
||||
"type": "action"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# Layout",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\alignDistributeUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "align", "Distribute", "UI"],
|
||||
"title": "# Align Distribute UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\alignSimpleUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "align", "UI", "Simple"],
|
||||
"title": "# Align Simple UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\center_locator.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "center", "locator"],
|
||||
"title": "# Center Locator",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\average_locator.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "average", "locator"],
|
||||
"title": "# Average Locator",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\selectWithinProximityUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "select", "proximity", "ui"],
|
||||
"title": "# Select Within Proximity UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\dupCurveUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "Duplicate", "Curve", "UI"],
|
||||
"title": "# Duplicate Curve UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\randomDeselectUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "random", "Deselect", "UI"],
|
||||
"title": "# Random Deselect UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\multiReferencerUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "multi", "reference"],
|
||||
"title": "# Multi Referencer UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\duplicateOffsetUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "duplicate", "offset", "UI"],
|
||||
"title": "# Duplicate Offset UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\spPaint3d.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "spPaint3d", "paint", "tool"],
|
||||
"title": "# SP Paint 3d",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\randomizeUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "randomize", "UI"],
|
||||
"title": "# Randomize UI",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\layout\\distributeWithinObjectUI.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["layout", "distribute", "ObjectUI", "within"],
|
||||
"title": "# Distribute Within Object UI",
|
||||
"tooltip": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "# Particles",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjects.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["particles", "instancerToObjects"],
|
||||
"title": "# Instancer To Objects",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstances.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["particles", "instancerToObjectsInstances"],
|
||||
"title": "# Instancer To Objects Instances",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py",
|
||||
"sourcetype": "file",
|
||||
"tags": [
|
||||
"particles",
|
||||
"instancerToObjectsInstancesWithAnimation"
|
||||
],
|
||||
"title": "# Instancer To Objects Instances With Animation",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["particles", "instancerToObjectsWithAnimation"],
|
||||
"title": "# Instancer To Objects With Animation",
|
||||
"tooltip": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Cleanup",
|
||||
"items": [
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\repair_faulty_containers.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "repair", "containers"],
|
||||
"title": "# Find and Repair Containers",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\removeNamespaces.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "remove", "namespaces"],
|
||||
"title": "# Remove Namespaces",
|
||||
"tooltip": "Remove all namespaces"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_user_defined_attributes.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "remove_user_defined_attributes"],
|
||||
"title": "# Remove User Defined Attributes",
|
||||
"tooltip": "Remove all user-defined attributes from all nodes"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnknownNodes.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "removeUnknownNodes"],
|
||||
"title": "# Remove Unknown Nodes",
|
||||
"tooltip": "Remove all unknown nodes"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\removeUnloadedReferences.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "removeUnloadedReferences"],
|
||||
"title": "# Remove Unloaded References",
|
||||
"tooltip": "Remove all unloaded references"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "removeReferencesFailedEdits"],
|
||||
"title": "# Remove References Failed Edits",
|
||||
"tooltip": "Remove failed edits for all references"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\remove_unused_looks.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "removeUnusedLooks"],
|
||||
"title": "# Remove Unused Looks",
|
||||
"tooltip": "Remove all loaded yet unused Avalon look containers"
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\uniqifyNodeNames.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "uniqifyNodeNames"],
|
||||
"title": "# Uniqify Node Names",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\autoRenameFileNodes.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "auto", "rename", "filenodes"],
|
||||
"title": "# Auto Rename File Nodes",
|
||||
"tooltip": ""
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\update_asset_id.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "update", "database", "asset", "id"],
|
||||
"title": "# Update Asset ID",
|
||||
"tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\ccRenameReplace.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "rename", "ui"],
|
||||
"title": "Renamer",
|
||||
"tooltip": "Rename UI"
|
||||
},
|
||||
{
|
||||
"type": "action",
|
||||
"command": "$OPENPYPE_SCRIPTS\\cleanup\\renameShapesToTransform.py",
|
||||
"sourcetype": "file",
|
||||
"tags": ["cleanup", "renameShapesToTransform"],
|
||||
"title": "# Rename Shapes To Transform",
|
||||
"tooltip": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -184,76 +184,6 @@ def uninstall():
|
|||
menu.uninstall()
|
||||
|
||||
|
||||
def lock():
|
||||
"""Lock scene
|
||||
|
||||
Add an invisible node to your Maya scene with the name of the
|
||||
current file, indicating that this file is "locked" and cannot
|
||||
be modified any further.
|
||||
|
||||
"""
|
||||
|
||||
if not cmds.objExists("lock"):
|
||||
with lib.maintained_selection():
|
||||
cmds.createNode("objectSet", name="lock")
|
||||
cmds.addAttr("lock", ln="basename", dataType="string")
|
||||
|
||||
# Permanently hide from outliner
|
||||
cmds.setAttr("lock.verticesOnlySet", True)
|
||||
|
||||
fname = cmds.file(query=True, sceneName=True)
|
||||
basename = os.path.basename(fname)
|
||||
cmds.setAttr("lock.basename", basename, type="string")
|
||||
|
||||
|
||||
def unlock():
|
||||
"""Permanently unlock a locked scene
|
||||
|
||||
Doesn't throw an error if scene is already unlocked.
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
cmds.delete("lock")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def is_locked():
|
||||
"""Query whether current scene is locked"""
|
||||
fname = cmds.file(query=True, sceneName=True)
|
||||
basename = os.path.basename(fname)
|
||||
|
||||
if self._ignore_lock:
|
||||
return False
|
||||
|
||||
try:
|
||||
return cmds.getAttr("lock.basename") == basename
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def lock_ignored():
|
||||
"""Context manager for temporarily ignoring the lock of a scene
|
||||
|
||||
The purpose of this function is to enable locking a scene and
|
||||
saving it with the lock still in place.
|
||||
|
||||
Example:
|
||||
>>> with lock_ignored():
|
||||
... pass # Do things without lock
|
||||
|
||||
"""
|
||||
|
||||
self._ignore_lock = True
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self._ignore_lock = False
|
||||
|
||||
|
||||
def parse_container(container):
|
||||
"""Return the container node's full container data.
|
||||
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ class CreateRender(plugin.Creator):
|
|||
# get pools
|
||||
pool_names = []
|
||||
|
||||
self.server_aliases = self.deadline_servers.keys()
|
||||
self.server_aliases = list(self.deadline_servers.keys())
|
||||
self.data["deadlineServers"] = self.server_aliases
|
||||
self.data["suspendPublishJob"] = False
|
||||
self.data["review"] = True
|
||||
|
|
@ -286,15 +286,12 @@ class CreateRender(plugin.Creator):
|
|||
raise RuntimeError("Both Deadline and Muster are enabled")
|
||||
|
||||
if deadline_enabled:
|
||||
# if default server is not between selected, use first one for
|
||||
# initial list of pools.
|
||||
try:
|
||||
deadline_url = self.deadline_servers["default"]
|
||||
except KeyError:
|
||||
deadline_url = [
|
||||
self.deadline_servers[k]
|
||||
for k in self.deadline_servers.keys()
|
||||
][0]
|
||||
# if 'default' server is not between selected,
|
||||
# use first one for initial list of pools.
|
||||
deadline_url = next(iter(self.deadline_servers.values()))
|
||||
|
||||
pool_names = self._get_deadline_pools(deadline_url)
|
||||
|
||||
|
|
|
|||
|
|
@ -72,9 +72,8 @@ class SetFrameRangeWithHandlesLoader(api.Loader):
|
|||
return
|
||||
|
||||
# Include handles
|
||||
handles = version_data.get("handles", 0)
|
||||
start -= handles
|
||||
end += handles
|
||||
start -= version_data.get("handleStart", 0)
|
||||
end += version_data.get("handleEnd", 0)
|
||||
|
||||
cmds.playbackOptions(minTime=start,
|
||||
maxTime=end,
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
|
||||
# Collect file nodes used by shading engines (if we have any)
|
||||
files = []
|
||||
look_sets = sets.keys()
|
||||
look_sets = list(sets.keys())
|
||||
shader_attrs = [
|
||||
"surfaceShader",
|
||||
"volumeShader",
|
||||
|
|
|
|||
|
|
@ -234,13 +234,14 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
publish_meta_path = None
|
||||
for aov in exp_files:
|
||||
full_paths = []
|
||||
for file in aov[aov.keys()[0]]:
|
||||
aov_first_key = list(aov.keys())[0]
|
||||
for file in aov[aov_first_key]:
|
||||
full_path = os.path.join(workspace, default_render_file,
|
||||
file)
|
||||
full_path = full_path.replace("\\", "/")
|
||||
full_paths.append(full_path)
|
||||
publish_meta_path = os.path.dirname(full_path)
|
||||
aov_dict[aov.keys()[0]] = full_paths
|
||||
aov_dict[aov_first_key] = full_paths
|
||||
|
||||
frame_start_render = int(self.get_render_attribute(
|
||||
"startFrame", layer=layer_name))
|
||||
|
|
|
|||
|
|
@ -38,12 +38,8 @@ class ExtractAnimation(openpype.api.Extractor):
|
|||
fullPath=True) or []
|
||||
|
||||
# Collect the start and end including handles
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"]
|
||||
handles = instance.data.get("handles", 0) or 0
|
||||
if handles:
|
||||
start -= handles
|
||||
end += handles
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
self.log.info("Extracting animation..")
|
||||
dirname = self.staging_dir(instance)
|
||||
|
|
|
|||
|
|
@ -38,13 +38,9 @@ class ExtractAssStandin(openpype.api.Extractor):
|
|||
self.log.info("Extracting ass sequence")
|
||||
|
||||
# Collect the start and end including handles
|
||||
start = instance.data.get("frameStart", 1)
|
||||
end = instance.data.get("frameEnd", 1)
|
||||
handles = instance.data.get("handles", 0)
|
||||
start = instance.data.get("frameStartHandle", 1)
|
||||
end = instance.data.get("frameEndHandle", 1)
|
||||
step = instance.data.get("step", 0)
|
||||
if handles:
|
||||
start -= handles
|
||||
end += handles
|
||||
|
||||
exported_files = cmds.arnoldExportAss(filename=file_path,
|
||||
selected=True,
|
||||
|
|
|
|||
|
|
@ -21,17 +21,9 @@ class ExtractCameraAlembic(openpype.api.Extractor):
|
|||
|
||||
def process(self, instance):
|
||||
|
||||
# get settings
|
||||
framerange = [instance.data.get("frameStart", 1),
|
||||
instance.data.get("frameEnd", 1)]
|
||||
handle_start = instance.data.get("handleStart", 0)
|
||||
handle_end = instance.data.get("handleEnd", 0)
|
||||
|
||||
# TODO: deprecated attribute "handles"
|
||||
|
||||
if handle_start is None:
|
||||
handle_start = instance.data.get("handles", 0)
|
||||
handle_end = instance.data.get("handles", 0)
|
||||
# Collect the start and end including handles
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
step = instance.data.get("step", 1.0)
|
||||
bake_to_worldspace = instance.data("bakeToWorldSpace", True)
|
||||
|
|
@ -61,10 +53,7 @@ class ExtractCameraAlembic(openpype.api.Extractor):
|
|||
|
||||
job_str = ' -selection -dataFormat "ogawa" '
|
||||
job_str += ' -attrPrefix cb'
|
||||
job_str += ' -frameRange {0} {1} '.format(framerange[0]
|
||||
- handle_start,
|
||||
framerange[1]
|
||||
+ handle_end)
|
||||
job_str += ' -frameRange {0} {1} '.format(start, end)
|
||||
job_str += ' -step {0} '.format(step)
|
||||
|
||||
if bake_to_worldspace:
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ def grouper(iterable, n, fillvalue=None):
|
|||
|
||||
"""
|
||||
args = [iter(iterable)] * n
|
||||
return itertools.izip_longest(fillvalue=fillvalue, *args)
|
||||
from six.moves import zip_longest
|
||||
return zip_longest(fillvalue=fillvalue, *args)
|
||||
|
||||
|
||||
def unlock(plug):
|
||||
|
|
@ -117,19 +118,9 @@ class ExtractCameraMayaScene(openpype.api.Extractor):
|
|||
# no preset found
|
||||
pass
|
||||
|
||||
framerange = [instance.data.get("frameStart", 1),
|
||||
instance.data.get("frameEnd", 1)]
|
||||
handle_start = instance.data.get("handleStart", 0)
|
||||
handle_end = instance.data.get("handleEnd", 0)
|
||||
|
||||
# TODO: deprecated attribute "handles"
|
||||
|
||||
if handle_start is None:
|
||||
handle_start = instance.data.get("handles", 0)
|
||||
handle_end = instance.data.get("handles", 0)
|
||||
|
||||
range_with_handles = [framerange[0] - handle_start,
|
||||
framerange[1] + handle_end]
|
||||
# Collect the start and end including handles
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
step = instance.data.get("step", 1.0)
|
||||
bake_to_worldspace = instance.data("bakeToWorldSpace", True)
|
||||
|
|
@ -164,7 +155,7 @@ class ExtractCameraMayaScene(openpype.api.Extractor):
|
|||
"Performing camera bakes: {}".format(transform))
|
||||
baked = lib.bake_to_world_space(
|
||||
transform,
|
||||
frame_range=range_with_handles,
|
||||
frame_range=[start, end],
|
||||
step=step
|
||||
)
|
||||
baked_shapes = cmds.ls(baked,
|
||||
|
|
|
|||
|
|
@ -168,12 +168,8 @@ class ExtractFBX(openpype.api.Extractor):
|
|||
self.log.info("Export options: {0}".format(options))
|
||||
|
||||
# Collect the start and end including handles
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"]
|
||||
handles = instance.data.get("handles", 0)
|
||||
if handles:
|
||||
start -= handles
|
||||
end += handles
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
options['bakeComplexStart'] = start
|
||||
options['bakeComplexEnd'] = end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import os
|
|||
import sys
|
||||
import json
|
||||
import tempfile
|
||||
import platform
|
||||
import contextlib
|
||||
import subprocess
|
||||
from collections import OrderedDict
|
||||
|
|
@ -62,6 +63,11 @@ def maketx(source, destination, *args):
|
|||
from openpype.lib import get_oiio_tools_path
|
||||
|
||||
maketx_path = get_oiio_tools_path("maketx")
|
||||
|
||||
if platform.system().lower() == "windows":
|
||||
# Ensure .exe extension
|
||||
maketx_path += ".exe"
|
||||
|
||||
if not os.path.exists(maketx_path):
|
||||
print(
|
||||
"OIIO tool not found in {}".format(maketx_path))
|
||||
|
|
@ -216,7 +222,7 @@ class ExtractLook(openpype.api.Extractor):
|
|||
self.log.info("Extract sets (%s) ..." % _scene_type)
|
||||
lookdata = instance.data["lookData"]
|
||||
relationships = lookdata["relationships"]
|
||||
sets = relationships.keys()
|
||||
sets = list(relationships.keys())
|
||||
if not sets:
|
||||
self.log.info("No sets found")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -28,14 +28,19 @@ class ExtractVRayProxy(openpype.api.Extractor):
|
|||
if not anim_on:
|
||||
# Remove animation information because it is not required for
|
||||
# non-animated subsets
|
||||
instance.data.pop("frameStart", None)
|
||||
instance.data.pop("frameEnd", None)
|
||||
keys = ["frameStart", "frameEnd",
|
||||
"handleStart", "handleEnd",
|
||||
"frameStartHandle", "frameEndHandle",
|
||||
# Backwards compatibility
|
||||
"handles"]
|
||||
for key in keys:
|
||||
instance.data.pop(key, None)
|
||||
|
||||
start_frame = 1
|
||||
end_frame = 1
|
||||
else:
|
||||
start_frame = instance.data["frameStart"]
|
||||
end_frame = instance.data["frameEnd"]
|
||||
start_frame = instance.data["frameStartHandle"]
|
||||
end_frame = instance.data["frameEndHandle"]
|
||||
|
||||
vertex_colors = instance.data.get("vertexColors", False)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ class ExtractYetiCache(openpype.api.Extractor):
|
|||
data_file = os.path.join(dirname, "yeti.fursettings")
|
||||
|
||||
# Collect information for writing cache
|
||||
start_frame = instance.data.get("frameStart")
|
||||
end_frame = instance.data.get("frameEnd")
|
||||
start_frame = instance.data.get("frameStartHandle")
|
||||
end_frame = instance.data.get("frameEndHandle")
|
||||
preroll = instance.data.get("preroll")
|
||||
if preroll > 0:
|
||||
start_frame -= preroll
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin):
|
|||
Maya API will return a list of values, which need to be properly
|
||||
handled to evaluate properly.
|
||||
"""
|
||||
if isinstance(attr_val, types.BooleanType):
|
||||
if isinstance(attr_val, bool):
|
||||
return attr_val
|
||||
elif isinstance(attr_val, (types.ListType, types.GeneratorType)):
|
||||
elif isinstance(attr_val, (list, types.GeneratorType)):
|
||||
return any(attr_val)
|
||||
else:
|
||||
return bool(attr_val)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import math
|
|||
import maya.api.OpenMaya as om
|
||||
import pymel.core as pm
|
||||
|
||||
from six.moves import xrange
|
||||
|
||||
|
||||
class GetOverlappingUVs(object):
|
||||
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin):
|
|||
bool: cast Maya attribute to Pythons boolean value.
|
||||
|
||||
"""
|
||||
if isinstance(attr_val, types.BooleanType):
|
||||
if isinstance(attr_val, bool):
|
||||
return attr_val
|
||||
elif isinstance(attr_val, (types.ListType, types.GeneratorType)):
|
||||
elif isinstance(attr_val, (list, types.GeneratorType)):
|
||||
return any(attr_val)
|
||||
else:
|
||||
return bool(attr_val)
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ class ProcessLauncher(QtCore.QObject):
|
|||
def start(self):
|
||||
if self._started:
|
||||
return
|
||||
self.log.info("Started launch logic of AfterEffects")
|
||||
self.log.info("Started launch logic of Photoshop")
|
||||
self._started = True
|
||||
self._start_process_timer.start()
|
||||
|
||||
|
|
|
|||
|
|
@ -344,6 +344,28 @@ class PhotoshopServerStub:
|
|||
)
|
||||
)
|
||||
|
||||
def hide_all_others_layers(self, layers):
|
||||
"""hides all layers that are not part of the list or that are not
|
||||
children of this list
|
||||
|
||||
Args:
|
||||
layers (list): list of PSItem - highest hierarchy
|
||||
"""
|
||||
extract_ids = set([ll.id for ll in self.get_layers_in_layers(layers)])
|
||||
|
||||
self.hide_all_others_layers_ids(extract_ids)
|
||||
|
||||
def hide_all_others_layers_ids(self, extract_ids):
|
||||
"""hides all layers that are not part of the list or that are not
|
||||
children of this list
|
||||
|
||||
Args:
|
||||
extract_ids (list): list of integer that should be visible
|
||||
"""
|
||||
for layer in self.get_layers():
|
||||
if layer.visible and layer.id not in extract_ids:
|
||||
self.set_visible(layer.id, False)
|
||||
|
||||
def get_layers_metadata(self):
|
||||
"""Reads layers metadata from Headline from active document in PS.
|
||||
(Headline accessible by File > File Info)
|
||||
|
|
|
|||
|
|
@ -38,10 +38,15 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
def process(self, context):
|
||||
self.log.info("CollectColorCodedInstances")
|
||||
self.log.debug("mapping:: {}".format(self.color_code_mapping))
|
||||
batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
if (os.environ.get("IS_TEST") and
|
||||
(not batch_dir or not os.path.exists(batch_dir))):
|
||||
self.log.debug("Automatic testing, no batch data, skipping")
|
||||
return
|
||||
|
||||
existing_subset_names = self._get_existing_subset_names(context)
|
||||
asset_name, task_name, variant = self._parse_batch()
|
||||
|
||||
asset_name, task_name, variant = self._parse_batch(batch_dir)
|
||||
|
||||
stub = photoshop.stub()
|
||||
layers = stub.get_layers()
|
||||
|
|
@ -125,9 +130,8 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
|
||||
return existing_subset_names
|
||||
|
||||
def _parse_batch(self):
|
||||
def _parse_batch(self, batch_dir):
|
||||
"""Parses asset_name, task_name, variant from batch manifest."""
|
||||
batch_dir = os.environ.get("OPENPYPE_PUBLISH_DATA")
|
||||
task_data = None
|
||||
if batch_dir and os.path.exists(batch_dir):
|
||||
task_data = parse_json(os.path.join(batch_dir,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ class ExtractImage(openpype.api.Extractor):
|
|||
with photoshop.maintained_selection():
|
||||
self.log.info("Extracting %s" % str(list(instance)))
|
||||
with photoshop.maintained_visibility():
|
||||
# Hide all other layers.
|
||||
layer = instance.data.get("layer")
|
||||
ids = set([layer.id])
|
||||
add_ids = instance.data.pop("ids", None)
|
||||
|
|
@ -34,11 +33,7 @@ class ExtractImage(openpype.api.Extractor):
|
|||
ids.update(set(add_ids))
|
||||
extract_ids = set([ll.id for ll in stub.
|
||||
get_layers_in_layers_ids(ids)])
|
||||
|
||||
for layer in stub.get_layers():
|
||||
# limit unnecessary calls to client
|
||||
if layer.visible and layer.id not in extract_ids:
|
||||
stub.set_visible(layer.id, False)
|
||||
stub.hide_all_others_layers_ids(extract_ids)
|
||||
|
||||
file_basename = os.path.splitext(
|
||||
stub.get_active_document_name()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
import openpype.api
|
||||
import openpype.lib
|
||||
|
|
@ -7,7 +8,7 @@ from openpype.hosts.photoshop import api as photoshop
|
|||
|
||||
class ExtractReview(openpype.api.Extractor):
|
||||
"""
|
||||
Produce a flattened image file from all 'image' instances.
|
||||
Produce a flattened or sequence image file from all 'image' instances.
|
||||
|
||||
If no 'image' instance is created, it produces flattened image from
|
||||
all visible layers.
|
||||
|
|
@ -20,54 +21,58 @@ class ExtractReview(openpype.api.Extractor):
|
|||
# Extract Options
|
||||
jpg_options = None
|
||||
mov_options = None
|
||||
make_image_sequence = None
|
||||
|
||||
def process(self, instance):
|
||||
staging_dir = self.staging_dir(instance)
|
||||
self.log.info("Outputting image to {}".format(staging_dir))
|
||||
|
||||
fps = instance.data.get("fps", 25)
|
||||
stub = photoshop.stub()
|
||||
self.output_seq_filename = os.path.splitext(
|
||||
stub.get_active_document_name())[0] + ".%04d.jpg"
|
||||
|
||||
layers = []
|
||||
for image_instance in instance.context:
|
||||
if image_instance.data["family"] != "image":
|
||||
continue
|
||||
layers.append(image_instance.data.get("layer"))
|
||||
layers = self._get_layers_from_image_instances(instance)
|
||||
self.log.info("Layers image instance found: {}".format(layers))
|
||||
|
||||
# Perform extraction
|
||||
output_image = "{}.jpg".format(
|
||||
os.path.splitext(stub.get_active_document_name())[0]
|
||||
)
|
||||
output_image_path = os.path.join(staging_dir, output_image)
|
||||
with photoshop.maintained_visibility():
|
||||
if layers:
|
||||
# Hide all other layers.
|
||||
extract_ids = set([ll.id for ll in stub.
|
||||
get_layers_in_layers(layers)])
|
||||
self.log.debug("extract_ids {}".format(extract_ids))
|
||||
for layer in stub.get_layers():
|
||||
# limit unnecessary calls to client
|
||||
if layer.visible and layer.id not in extract_ids:
|
||||
stub.set_visible(layer.id, False)
|
||||
if self.make_image_sequence and len(layers) > 1:
|
||||
self.log.info("Extract layers to image sequence.")
|
||||
img_list = self._saves_sequences_layers(staging_dir, layers)
|
||||
|
||||
stub.saveAs(output_image_path, 'jpg', True)
|
||||
instance.data["representations"].append({
|
||||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": img_list,
|
||||
"frameStart": 0,
|
||||
"frameEnd": len(img_list),
|
||||
"fps": fps,
|
||||
"stagingDir": staging_dir,
|
||||
"tags": self.jpg_options['tags'],
|
||||
})
|
||||
|
||||
else:
|
||||
self.log.info("Extract layers to flatten image.")
|
||||
img_list = self._saves_flattened_layers(staging_dir, layers)
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": img_list,
|
||||
"stagingDir": staging_dir,
|
||||
"tags": self.jpg_options['tags']
|
||||
})
|
||||
|
||||
ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
|
||||
instance.data["representations"].append({
|
||||
"name": "jpg",
|
||||
"ext": "jpg",
|
||||
"files": output_image,
|
||||
"stagingDir": staging_dir,
|
||||
"tags": self.jpg_options['tags']
|
||||
})
|
||||
instance.data["stagingDir"] = staging_dir
|
||||
|
||||
# Generate thumbnail.
|
||||
thumbnail_path = os.path.join(staging_dir, "thumbnail.jpg")
|
||||
self.log.info(f"Generate thumbnail {thumbnail_path}")
|
||||
args = [
|
||||
ffmpeg_path,
|
||||
"-y",
|
||||
"-i", output_image_path,
|
||||
"-i", os.path.join(staging_dir, self.output_seq_filename),
|
||||
"-vf", "scale=300:-1",
|
||||
"-vframes", "1",
|
||||
thumbnail_path
|
||||
|
|
@ -81,14 +86,17 @@ class ExtractReview(openpype.api.Extractor):
|
|||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail"]
|
||||
})
|
||||
|
||||
# Generate mov.
|
||||
mov_path = os.path.join(staging_dir, "review.mov")
|
||||
self.log.info(f"Generate mov review: {mov_path}")
|
||||
img_number = len(img_list)
|
||||
args = [
|
||||
ffmpeg_path,
|
||||
"-y",
|
||||
"-i", output_image_path,
|
||||
"-i", os.path.join(staging_dir, self.output_seq_filename),
|
||||
"-vf", "pad=ceil(iw/2)*2:ceil(ih/2)*2",
|
||||
"-vframes", "1",
|
||||
"-vframes", str(img_number),
|
||||
mov_path
|
||||
]
|
||||
output = openpype.lib.run_subprocess(args)
|
||||
|
|
@ -99,15 +107,86 @@ class ExtractReview(openpype.api.Extractor):
|
|||
"files": os.path.basename(mov_path),
|
||||
"stagingDir": staging_dir,
|
||||
"frameStart": 1,
|
||||
"frameEnd": 1,
|
||||
"fps": 25,
|
||||
"frameEnd": img_number,
|
||||
"fps": fps,
|
||||
"preview": True,
|
||||
"tags": self.mov_options['tags']
|
||||
})
|
||||
|
||||
# Required for extract_review plugin (L222 onwards).
|
||||
instance.data["frameStart"] = 1
|
||||
instance.data["frameEnd"] = 1
|
||||
instance.data["frameEnd"] = img_number
|
||||
instance.data["fps"] = 25
|
||||
|
||||
self.log.info(f"Extracted {instance} to {staging_dir}")
|
||||
|
||||
def _get_image_path_from_instances(self, instance):
|
||||
img_list = []
|
||||
|
||||
for instance in sorted(instance.context):
|
||||
if instance.data["family"] != "image":
|
||||
continue
|
||||
|
||||
for rep in instance.data["representations"]:
|
||||
img_path = os.path.join(
|
||||
rep["stagingDir"],
|
||||
rep["files"]
|
||||
)
|
||||
img_list.append(img_path)
|
||||
|
||||
return img_list
|
||||
|
||||
def _copy_image_to_staging_dir(self, staging_dir, img_list):
|
||||
copy_files = []
|
||||
for i, img_src in enumerate(img_list):
|
||||
img_filename = self.output_seq_filename % i
|
||||
img_dst = os.path.join(staging_dir, img_filename)
|
||||
|
||||
self.log.debug(
|
||||
"Copying file .. {} -> {}".format(img_src, img_dst)
|
||||
)
|
||||
shutil.copy(img_src, img_dst)
|
||||
copy_files.append(img_filename)
|
||||
|
||||
return copy_files
|
||||
|
||||
def _get_layers_from_image_instances(self, instance):
|
||||
layers = []
|
||||
for image_instance in instance.context:
|
||||
if image_instance.data["family"] != "image":
|
||||
continue
|
||||
layers.append(image_instance.data.get("layer"))
|
||||
|
||||
return sorted(layers)
|
||||
|
||||
def _saves_flattened_layers(self, staging_dir, layers):
|
||||
img_filename = self.output_seq_filename % 0
|
||||
output_image_path = os.path.join(staging_dir, img_filename)
|
||||
stub = photoshop.stub()
|
||||
|
||||
with photoshop.maintained_visibility():
|
||||
self.log.info("Extracting {}".format(layers))
|
||||
if layers:
|
||||
stub.hide_all_others_layers(layers)
|
||||
|
||||
stub.saveAs(output_image_path, 'jpg', True)
|
||||
|
||||
return img_filename
|
||||
|
||||
def _saves_sequences_layers(self, staging_dir, layers):
|
||||
stub = photoshop.stub()
|
||||
|
||||
list_img_filename = []
|
||||
with photoshop.maintained_visibility():
|
||||
for i, layer in enumerate(layers):
|
||||
self.log.info("Extracting {}".format(layer))
|
||||
|
||||
img_filename = self.output_seq_filename % i
|
||||
output_image_path = os.path.join(staging_dir, img_filename)
|
||||
list_img_filename.append(img_filename)
|
||||
|
||||
with photoshop.maintained_visibility():
|
||||
stub.hide_all_others_layers([layer])
|
||||
stub.saveAs(output_image_path, 'jpg', True)
|
||||
|
||||
return list_img_filename
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ def get_resolve_module():
|
|||
sys.exit()
|
||||
# assign global var and return
|
||||
bmdvr = bmd.scriptapp("Resolve")
|
||||
# bmdvf = bmd.scriptapp("Fusion")
|
||||
bmdvf = bmd.scriptapp("Fusion")
|
||||
resolve.api.bmdvr = bmdvr
|
||||
resolve.api.bmdvf = bmdvr.Fusion()
|
||||
resolve.api.bmdvf = bmdvf
|
||||
log.info(("Assigning resolve module to "
|
||||
f"`pype.hosts.resolve.api.bmdvr`: {resolve.api.bmdvr}"))
|
||||
log.info(("Assigning resolve module to "
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"asset": "sq01_sh0010",
|
||||
"task": "Compositing",
|
||||
"variant": "myVariant",
|
||||
"uuid": "a485f148-9121-46a5-8157-aa64df0fb449",
|
||||
"instance_id": "a485f148-9121-46a5-8157-aa64df0fb449",
|
||||
"creator_attributes": {
|
||||
"number_key": 10,
|
||||
"ha": 10
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
"asset": "sq01_sh0010",
|
||||
"task": "Compositing",
|
||||
"variant": "myVariant2",
|
||||
"uuid": "a485f148-9121-46a5-8157-aa64df0fb444",
|
||||
"creator_attributes": {},
|
||||
"instance_id": "a485f148-9121-46a5-8157-aa64df0fb444",
|
||||
"publish_attributes": {
|
||||
"CollectFtrackApi": {
|
||||
"add_ftrack_family": true
|
||||
|
|
@ -47,8 +47,8 @@
|
|||
"asset": "sq01_sh0010",
|
||||
"task": "Compositing",
|
||||
"variant": "Main",
|
||||
"uuid": "3607bc95-75f6-4648-a58d-e699f413d09f",
|
||||
"creator_attributes": {},
|
||||
"instance_id": "3607bc95-75f6-4648-a58d-e699f413d09f",
|
||||
"publish_attributes": {
|
||||
"CollectFtrackApi": {
|
||||
"add_ftrack_family": true
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
"asset": "sq01_sh0020",
|
||||
"task": "Compositing",
|
||||
"variant": "Main2",
|
||||
"uuid": "4ccf56f6-9982-4837-967c-a49695dbe8eb",
|
||||
"instance_id": "4ccf56f6-9982-4837-967c-a49695dbe8eb",
|
||||
"creator_attributes": {},
|
||||
"publish_attributes": {
|
||||
"CollectFtrackApi": {
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"asset": "sq01_sh0020",
|
||||
"task": "Compositing",
|
||||
"variant": "Main2",
|
||||
"uuid": "4ccf56f6-9982-4837-967c-a49695dbe8ec",
|
||||
"instance_id": "4ccf56f6-9982-4837-967c-a49695dbe8ec",
|
||||
"creator_attributes": {},
|
||||
"publish_attributes": {
|
||||
"CollectFtrackApi": {
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"asset": "Alpaca_01",
|
||||
"task": "modeling",
|
||||
"variant": "Main",
|
||||
"uuid": "7c9ddfc7-9f9c-4c1c-b233-38c966735fb6",
|
||||
"instance_id": "7c9ddfc7-9f9c-4c1c-b233-38c966735fb6",
|
||||
"creator_attributes": {},
|
||||
"publish_attributes": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ def update_instances(update_list):
|
|||
|
||||
instances = HostContext.get_instances()
|
||||
for instance_data in instances:
|
||||
instance_id = instance_data["uuid"]
|
||||
instance_id = instance_data["instance_id"]
|
||||
if instance_id in updated_instances:
|
||||
new_instance_data = updated_instances[instance_id]
|
||||
old_keys = set(instance_data.keys())
|
||||
|
|
@ -132,10 +132,10 @@ def remove_instances(instances):
|
|||
|
||||
current_instances = HostContext.get_instances()
|
||||
for instance in instances:
|
||||
instance_id = instance.data["uuid"]
|
||||
instance_id = instance.data["instance_id"]
|
||||
found_idx = None
|
||||
for idx, _instance in enumerate(current_instances):
|
||||
if instance_id == _instance["uuid"]:
|
||||
if instance_id == _instance["instance_id"]:
|
||||
found_idx = idx
|
||||
break
|
||||
|
||||
|
|
|
|||
20
openpype/hosts/traypublisher/api/__init__.py
Normal file
20
openpype/hosts/traypublisher/api/__init__.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from .pipeline import (
|
||||
install,
|
||||
ls,
|
||||
|
||||
set_project_name,
|
||||
get_context_title,
|
||||
get_context_data,
|
||||
update_context_data,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"install",
|
||||
"ls",
|
||||
|
||||
"set_project_name",
|
||||
"get_context_title",
|
||||
"get_context_data",
|
||||
"update_context_data",
|
||||
)
|
||||
180
openpype/hosts/traypublisher/api/pipeline.py
Normal file
180
openpype/hosts/traypublisher/api/pipeline.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import os
|
||||
import json
|
||||
import tempfile
|
||||
import atexit
|
||||
|
||||
from avalon import io
|
||||
import avalon.api
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import BaseCreator
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(
|
||||
os.path.abspath(__file__)
|
||||
))
|
||||
PUBLISH_PATH = os.path.join(ROOT_DIR, "plugins", "publish")
|
||||
CREATE_PATH = os.path.join(ROOT_DIR, "plugins", "create")
|
||||
|
||||
|
||||
class HostContext:
|
||||
_context_json_path = None
|
||||
|
||||
@staticmethod
|
||||
def _on_exit():
|
||||
if (
|
||||
HostContext._context_json_path
|
||||
and os.path.exists(HostContext._context_json_path)
|
||||
):
|
||||
os.remove(HostContext._context_json_path)
|
||||
|
||||
@classmethod
|
||||
def get_context_json_path(cls):
|
||||
if cls._context_json_path is None:
|
||||
output_file = tempfile.NamedTemporaryFile(
|
||||
mode="w", prefix="traypub_", suffix=".json"
|
||||
)
|
||||
output_file.close()
|
||||
cls._context_json_path = output_file.name
|
||||
atexit.register(HostContext._on_exit)
|
||||
print(cls._context_json_path)
|
||||
return cls._context_json_path
|
||||
|
||||
@classmethod
|
||||
def _get_data(cls, group=None):
|
||||
json_path = cls.get_context_json_path()
|
||||
data = {}
|
||||
if not os.path.exists(json_path):
|
||||
with open(json_path, "w") as json_stream:
|
||||
json.dump(data, json_stream)
|
||||
else:
|
||||
with open(json_path, "r") as json_stream:
|
||||
content = json_stream.read()
|
||||
if content:
|
||||
data = json.loads(content)
|
||||
if group is None:
|
||||
return data
|
||||
return data.get(group)
|
||||
|
||||
@classmethod
|
||||
def _save_data(cls, group, new_data):
|
||||
json_path = cls.get_context_json_path()
|
||||
data = cls._get_data()
|
||||
data[group] = new_data
|
||||
with open(json_path, "w") as json_stream:
|
||||
json.dump(data, json_stream)
|
||||
|
||||
@classmethod
|
||||
def add_instance(cls, instance):
|
||||
instances = cls.get_instances()
|
||||
instances.append(instance)
|
||||
cls.save_instances(instances)
|
||||
|
||||
@classmethod
|
||||
def get_instances(cls):
|
||||
return cls._get_data("instances") or []
|
||||
|
||||
@classmethod
|
||||
def save_instances(cls, instances):
|
||||
cls._save_data("instances", instances)
|
||||
|
||||
@classmethod
|
||||
def get_context_data(cls):
|
||||
return cls._get_data("context") or {}
|
||||
|
||||
@classmethod
|
||||
def save_context_data(cls, data):
|
||||
cls._save_data("context", data)
|
||||
|
||||
@classmethod
|
||||
def get_project_name(cls):
|
||||
return cls._get_data("project_name")
|
||||
|
||||
@classmethod
|
||||
def set_project_name(cls, project_name):
|
||||
cls._save_data("project_name", project_name)
|
||||
|
||||
@classmethod
|
||||
def get_data_to_store(cls):
|
||||
return {
|
||||
"project_name": cls.get_project_name(),
|
||||
"instances": cls.get_instances(),
|
||||
"context": cls.get_context_data(),
|
||||
}
|
||||
|
||||
|
||||
def list_instances():
|
||||
return HostContext.get_instances()
|
||||
|
||||
|
||||
def update_instances(update_list):
|
||||
updated_instances = {}
|
||||
for instance, _changes in update_list:
|
||||
updated_instances[instance.id] = instance.data_to_store()
|
||||
|
||||
instances = HostContext.get_instances()
|
||||
for instance_data in instances:
|
||||
instance_id = instance_data["instance_id"]
|
||||
if instance_id in updated_instances:
|
||||
new_instance_data = updated_instances[instance_id]
|
||||
old_keys = set(instance_data.keys())
|
||||
new_keys = set(new_instance_data.keys())
|
||||
instance_data.update(new_instance_data)
|
||||
for key in (old_keys - new_keys):
|
||||
instance_data.pop(key)
|
||||
|
||||
HostContext.save_instances(instances)
|
||||
|
||||
|
||||
def remove_instances(instances):
|
||||
if not isinstance(instances, (tuple, list)):
|
||||
instances = [instances]
|
||||
|
||||
current_instances = HostContext.get_instances()
|
||||
for instance in instances:
|
||||
instance_id = instance.data["instance_id"]
|
||||
found_idx = None
|
||||
for idx, _instance in enumerate(current_instances):
|
||||
if instance_id == _instance["instance_id"]:
|
||||
found_idx = idx
|
||||
break
|
||||
|
||||
if found_idx is not None:
|
||||
current_instances.pop(found_idx)
|
||||
HostContext.save_instances(current_instances)
|
||||
|
||||
|
||||
def get_context_data():
|
||||
return HostContext.get_context_data()
|
||||
|
||||
|
||||
def update_context_data(data, changes):
|
||||
HostContext.save_context_data(data)
|
||||
|
||||
|
||||
def get_context_title():
|
||||
return HostContext.get_project_name()
|
||||
|
||||
|
||||
def ls():
|
||||
"""Probably will never return loaded containers."""
|
||||
return []
|
||||
|
||||
|
||||
def install():
|
||||
"""This is called before a project is known.
|
||||
|
||||
Project is defined with 'set_project_name'.
|
||||
"""
|
||||
os.environ["AVALON_APP"] = "traypublisher"
|
||||
|
||||
pyblish.api.register_host("traypublisher")
|
||||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.api.register_plugin_path(BaseCreator, CREATE_PATH)
|
||||
|
||||
|
||||
def set_project_name(project_name):
|
||||
# TODO Deregister project specific plugins and register new project plugins
|
||||
os.environ["AVALON_PROJECT"] = project_name
|
||||
avalon.api.Session["AVALON_PROJECT"] = project_name
|
||||
io.install()
|
||||
HostContext.set_project_name(project_name)
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
from openpype.hosts.traypublisher.api import pipeline
|
||||
from openpype.pipeline import (
|
||||
Creator,
|
||||
CreatedInstance,
|
||||
lib
|
||||
)
|
||||
|
||||
|
||||
class WorkfileCreator(Creator):
|
||||
identifier = "workfile"
|
||||
label = "Workfile"
|
||||
family = "workfile"
|
||||
description = "Publish backup of workfile"
|
||||
|
||||
create_allow_context_change = True
|
||||
|
||||
extensions = [
|
||||
# Maya
|
||||
".ma", ".mb",
|
||||
# Nuke
|
||||
".nk",
|
||||
# Hiero
|
||||
".hrox",
|
||||
# Houdini
|
||||
".hip", ".hiplc", ".hipnc",
|
||||
# Blender
|
||||
".blend",
|
||||
# Celaction
|
||||
".scn",
|
||||
# TVPaint
|
||||
".tvpp",
|
||||
# Fusion
|
||||
".comp",
|
||||
# Harmony
|
||||
".zip",
|
||||
# Premiere
|
||||
".prproj",
|
||||
# Resolve
|
||||
".drp",
|
||||
# Photoshop
|
||||
".psd", ".psb",
|
||||
# Aftereffects
|
||||
".aep"
|
||||
]
|
||||
|
||||
def get_icon(self):
|
||||
return "fa.file"
|
||||
|
||||
def collect_instances(self):
|
||||
for instance_data in pipeline.list_instances():
|
||||
creator_id = instance_data.get("creator_identifier")
|
||||
if creator_id == self.identifier:
|
||||
instance = CreatedInstance.from_existing(
|
||||
instance_data, self
|
||||
)
|
||||
self._add_instance_to_context(instance)
|
||||
|
||||
def update_instances(self, update_list):
|
||||
pipeline.update_instances(update_list)
|
||||
|
||||
def remove_instances(self, instances):
|
||||
pipeline.remove_instances(instances)
|
||||
for instance in instances:
|
||||
self._remove_instance_from_context(instance)
|
||||
|
||||
def create(self, subset_name, data, pre_create_data):
|
||||
# Pass precreate data to creator attributes
|
||||
data["creator_attributes"] = pre_create_data
|
||||
# Create new instance
|
||||
new_instance = CreatedInstance(self.family, subset_name, data, self)
|
||||
# Host implementation of storing metadata about instance
|
||||
pipeline.HostContext.add_instance(new_instance.data_to_store())
|
||||
# Add instance to current context
|
||||
self._add_instance_to_context(new_instance)
|
||||
|
||||
def get_default_variants(self):
|
||||
return [
|
||||
"Main"
|
||||
]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
output = [
|
||||
lib.FileDef(
|
||||
"filepath",
|
||||
folders=False,
|
||||
extensions=self.extensions,
|
||||
label="Filepath"
|
||||
)
|
||||
]
|
||||
return output
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Use same attributes as for instance attrobites
|
||||
return self.get_instance_attr_defs()
|
||||
|
||||
def get_detail_description(self):
|
||||
return """# Publish workfile backup"""
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectSource(pyblish.api.ContextPlugin):
|
||||
"""Collecting instances from traypublisher host."""
|
||||
|
||||
label = "Collect source"
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, context):
|
||||
# get json paths from os and load them
|
||||
source_name = "traypublisher"
|
||||
for instance in context:
|
||||
source = instance.data.get("source")
|
||||
if not source:
|
||||
instance.data["source"] = source_name
|
||||
self.log.info((
|
||||
"Source of instance \"{}\" is changed to \"{}\""
|
||||
).format(instance.data["name"], source_name))
|
||||
else:
|
||||
self.log.info((
|
||||
"Source of instance \"{}\" was already set to \"{}\""
|
||||
).format(instance.data["name"], source))
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectWorkfile(pyblish.api.InstancePlugin):
|
||||
"""Collect representation of workfile instances."""
|
||||
|
||||
label = "Collect Workfile"
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
families = ["workfile"]
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
repres = instance.data["representations"]
|
||||
|
||||
creator_attributes = instance.data["creator_attributes"]
|
||||
filepath = creator_attributes["filepath"]
|
||||
instance.data["sourceFilepath"] = filepath
|
||||
|
||||
staging_dir = os.path.dirname(filepath)
|
||||
filename = os.path.basename(filepath)
|
||||
ext = os.path.splitext(filename)[-1]
|
||||
|
||||
repres.append({
|
||||
"ext": ext,
|
||||
"name": ext,
|
||||
"stagingDir": staging_dir,
|
||||
"files": filename
|
||||
})
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from openpype.pipeline import PublishValidationError
|
||||
|
||||
|
||||
class ValidateWorkfilePath(pyblish.api.InstancePlugin):
|
||||
"""Validate existence of workfile instance existence."""
|
||||
|
||||
label = "Collect Workfile"
|
||||
order = pyblish.api.ValidatorOrder - 0.49
|
||||
families = ["workfile"]
|
||||
hosts = ["traypublisher"]
|
||||
|
||||
def process(self, instance):
|
||||
filepath = instance.data["sourceFilepath"]
|
||||
if not filepath:
|
||||
raise PublishValidationError((
|
||||
"Filepath of 'workfile' instance \"{}\" is not set"
|
||||
).format(instance.data["name"]))
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
raise PublishValidationError((
|
||||
"Filepath of 'workfile' instance \"{}\" does not exist: {}"
|
||||
).format(instance.data["name"], filepath))
|
||||
|
|
@ -29,6 +29,7 @@ from .execute import (
|
|||
get_linux_launcher_args,
|
||||
execute,
|
||||
run_subprocess,
|
||||
run_detached_process,
|
||||
run_openpype_process,
|
||||
clean_envs_for_openpype_process,
|
||||
path_to_subprocess_arg,
|
||||
|
|
@ -130,7 +131,7 @@ from .applications import (
|
|||
PostLaunchHook,
|
||||
|
||||
EnvironmentPrepData,
|
||||
prepare_host_environments,
|
||||
prepare_app_environments,
|
||||
prepare_context_environments,
|
||||
get_app_environments_for_context,
|
||||
apply_project_environments_value
|
||||
|
|
@ -188,6 +189,7 @@ __all__ = [
|
|||
"get_linux_launcher_args",
|
||||
"execute",
|
||||
"run_subprocess",
|
||||
"run_detached_process",
|
||||
"run_openpype_process",
|
||||
"clean_envs_for_openpype_process",
|
||||
"path_to_subprocess_arg",
|
||||
|
|
@ -261,7 +263,7 @@ __all__ = [
|
|||
"PreLaunchHook",
|
||||
"PostLaunchHook",
|
||||
"EnvironmentPrepData",
|
||||
"prepare_host_environments",
|
||||
"prepare_app_environments",
|
||||
"prepare_context_environments",
|
||||
"get_app_environments_for_context",
|
||||
"apply_project_environments_value",
|
||||
|
|
|
|||
|
|
@ -1295,7 +1295,7 @@ def get_app_environments_for_context(
|
|||
"env": env
|
||||
})
|
||||
|
||||
prepare_host_environments(data, env_group)
|
||||
prepare_app_environments(data, env_group)
|
||||
prepare_context_environments(data, env_group)
|
||||
|
||||
# Discard avalon connection
|
||||
|
|
@ -1316,7 +1316,7 @@ def _merge_env(env, current_env):
|
|||
return result
|
||||
|
||||
|
||||
def prepare_host_environments(data, env_group=None, implementation_envs=True):
|
||||
def prepare_app_environments(data, env_group=None, implementation_envs=True):
|
||||
"""Modify launch environments based on launched app and context.
|
||||
|
||||
Args:
|
||||
|
|
@ -1474,6 +1474,22 @@ def prepare_context_environments(data, env_group=None):
|
|||
)
|
||||
|
||||
app = data["app"]
|
||||
context_env = {
|
||||
"AVALON_PROJECT": project_doc["name"],
|
||||
"AVALON_ASSET": asset_doc["name"],
|
||||
"AVALON_TASK": task_name,
|
||||
"AVALON_APP_NAME": app.full_name
|
||||
}
|
||||
|
||||
log.debug(
|
||||
"Context environments set:\n{}".format(
|
||||
json.dumps(context_env, indent=4)
|
||||
)
|
||||
)
|
||||
data["env"].update(context_env)
|
||||
if not app.is_host:
|
||||
return
|
||||
|
||||
workdir_data = get_workdir_data(
|
||||
project_doc, asset_doc, task_name, app.host_name
|
||||
)
|
||||
|
|
@ -1504,20 +1520,8 @@ def prepare_context_environments(data, env_group=None):
|
|||
"Couldn't create workdir because: {}".format(str(exc))
|
||||
)
|
||||
|
||||
context_env = {
|
||||
"AVALON_PROJECT": project_doc["name"],
|
||||
"AVALON_ASSET": asset_doc["name"],
|
||||
"AVALON_TASK": task_name,
|
||||
"AVALON_APP": app.host_name,
|
||||
"AVALON_APP_NAME": app.full_name,
|
||||
"AVALON_WORKDIR": workdir
|
||||
}
|
||||
log.debug(
|
||||
"Context environments set:\n{}".format(
|
||||
json.dumps(context_env, indent=4)
|
||||
)
|
||||
)
|
||||
data["env"].update(context_env)
|
||||
data["env"]["AVALON_APP"] = app.host_name
|
||||
data["env"]["AVALON_WORKDIR"] = workdir
|
||||
|
||||
_prepare_last_workfile(data, workdir)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ def collect_frames(files):
|
|||
Returns:
|
||||
(dict): {'/asset/subset_v001.0001.png': '0001', ....}
|
||||
"""
|
||||
collections, remainder = clique.assemble(files)
|
||||
collections, remainder = clique.assemble(files, minimum_items=1)
|
||||
|
||||
sources_and_frames = {}
|
||||
if collections:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import platform
|
||||
import json
|
||||
import tempfile
|
||||
import distutils.spawn
|
||||
|
||||
from .log import PypeLogger as Logger
|
||||
|
|
@ -181,6 +185,80 @@ def run_openpype_process(*args, **kwargs):
|
|||
return run_subprocess(args, env=env, **kwargs)
|
||||
|
||||
|
||||
def run_detached_process(args, **kwargs):
|
||||
"""Execute process with passed arguments as separated process.
|
||||
|
||||
Values from 'os.environ' are used for environments if are not passed.
|
||||
They are cleaned using 'clean_envs_for_openpype_process' function.
|
||||
|
||||
Example:
|
||||
```
|
||||
run_detached_openpype_process("run", "<path to .py script>")
|
||||
```
|
||||
|
||||
Args:
|
||||
*args (tuple): OpenPype cli arguments.
|
||||
**kwargs (dict): Keyword arguments for for subprocess.Popen.
|
||||
|
||||
Returns:
|
||||
subprocess.Popen: Pointer to launched process but it is possible that
|
||||
launched process is already killed (on linux).
|
||||
"""
|
||||
env = kwargs.pop("env", None)
|
||||
# Keep env untouched if are passed and not empty
|
||||
if not env:
|
||||
env = os.environ
|
||||
|
||||
# Create copy of passed env
|
||||
kwargs["env"] = {k: v for k, v in env.items()}
|
||||
|
||||
low_platform = platform.system().lower()
|
||||
if low_platform == "darwin":
|
||||
new_args = ["open", "-na", args.pop(0), "--args"]
|
||||
new_args.extend(args)
|
||||
args = new_args
|
||||
|
||||
elif low_platform == "windows":
|
||||
flags = (
|
||||
subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.DETACHED_PROCESS
|
||||
)
|
||||
kwargs["creationflags"] = flags
|
||||
|
||||
if not sys.stdout:
|
||||
kwargs["stdout"] = subprocess.DEVNULL
|
||||
kwargs["stderr"] = subprocess.DEVNULL
|
||||
|
||||
elif low_platform == "linux" and get_linux_launcher_args() is not None:
|
||||
json_data = {
|
||||
"args": args,
|
||||
"env": kwargs.pop("env")
|
||||
}
|
||||
json_temp = tempfile.NamedTemporaryFile(
|
||||
mode="w", prefix="op_app_args", suffix=".json", delete=False
|
||||
)
|
||||
json_temp.close()
|
||||
json_temp_filpath = json_temp.name
|
||||
with open(json_temp_filpath, "w") as stream:
|
||||
json.dump(json_data, stream)
|
||||
|
||||
new_args = get_linux_launcher_args()
|
||||
new_args.append(json_temp_filpath)
|
||||
|
||||
# Create mid-process which will launch application
|
||||
process = subprocess.Popen(new_args, **kwargs)
|
||||
# Wait until the process finishes
|
||||
# - This is important! The process would stay in "open" state.
|
||||
process.wait()
|
||||
# Remove the temp file
|
||||
os.remove(json_temp_filpath)
|
||||
# Return process which is already terminated
|
||||
return process
|
||||
|
||||
process = subprocess.Popen(args, **kwargs)
|
||||
return process
|
||||
|
||||
|
||||
def path_to_subprocess_arg(path):
|
||||
"""Prepare path for subprocess arguments.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,11 +49,13 @@ class Terminal:
|
|||
"""
|
||||
|
||||
from openpype.lib import env_value_to_bool
|
||||
use_colors = env_value_to_bool(
|
||||
"OPENPYPE_LOG_NO_COLORS", default=Terminal.use_colors
|
||||
log_no_colors = env_value_to_bool(
|
||||
"OPENPYPE_LOG_NO_COLORS", default=None
|
||||
)
|
||||
if not use_colors:
|
||||
Terminal.use_colors = use_colors
|
||||
if log_no_colors is not None:
|
||||
Terminal.use_colors = not log_no_colors
|
||||
|
||||
if not Terminal.use_colors:
|
||||
Terminal._initialized = True
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -33,16 +33,21 @@ DEFAULT_OPENPYPE_MODULES = (
|
|||
"avalon_apps",
|
||||
"clockify",
|
||||
"log_viewer",
|
||||
"deadline",
|
||||
"muster",
|
||||
"royalrender",
|
||||
"python_console_interpreter",
|
||||
"ftrack",
|
||||
"slack",
|
||||
"webserver",
|
||||
"launcher_action",
|
||||
"project_manager_action",
|
||||
"settings_action",
|
||||
"standalonepublish_action",
|
||||
"traypublish_action",
|
||||
"job_queue",
|
||||
"timers_manager",
|
||||
"sync_server",
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -218,8 +223,6 @@ def load_interfaces(force=False):
|
|||
|
||||
def _load_interfaces():
|
||||
# Key under which will be modules imported in `sys.modules`
|
||||
from openpype.lib import import_filepath
|
||||
|
||||
modules_key = "openpype_interfaces"
|
||||
|
||||
sys.modules[modules_key] = openpype_interfaces = (
|
||||
|
|
@ -844,6 +847,7 @@ class TrayModulesManager(ModulesManager):
|
|||
"avalon",
|
||||
"clockify",
|
||||
"standalonepublish_tool",
|
||||
"traypublish_tool",
|
||||
"log_viewer",
|
||||
"local_settings",
|
||||
"settings"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import attr
|
|||
import requests
|
||||
|
||||
import pyblish.api
|
||||
from .abstract_metaplugins import AbstractMetaInstancePlugin
|
||||
from openpype.lib.abstract_metaplugins import AbstractMetaInstancePlugin
|
||||
|
||||
|
||||
def requests_post(*args, **kwargs):
|
||||
|
|
@ -5,9 +5,9 @@ import pyblish.api
|
|||
|
||||
from avalon import api
|
||||
|
||||
from openpype.lib import abstract_submit_deadline
|
||||
from openpype.lib.abstract_submit_deadline import DeadlineJobInfo
|
||||
from openpype.lib import env_value_to_bool
|
||||
from openpype_modules.deadline import abstract_submit_deadline
|
||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||
|
||||
|
||||
@attr.s
|
||||
|
|
@ -24,7 +24,9 @@ class DeadlinePluginInfo():
|
|||
MultiProcess = attr.ib(default=None)
|
||||
|
||||
|
||||
class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline):
|
||||
class AfterEffectsSubmitDeadline(
|
||||
abstract_submit_deadline.AbstractSubmitDeadline
|
||||
):
|
||||
|
||||
label = "Submit AE to Deadline"
|
||||
order = pyblish.api.IntegratorOrder + 0.1
|
||||
|
|
@ -8,11 +8,11 @@ import re
|
|||
|
||||
import attr
|
||||
import pyblish.api
|
||||
|
||||
import openpype.lib.abstract_submit_deadline
|
||||
from openpype.lib.abstract_submit_deadline import DeadlineJobInfo
|
||||
from avalon import api
|
||||
|
||||
from openpype_modules.deadline import abstract_submit_deadline
|
||||
from openpype_modules.deadline.abstract_submit_deadline import DeadlineJobInfo
|
||||
|
||||
|
||||
class _ZipFile(ZipFile):
|
||||
"""Extended check for windows invalid characters."""
|
||||
|
|
@ -217,7 +217,8 @@ class PluginInfo(object):
|
|||
|
||||
|
||||
class HarmonySubmitDeadline(
|
||||
openpype.lib.abstract_submit_deadline.AbstractSubmitDeadline):
|
||||
abstract_submit_deadline.AbstractSubmitDeadline
|
||||
):
|
||||
"""Submit render write of Harmony scene to Deadline.
|
||||
|
||||
Renders are submitted to a Deadline Web Service as
|
||||
|
|
@ -50,8 +50,8 @@ class HoudiniSubmitRenderDeadline(pyblish.api.InstancePlugin):
|
|||
|
||||
# StartFrame to EndFrame by byFrameStep
|
||||
frames = "{start}-{end}x{step}".format(
|
||||
start=int(instance.data["startFrame"]),
|
||||
end=int(instance.data["endFrame"]),
|
||||
start=int(instance.data["frameStart"]),
|
||||
end=int(instance.data["frameEnd"]),
|
||||
step=int(instance.data["byFrameStep"]),
|
||||
)
|
||||
|
||||
|
|
@ -316,8 +316,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin):
|
|||
import speedcopy
|
||||
|
||||
self.log.info("Preparing to copy ...")
|
||||
start = instance.data.get("startFrame")
|
||||
end = instance.data.get("endFrame")
|
||||
start = instance.data.get("frameStart")
|
||||
end = instance.data.get("frameEnd")
|
||||
|
||||
# get latest version of subset
|
||||
# this will stop if subset wasn't published yet
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
import os
|
||||
import json
|
||||
import requests
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.lib.abstract_submit_deadline import requests_get
|
||||
from openpype.lib.delivery import collect_frames
|
||||
from openpype_modules.deadline.abstract_submit_deadline import requests_get
|
||||
|
||||
|
||||
class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
||||
|
|
@ -30,47 +29,58 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
staging_dir = repre["stagingDir"]
|
||||
existing_files = self._get_existing_files(staging_dir)
|
||||
|
||||
expected_non_existent = expected_files.difference(
|
||||
existing_files)
|
||||
if len(expected_non_existent) != 0:
|
||||
self.log.info("Some expected files missing {}".format(
|
||||
expected_non_existent))
|
||||
if self.allow_user_override:
|
||||
# We always check for user override because the user might have
|
||||
# also overridden the Job frame list to be longer than the
|
||||
# originally submitted frame range
|
||||
# todo: We should first check if Job frame range was overridden
|
||||
# at all so we don't unnecessarily override anything
|
||||
file_name_template, frame_placeholder = \
|
||||
self._get_file_name_template_and_placeholder(
|
||||
expected_files)
|
||||
|
||||
if self.allow_user_override:
|
||||
file_name_template, frame_placeholder = \
|
||||
self._get_file_name_template_and_placeholder(
|
||||
expected_files)
|
||||
if not file_name_template:
|
||||
raise RuntimeError("Unable to retrieve file_name template"
|
||||
"from files: {}".format(expected_files))
|
||||
|
||||
if not file_name_template:
|
||||
return
|
||||
job_expected_files = self._get_job_expected_files(
|
||||
file_name_template,
|
||||
frame_placeholder,
|
||||
frame_list)
|
||||
|
||||
real_expected_rendered = self._get_real_render_expected(
|
||||
file_name_template,
|
||||
frame_placeholder,
|
||||
frame_list)
|
||||
job_files_diff = job_expected_files.difference(expected_files)
|
||||
if job_files_diff:
|
||||
self.log.debug(
|
||||
"Detected difference in expected output files from "
|
||||
"Deadline job. Assuming an updated frame list by the "
|
||||
"user. Difference: {}".format(sorted(job_files_diff))
|
||||
)
|
||||
|
||||
real_expected_non_existent = \
|
||||
real_expected_rendered.difference(existing_files)
|
||||
if len(real_expected_non_existent) != 0:
|
||||
raise RuntimeError("Still missing some files {}".
|
||||
format(real_expected_non_existent))
|
||||
self.log.info("Update range from actual job range")
|
||||
repre["files"] = sorted(list(real_expected_rendered))
|
||||
else:
|
||||
raise RuntimeError("Some expected files missing {}".format(
|
||||
expected_non_existent))
|
||||
# Update the representation expected files
|
||||
self.log.info("Update range from actual job range "
|
||||
"to frame list: {}".format(frame_list))
|
||||
repre["files"] = sorted(job_expected_files)
|
||||
|
||||
# Update the expected files
|
||||
expected_files = job_expected_files
|
||||
|
||||
# We don't use set.difference because we do allow other existing
|
||||
# files to be in the folder that we might not want to use.
|
||||
missing = expected_files - existing_files
|
||||
if missing:
|
||||
raise RuntimeError("Missing expected files: {}".format(
|
||||
sorted(missing)))
|
||||
|
||||
def _get_frame_list(self, original_job_id):
|
||||
"""
|
||||
Returns list of frame ranges from all render job.
|
||||
"""Returns list of frame ranges from all render job.
|
||||
|
||||
Render job might be requeried so job_id in metadata.json is invalid
|
||||
GlobalJobPreload injects current ids to RENDER_JOB_IDS.
|
||||
Render job might be re-submitted so job_id in metadata.json could be
|
||||
invalid. GlobalJobPreload injects current job id to RENDER_JOB_IDS.
|
||||
|
||||
Args:
|
||||
original_job_id (str)
|
||||
Returns:
|
||||
(list)
|
||||
Args:
|
||||
original_job_id (str)
|
||||
Returns:
|
||||
(list)
|
||||
"""
|
||||
all_frame_lists = []
|
||||
render_job_ids = os.environ.get("RENDER_JOB_IDS")
|
||||
|
|
@ -87,13 +97,15 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
|
||||
return all_frame_lists
|
||||
|
||||
def _get_real_render_expected(self, file_name_template, frame_placeholder,
|
||||
frame_list):
|
||||
"""
|
||||
Calculates list of names of expected rendered files.
|
||||
def _get_job_expected_files(self,
|
||||
file_name_template,
|
||||
frame_placeholder,
|
||||
frame_list):
|
||||
"""Calculates list of names of expected rendered files.
|
||||
|
||||
Might be different from expected files from submission if user
|
||||
explicitly and manually changed the frame list on the Deadline job.
|
||||
|
||||
Might be different from job expected files if user explicitly and
|
||||
manually change frame list on Deadline job.
|
||||
"""
|
||||
real_expected_rendered = set()
|
||||
src_padding_exp = "%0{}d".format(len(frame_placeholder))
|
||||
|
|
@ -115,6 +127,14 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
|
||||
file_name_template = frame_placeholder = None
|
||||
for file_name, frame in sources_and_frames.items():
|
||||
|
||||
# There might be cases where clique was unable to collect
|
||||
# collections in `collect_frames` - thus we capture that case
|
||||
if frame is None:
|
||||
self.log.warning("Unable to detect frame from filename: "
|
||||
"{}".format(file_name))
|
||||
continue
|
||||
|
||||
frame_placeholder = "#" * len(frame)
|
||||
file_name_template = os.path.basename(
|
||||
file_name.replace(frame, frame_placeholder))
|
||||
|
|
@ -123,11 +143,11 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
return file_name_template, frame_placeholder
|
||||
|
||||
def _get_job_info(self, job_id):
|
||||
"""
|
||||
Calls DL for actual job info for 'job_id'
|
||||
"""Calls DL for actual job info for 'job_id'
|
||||
|
||||
Might be different than job info saved in metadata.json if user
|
||||
manually changes job pre/during rendering.
|
||||
|
||||
Might be different than job info saved in metadata.json if user
|
||||
manually changes job pre/during rendering.
|
||||
"""
|
||||
# get default deadline webservice url from deadline module
|
||||
deadline_url = self.instance.context.data["defaultDeadline"]
|
||||
|
|
@ -140,8 +160,8 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
try:
|
||||
response = requests_get(url)
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("Deadline is not accessible at {}".format(deadline_url))
|
||||
# self.log("Deadline is not accessible at {}".format(deadline_url))
|
||||
self.log.error("Deadline is not accessible at "
|
||||
"{}".format(deadline_url))
|
||||
return {}
|
||||
|
||||
if not response.ok:
|
||||
|
|
@ -155,29 +175,26 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
return json_content.pop()
|
||||
return {}
|
||||
|
||||
def _parse_metadata_json(self, json_path):
|
||||
if not os.path.exists(json_path):
|
||||
msg = "Metadata file {} doesn't exist".format(json_path)
|
||||
raise RuntimeError(msg)
|
||||
|
||||
with open(json_path) as fp:
|
||||
try:
|
||||
return json.load(fp)
|
||||
except Exception as exc:
|
||||
self.log.error(
|
||||
"Error loading json: "
|
||||
"{} - Exception: {}".format(json_path, exc)
|
||||
)
|
||||
|
||||
def _get_existing_files(self, out_dir):
|
||||
"""Returns set of existing file names from 'out_dir'"""
|
||||
def _get_existing_files(self, staging_dir):
|
||||
"""Returns set of existing file names from 'staging_dir'"""
|
||||
existing_files = set()
|
||||
for file_name in os.listdir(out_dir):
|
||||
for file_name in os.listdir(staging_dir):
|
||||
existing_files.add(file_name)
|
||||
return existing_files
|
||||
|
||||
def _get_expected_files(self, repre):
|
||||
"""Returns set of file names from metadata.json"""
|
||||
"""Returns set of file names in representation['files']
|
||||
|
||||
The representations are collected from `CollectRenderedFiles` using
|
||||
the metadata.json file submitted along with the render job.
|
||||
|
||||
Args:
|
||||
repre (dict): The representation containing 'files'
|
||||
|
||||
Returns:
|
||||
set: Set of expected file_names in the staging directory.
|
||||
|
||||
"""
|
||||
expected_files = set()
|
||||
|
||||
files = repre["files"]
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
|
@ -1 +0,0 @@
|
|||
Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue