mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge remote-tracking branch 'origin/develop' into enhancement/OP-2040_Maya_Render_creator-should-have-configurable-options
This commit is contained in:
commit
e65798b612
72 changed files with 2507 additions and 1823 deletions
|
|
@ -266,7 +266,7 @@ class AssetLoader(LoaderPlugin):
|
|||
# Only containerise if it's not already a collection from a .blend file.
|
||||
# representation = context["representation"]["name"]
|
||||
# if representation != "blend":
|
||||
# from avalon.blender.pipeline import containerise
|
||||
# from openpype.hosts.blender.api.pipeline import containerise
|
||||
# return containerise(
|
||||
# name=name,
|
||||
# namespace=namespace,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ def install():
|
|||
This is where you install menus and register families, data
|
||||
and loaders into fusion.
|
||||
|
||||
It is called automatically when installing via `api.install(avalon.fusion)`
|
||||
It is called automatically when installing via
|
||||
`openpype.pipeline.install_host(openpype.hosts.fusion.api)`
|
||||
|
||||
See the Maya equivalent for inspiration on how to implement this.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from openpype.pipeline import load
|
|||
|
||||
|
||||
class FusionSetFrameRangeLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range excluding pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
@ -40,7 +40,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin):
|
|||
|
||||
|
||||
class FusionSetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range including pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
|
|||
|
|
@ -463,7 +463,7 @@ def imprint(node_id, data, remove=False):
|
|||
remove (bool): Removes the data from the scene.
|
||||
|
||||
Example:
|
||||
>>> from avalon.harmony import lib
|
||||
>>> from openpype.hosts.harmony.api import lib
|
||||
>>> node = "Top/Display"
|
||||
>>> data = {"str": "someting", "int": 1, "float": 0.32, "bool": True}
|
||||
>>> lib.imprint(layer, data)
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ class CollectFarmRender(openpype.lib.abstract_collect_render.
|
|||
label=node.split("/")[1],
|
||||
subset=subset_name,
|
||||
asset=legacy_io.Session["AVALON_ASSET"],
|
||||
task=task_name,
|
||||
attachTo=False,
|
||||
setMembers=[node],
|
||||
publish=info[4],
|
||||
|
|
|
|||
|
|
@ -553,10 +553,10 @@ class PublishAction(QtWidgets.QAction):
|
|||
#
|
||||
# '''
|
||||
# import hiero.core
|
||||
# from avalon.nuke import imprint
|
||||
# from pype.hosts.nuke import (
|
||||
# lib as nklib
|
||||
# )
|
||||
# from openpype.hosts.nuke.api.lib import (
|
||||
# BuildWorkfile,
|
||||
# imprint
|
||||
# )
|
||||
#
|
||||
# # check if the file exists if does then Raise "File exists!"
|
||||
# if os.path.exists(filepath):
|
||||
|
|
@ -583,8 +583,7 @@ class PublishAction(QtWidgets.QAction):
|
|||
#
|
||||
# nuke_script.addNode(root_node)
|
||||
#
|
||||
# # here to call pype.hosts.nuke.lib.BuildWorkfile
|
||||
# script_builder = nklib.BuildWorkfile(
|
||||
# script_builder = BuildWorkfile(
|
||||
# root_node=root_node,
|
||||
# root_path=root_path,
|
||||
# nodes=nuke_script.getNodes(),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from openpype.pipeline import load
|
|||
|
||||
|
||||
class SetFrameRangeLoader(load.LoaderPlugin):
|
||||
"""Set Houdini frame range"""
|
||||
"""Set frame range excluding pre- and post-handles"""
|
||||
|
||||
families = [
|
||||
"animation",
|
||||
|
|
@ -44,7 +44,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
|
|||
|
||||
|
||||
class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
||||
"""Set Maya frame range including pre- and post-handles"""
|
||||
"""Set frame range including pre- and post-handles"""
|
||||
|
||||
families = [
|
||||
"animation",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from openpype.hosts.houdini.api import pipeline
|
|||
|
||||
|
||||
class AbcLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Load Alembic"""
|
||||
|
||||
families = ["model", "animation", "pointcache", "gpuCache"]
|
||||
label = "Load Alembic"
|
||||
|
|
|
|||
75
openpype/hosts/houdini/plugins/load/load_alembic_archive.py
Normal file
75
openpype/hosts/houdini/plugins/load/load_alembic_archive.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
from openpype.pipeline import (
|
||||
load,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.hosts.houdini.api import pipeline
|
||||
|
||||
|
||||
class AbcArchiveLoader(load.LoaderPlugin):
|
||||
"""Load Alembic as full geometry network hierarchy """
|
||||
|
||||
families = ["model", "animation", "pointcache", "gpuCache"]
|
||||
label = "Load Alembic as Archive"
|
||||
representations = ["abc"]
|
||||
order = -5
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
import hou
|
||||
|
||||
# Format file name, Houdini only wants forward slashes
|
||||
file_path = os.path.normpath(self.fname)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
|
||||
# Get the root node
|
||||
obj = hou.node("/obj")
|
||||
|
||||
# Define node name
|
||||
namespace = namespace if namespace else context["asset"]["name"]
|
||||
node_name = "{}_{}".format(namespace, name) if namespace else name
|
||||
|
||||
# Create an Alembic archive node
|
||||
node = obj.createNode("alembicarchive", node_name=node_name)
|
||||
node.moveToGoodPosition()
|
||||
|
||||
# TODO: add FPS of project / asset
|
||||
node.setParms({"fileName": file_path,
|
||||
"channelRef": True})
|
||||
|
||||
# Apply some magic
|
||||
node.parm("buildHierarchy").pressButton()
|
||||
node.moveToGoodPosition()
|
||||
|
||||
nodes = [node]
|
||||
|
||||
self[:] = nodes
|
||||
|
||||
return pipeline.containerise(node_name,
|
||||
namespace,
|
||||
nodes,
|
||||
context,
|
||||
self.__class__.__name__,
|
||||
suffix="")
|
||||
|
||||
def update(self, container, representation):
|
||||
|
||||
node = container["node"]
|
||||
|
||||
# Update the file path
|
||||
file_path = get_representation_path(representation)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
|
||||
# Update attributes
|
||||
node.setParms({"fileName": file_path,
|
||||
"representation": str(representation["_id"])})
|
||||
|
||||
# Rebuild
|
||||
node.parm("buildHierarchy").pressButton()
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
node = container["node"]
|
||||
node.destroy()
|
||||
107
openpype/hosts/houdini/plugins/load/load_bgeo.py
Normal file
107
openpype/hosts/houdini/plugins/load/load_bgeo.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
|
||||
from openpype.pipeline import (
|
||||
load,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.hosts.houdini.api import pipeline
|
||||
|
||||
|
||||
class BgeoLoader(load.LoaderPlugin):
|
||||
"""Load bgeo files to Houdini."""
|
||||
|
||||
label = "Load bgeo"
|
||||
families = ["model", "pointcache", "bgeo"]
|
||||
representations = [
|
||||
"bgeo", "bgeosc", "bgeogz",
|
||||
"bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"]
|
||||
order = -10
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
import hou
|
||||
|
||||
# Get the root node
|
||||
obj = hou.node("/obj")
|
||||
|
||||
# Define node name
|
||||
namespace = namespace if namespace else context["asset"]["name"]
|
||||
node_name = "{}_{}".format(namespace, name) if namespace else name
|
||||
|
||||
# Create a new geo node
|
||||
container = obj.createNode("geo", node_name=node_name)
|
||||
is_sequence = bool(context["representation"]["context"].get("frame"))
|
||||
|
||||
# Remove the file node, it only loads static meshes
|
||||
# Houdini 17 has removed the file node from the geo node
|
||||
file_node = container.node("file1")
|
||||
if file_node:
|
||||
file_node.destroy()
|
||||
|
||||
# Explicitly create a file node
|
||||
file_node = container.createNode("file", node_name=node_name)
|
||||
file_node.setParms({"file": self.format_path(self.fname, is_sequence)})
|
||||
|
||||
# Set display on last node
|
||||
file_node.setDisplayFlag(True)
|
||||
|
||||
nodes = [container, file_node]
|
||||
self[:] = nodes
|
||||
|
||||
return pipeline.containerise(
|
||||
node_name,
|
||||
namespace,
|
||||
nodes,
|
||||
context,
|
||||
self.__class__.__name__,
|
||||
suffix="",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_path(path, is_sequence):
|
||||
"""Format file path correctly for single bgeo or bgeo sequence."""
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError("Path does not exist: %s" % path)
|
||||
|
||||
# The path is either a single file or sequence in a folder.
|
||||
if not is_sequence:
|
||||
filename = path
|
||||
print("single")
|
||||
else:
|
||||
filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path)
|
||||
|
||||
filename = os.path.join(path, filename)
|
||||
|
||||
filename = os.path.normpath(filename)
|
||||
filename = filename.replace("\\", "/")
|
||||
|
||||
return filename
|
||||
|
||||
def update(self, container, representation):
|
||||
|
||||
node = container["node"]
|
||||
try:
|
||||
file_node = next(
|
||||
n for n in node.children() if n.type().name() == "file"
|
||||
)
|
||||
except StopIteration:
|
||||
self.log.error("Could not find node of type `alembic`")
|
||||
return
|
||||
|
||||
# Update the file path
|
||||
file_path = get_representation_path(representation)
|
||||
file_path = self.format_path(file_path)
|
||||
|
||||
file_node.setParms({"fileName": file_path})
|
||||
|
||||
# Update attribute
|
||||
node.setParms({"representation": str(representation["_id"])})
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
node = container["node"]
|
||||
node.destroy()
|
||||
|
|
@ -78,7 +78,7 @@ def transfer_non_default_values(src, dest, ignore=None):
|
|||
|
||||
|
||||
class CameraLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Load camera from an Alembic file"""
|
||||
|
||||
families = ["camera"]
|
||||
label = "Load Camera (abc)"
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ def get_image_avalon_container():
|
|||
|
||||
|
||||
class ImageLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Load images into COP2"""
|
||||
|
||||
families = ["colorbleed.imagesequence"]
|
||||
families = ["imagesequence"]
|
||||
label = "Load Image (COP2)"
|
||||
representations = ["*"]
|
||||
order = -10
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from openpype.hosts.houdini.api import pipeline
|
|||
|
||||
|
||||
class VdbLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Load VDB"""
|
||||
|
||||
families = ["vdbcache"]
|
||||
label = "Load VDB"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
from openpype.lib.vendor_bin_utils import find_executable
|
||||
from openpype.pipeline import load
|
||||
|
||||
|
||||
|
|
@ -14,12 +18,7 @@ class ShowInUsdview(load.LoaderPlugin):
|
|||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import avalon.lib as lib
|
||||
|
||||
usdview = lib.which("usdview")
|
||||
usdview = find_executable("usdview")
|
||||
|
||||
filepath = os.path.normpath(self.fname)
|
||||
filepath = filepath.replace("\\", "/")
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ IMAGE_PREFIXES = {
|
|||
"arnold": "defaultRenderGlobals.imageFilePrefix",
|
||||
"renderman": "rmanGlobals.imageFileFormat",
|
||||
"redshift": "defaultRenderGlobals.imageFilePrefix",
|
||||
"mayahardware2": "defaultRenderGlobals.imageFilePrefix"
|
||||
}
|
||||
|
||||
RENDERMAN_IMAGE_DIR = "maya/<scene>/<layer>"
|
||||
|
|
@ -169,7 +170,8 @@ def get(layer, render_instance=None):
|
|||
"arnold": RenderProductsArnold,
|
||||
"vray": RenderProductsVray,
|
||||
"redshift": RenderProductsRedshift,
|
||||
"renderman": RenderProductsRenderman
|
||||
"renderman": RenderProductsRenderman,
|
||||
"mayahardware2": RenderProductsMayaHardware
|
||||
}.get(renderer_name.lower(), None)
|
||||
if renderer is None:
|
||||
raise UnsupportedRendererException(
|
||||
|
|
@ -1173,6 +1175,67 @@ class RenderProductsRenderman(ARenderProducts):
|
|||
return new_files
|
||||
|
||||
|
||||
class RenderProductsMayaHardware(ARenderProducts):
|
||||
"""Expected files for MayaHardware renderer."""
|
||||
|
||||
renderer = "mayahardware2"
|
||||
|
||||
extensions = [
|
||||
{"label": "JPEG", "index": 8, "extension": "jpg"},
|
||||
{"label": "PNG", "index": 32, "extension": "png"},
|
||||
{"label": "EXR(exr)", "index": 40, "extension": "exr"}
|
||||
]
|
||||
|
||||
def _get_extension(self, value):
|
||||
result = None
|
||||
if isinstance(value, int):
|
||||
extensions = {
|
||||
extension["index"]: extension["extension"]
|
||||
for extension in self.extensions
|
||||
}
|
||||
try:
|
||||
result = extensions[value]
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
"Could not find extension for {}".format(value)
|
||||
)
|
||||
|
||||
if isinstance(value, six.string_types):
|
||||
extensions = {
|
||||
extension["label"]: extension["extension"]
|
||||
for extension in self.extensions
|
||||
}
|
||||
try:
|
||||
result = extensions[value]
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
"Could not find extension for {}".format(value)
|
||||
)
|
||||
|
||||
if not result:
|
||||
raise NotImplementedError(
|
||||
"Could not find extension for {}".format(value)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
def get_render_products(self):
|
||||
"""Get all AOVs.
|
||||
See Also:
|
||||
:func:`ARenderProducts.get_render_products()`
|
||||
"""
|
||||
ext = self._get_extension(
|
||||
self._get_attr("defaultRenderGlobals.imageFormat")
|
||||
)
|
||||
|
||||
products = []
|
||||
for cam in self.get_renderable_cameras():
|
||||
product = RenderProduct(productName="beauty", ext=ext, camera=cam)
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
||||
|
||||
class AOVError(Exception):
|
||||
"""Custom exception for determining AOVs."""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import openpype.hosts.maya.api.plugin
|
|||
|
||||
|
||||
class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Loader to reference an Alembic file"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from openpype.hosts.maya.api.lib import (
|
|||
|
||||
|
||||
class SetFrameRangeLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range excluding pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
@ -44,7 +44,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
|
|||
|
||||
|
||||
class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range including pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from openpype.hosts.maya.api.pipeline import containerise
|
|||
|
||||
|
||||
class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
||||
"""Load the Proxy"""
|
||||
"""Load Arnold Proxy as reference"""
|
||||
|
||||
families = ["ass"]
|
||||
representations = ["ass"]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from openpype.api import get_project_settings
|
|||
|
||||
|
||||
class GpuCacheLoader(load.LoaderPlugin):
|
||||
"""Load model Alembic as gpuCache"""
|
||||
"""Load Alembic as gpuCache"""
|
||||
|
||||
families = ["model"]
|
||||
representations = ["abc"]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from openpype.hosts.maya.api.lib import maintained_selection
|
|||
|
||||
|
||||
class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
||||
"""Load the model"""
|
||||
"""Reference file"""
|
||||
|
||||
families = ["model",
|
||||
"pointcache",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ def _fix_duplicate_vvg_callbacks():
|
|||
|
||||
|
||||
class LoadVDBtoVRay(load.LoaderPlugin):
|
||||
"""Load OpenVDB in a V-Ray Volume Grid"""
|
||||
|
||||
families = ["vdbcache"]
|
||||
representations = ["vdb"]
|
||||
|
|
|
|||
|
|
@ -287,7 +287,8 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
|
|||
"byFrameStep": int(
|
||||
self.get_render_attribute("byFrameStep",
|
||||
layer=layer_name)),
|
||||
"renderer": renderer,
|
||||
"renderer": self.get_render_attribute(
|
||||
"currentRenderer", layer=layer_name).lower(),
|
||||
# instance subset
|
||||
"family": "renderlayer",
|
||||
"families": ["renderlayer"],
|
||||
|
|
|
|||
|
|
@ -50,15 +50,17 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin):
|
|||
'vray': 'vraySettings.fileNamePrefix',
|
||||
'arnold': 'defaultRenderGlobals.imageFilePrefix',
|
||||
'renderman': 'rmanGlobals.imageFileFormat',
|
||||
'redshift': 'defaultRenderGlobals.imageFilePrefix'
|
||||
'redshift': 'defaultRenderGlobals.imageFilePrefix',
|
||||
'mayahardware2': 'defaultRenderGlobals.imageFilePrefix',
|
||||
}
|
||||
|
||||
ImagePrefixTokens = {
|
||||
|
||||
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
|
||||
'mentalray': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa: E501
|
||||
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa: E501
|
||||
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>',
|
||||
'vray': 'maya/<Scene>/<Layer>/<Layer>',
|
||||
'renderman': '<layer>{aov_separator}<aov>.<f4>.<ext>' # noqa
|
||||
'renderman': '<layer>{aov_separator}<aov>.<f4>.<ext>',
|
||||
'mayahardware2': 'maya/<Scene>/<RenderLayer>/<RenderLayer>',
|
||||
}
|
||||
|
||||
_aov_chars = {
|
||||
|
|
@ -234,7 +236,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin):
|
|||
# load validation definitions from settings
|
||||
validation_settings = (
|
||||
instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501
|
||||
"{}_render_attributes".format(renderer))
|
||||
"{}_render_attributes".format(renderer)) or []
|
||||
)
|
||||
|
||||
# go through definitions and test if such node.attribute exists.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from pprint import pformat
|
||||
import re
|
||||
import six
|
||||
import platform
|
||||
|
|
@ -193,7 +194,7 @@ def imprint(node, data, tab=None):
|
|||
Examples:
|
||||
```
|
||||
import nuke
|
||||
from avalon.nuke import lib
|
||||
from openpype.hosts.nuke.api import lib
|
||||
|
||||
node = nuke.createNode("NoOp")
|
||||
data = {
|
||||
|
|
@ -364,17 +365,15 @@ def fix_data_for_node_create(data):
|
|||
return data
|
||||
|
||||
|
||||
def add_write_node(name, **kwarg):
|
||||
def add_write_node_legacy(name, **kwarg):
|
||||
"""Adding nuke write node
|
||||
|
||||
Arguments:
|
||||
name (str): nuke node name
|
||||
kwarg (attrs): data for nuke knobs
|
||||
|
||||
Returns:
|
||||
node (obj): nuke write node
|
||||
"""
|
||||
frame_range = kwarg.get("frame_range", None)
|
||||
frame_range = kwarg.get("use_range_limit", None)
|
||||
|
||||
w = nuke.createNode(
|
||||
"Write",
|
||||
|
|
@ -400,6 +399,35 @@ def add_write_node(name, **kwarg):
|
|||
return w
|
||||
|
||||
|
||||
def add_write_node(name, file_path, knobs, **kwarg):
|
||||
"""Adding nuke write node
|
||||
|
||||
Arguments:
|
||||
name (str): nuke node name
|
||||
kwarg (attrs): data for nuke knobs
|
||||
|
||||
Returns:
|
||||
node (obj): nuke write node
|
||||
"""
|
||||
frame_range = kwarg.get("use_range_limit", None)
|
||||
|
||||
w = nuke.createNode(
|
||||
"Write",
|
||||
"name {}".format(name))
|
||||
|
||||
w["file"].setValue(file_path)
|
||||
|
||||
# finally add knob overrides
|
||||
set_node_knobs_from_settings(w, knobs, **kwarg)
|
||||
|
||||
if frame_range:
|
||||
w["use_limit"].setValue(True)
|
||||
w["first"].setValue(frame_range[0])
|
||||
w["last"].setValue(frame_range[1])
|
||||
|
||||
return w
|
||||
|
||||
|
||||
def read_avalon_data(node):
|
||||
"""Return user-defined knobs from given `node`
|
||||
|
||||
|
|
@ -500,13 +528,9 @@ def get_nuke_imageio_settings():
|
|||
return get_anatomy_settings(Context.project_name)["imageio"]["nuke"]
|
||||
|
||||
|
||||
def get_created_node_imageio_setting(**kwarg):
|
||||
def get_created_node_imageio_setting_legacy(nodeclass, creator, subset):
|
||||
''' Get preset data for dataflow (fileType, compression, bitDepth)
|
||||
'''
|
||||
log.debug(kwarg)
|
||||
nodeclass = kwarg.get("nodeclass", None)
|
||||
creator = kwarg.get("creator", None)
|
||||
subset = kwarg.get("subset", None)
|
||||
|
||||
assert any([creator, nodeclass]), nuke.message(
|
||||
"`{}`: Missing mandatory kwargs `host`, `cls`".format(__file__))
|
||||
|
|
@ -578,6 +602,97 @@ def get_created_node_imageio_setting(**kwarg):
|
|||
return imageio_node
|
||||
|
||||
|
||||
def get_imageio_node_setting(node_class, plugin_name, subset):
|
||||
''' Get preset data for dataflow (fileType, compression, bitDepth)
|
||||
'''
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
required_nodes = imageio_nodes["requiredNodes"]
|
||||
|
||||
imageio_node = None
|
||||
for node in required_nodes:
|
||||
log.info(node)
|
||||
if (
|
||||
node_class in node["nukeNodeClass"]
|
||||
and plugin_name in node["plugins"]
|
||||
):
|
||||
imageio_node = node
|
||||
break
|
||||
|
||||
log.debug("__ imageio_node: {}".format(imageio_node))
|
||||
|
||||
if not imageio_node:
|
||||
return
|
||||
|
||||
# find overrides and update knobs with them
|
||||
get_imageio_node_override_setting(
|
||||
node_class,
|
||||
plugin_name,
|
||||
subset,
|
||||
imageio_node["knobs"]
|
||||
)
|
||||
|
||||
log.info("ImageIO node: {}".format(imageio_node))
|
||||
return imageio_node
|
||||
|
||||
|
||||
def get_imageio_node_override_setting(
|
||||
node_class, plugin_name, subset, knobs_settings
|
||||
):
|
||||
''' Get imageio node overrides from settings
|
||||
'''
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
override_nodes = imageio_nodes["overrideNodes"]
|
||||
|
||||
# find matching override node
|
||||
override_imageio_node = None
|
||||
for onode in override_nodes:
|
||||
log.info(onode)
|
||||
if node_class not in onode["nukeNodeClass"]:
|
||||
continue
|
||||
|
||||
if plugin_name not in onode["plugins"]:
|
||||
continue
|
||||
|
||||
if (
|
||||
onode["subsets"]
|
||||
and not any(re.search(s, subset) for s in onode["subsets"])
|
||||
):
|
||||
continue
|
||||
|
||||
override_imageio_node = onode
|
||||
break
|
||||
|
||||
log.debug("__ override_imageio_node: {}".format(override_imageio_node))
|
||||
# add overrides to imageio_node
|
||||
if override_imageio_node:
|
||||
# get all knob names in imageio_node
|
||||
knob_names = [k["name"] for k in knobs_settings]
|
||||
|
||||
for oknob in override_imageio_node["knobs"]:
|
||||
for knob in knobs_settings:
|
||||
# override matching knob name
|
||||
if oknob["name"] == knob["name"]:
|
||||
log.debug(
|
||||
"_ overriding knob: `{}` > `{}`".format(
|
||||
knob, oknob
|
||||
))
|
||||
if not oknob["value"]:
|
||||
# remove original knob if no value found in oknob
|
||||
knobs_settings.remove(knob)
|
||||
else:
|
||||
# override knob value with oknob's
|
||||
knob["value"] = oknob["value"]
|
||||
|
||||
# add missing knobs into imageio_node
|
||||
if oknob["name"] not in knob_names:
|
||||
log.debug(
|
||||
"_ adding knob: `{}`".format(oknob))
|
||||
knobs_settings.append(oknob)
|
||||
knob_names.append(oknob["name"])
|
||||
|
||||
return knobs_settings
|
||||
|
||||
|
||||
def get_imageio_input_colorspace(filename):
|
||||
''' Get input file colorspace based on regex in settings.
|
||||
'''
|
||||
|
|
@ -725,15 +840,14 @@ def check_subsetname_exists(nodes, subset_name):
|
|||
def get_render_path(node):
|
||||
''' Generate Render path from presets regarding avalon knob data
|
||||
'''
|
||||
data = {'avalon': read_avalon_data(node)}
|
||||
data_preset = {
|
||||
"nodeclass": data["avalon"]["family"],
|
||||
"families": [data["avalon"]["families"]],
|
||||
"creator": data["avalon"]["creator"],
|
||||
"subset": data["avalon"]["subset"]
|
||||
}
|
||||
avalon_knob_data = read_avalon_data(node)
|
||||
data = {'avalon': avalon_knob_data}
|
||||
|
||||
nuke_imageio_writes = get_created_node_imageio_setting(**data_preset)
|
||||
nuke_imageio_writes = get_imageio_node_setting(
|
||||
node_class=avalon_knob_data["family"],
|
||||
plugin_name=avalon_knob_data["creator"],
|
||||
subset=avalon_knob_data["subset"]
|
||||
)
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
|
||||
data.update({
|
||||
|
|
@ -825,8 +939,282 @@ def add_button_clear_rendered(node, path):
|
|||
node.addKnob(knob)
|
||||
|
||||
|
||||
def create_write_node(name, data, input=None, prenodes=None,
|
||||
review=True, linked_knobs=None, farm=True):
|
||||
def create_prenodes(
|
||||
prev_node,
|
||||
nodes_setting,
|
||||
plugin_name=None,
|
||||
subset=None,
|
||||
**kwargs
|
||||
):
|
||||
last_node = None
|
||||
for_dependency = {}
|
||||
for name, node in nodes_setting.items():
|
||||
# get attributes
|
||||
nodeclass = node["nodeclass"]
|
||||
knobs = node["knobs"]
|
||||
|
||||
# create node
|
||||
now_node = nuke.createNode(
|
||||
nodeclass, "name {}".format(name))
|
||||
now_node.hideControlPanel()
|
||||
|
||||
# add for dependency linking
|
||||
for_dependency[name] = {
|
||||
"node": now_node,
|
||||
"dependent": node["dependent"]
|
||||
}
|
||||
|
||||
if all([plugin_name, subset]):
|
||||
# find imageio overrides
|
||||
get_imageio_node_override_setting(
|
||||
now_node.Class(),
|
||||
plugin_name,
|
||||
subset,
|
||||
knobs
|
||||
)
|
||||
|
||||
# add data to knob
|
||||
set_node_knobs_from_settings(now_node, knobs, **kwargs)
|
||||
|
||||
# switch actual node to previous
|
||||
last_node = now_node
|
||||
|
||||
for _node_name, node_prop in for_dependency.items():
|
||||
if not node_prop["dependent"]:
|
||||
node_prop["node"].setInput(
|
||||
0, prev_node)
|
||||
elif node_prop["dependent"] in for_dependency:
|
||||
_prev_node = for_dependency[node_prop["dependent"]]["node"]
|
||||
node_prop["node"].setInput(
|
||||
0, _prev_node)
|
||||
else:
|
||||
log.warning("Dependency has wrong name of node: {}".format(
|
||||
node_prop
|
||||
))
|
||||
|
||||
return last_node
|
||||
|
||||
|
||||
def create_write_node(
|
||||
name,
|
||||
data,
|
||||
input=None,
|
||||
prenodes=None,
|
||||
review=True,
|
||||
farm=True,
|
||||
linked_knobs=None,
|
||||
**kwargs
|
||||
):
|
||||
''' Creating write node which is group node
|
||||
|
||||
Arguments:
|
||||
name (str): name of node
|
||||
data (dict): creator write instance data
|
||||
input (node)[optional]: selected node to connect to
|
||||
prenodes (dict)[optional]:
|
||||
nodes to be created before write with dependency
|
||||
review (bool)[optional]: adding review knob
|
||||
farm (bool)[optional]: rendering workflow target
|
||||
kwargs (dict)[optional]: additional key arguments for formating
|
||||
|
||||
Example:
|
||||
prenodes = {
|
||||
"nodeName": {
|
||||
"nodeclass": "Reformat",
|
||||
"dependent": [
|
||||
following_node_01,
|
||||
...
|
||||
],
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "knobname",
|
||||
"value": "knob value"
|
||||
},
|
||||
...
|
||||
]
|
||||
},
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
Return:
|
||||
node (obj): group node with avalon data as Knobs
|
||||
'''
|
||||
prenodes = prenodes or {}
|
||||
|
||||
# group node knob overrides
|
||||
knob_overrides = data.pop("knobs", [])
|
||||
|
||||
# filtering variables
|
||||
plugin_name = data["creator"]
|
||||
subset = data["subset"]
|
||||
|
||||
# get knob settings for write node
|
||||
imageio_writes = get_imageio_node_setting(
|
||||
node_class=data["nodeclass"],
|
||||
plugin_name=plugin_name,
|
||||
subset=subset
|
||||
)
|
||||
|
||||
for knob in imageio_writes["knobs"]:
|
||||
if knob["name"] == "file_type":
|
||||
representation = knob["value"]
|
||||
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
try:
|
||||
data.update({
|
||||
"app": host_name,
|
||||
"imageio_writes": imageio_writes,
|
||||
"representation": representation,
|
||||
})
|
||||
anatomy_filled = format_anatomy(data)
|
||||
|
||||
except Exception as e:
|
||||
msg = "problem with resolving anatomy template: {}".format(e)
|
||||
log.error(msg)
|
||||
nuke.message(msg)
|
||||
|
||||
# build file path to workfiles
|
||||
fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/")
|
||||
fpath = data["fpath_template"].format(
|
||||
work=fdir,
|
||||
version=data["version"],
|
||||
subset=data["subset"],
|
||||
frame=data["frame"],
|
||||
ext=representation
|
||||
)
|
||||
|
||||
# create directory
|
||||
if not os.path.isdir(os.path.dirname(fpath)):
|
||||
log.warning("Path does not exist! I am creating it.")
|
||||
os.makedirs(os.path.dirname(fpath))
|
||||
|
||||
GN = nuke.createNode("Group", "name {}".format(name))
|
||||
|
||||
prev_node = None
|
||||
with GN:
|
||||
if input:
|
||||
input_name = str(input.name()).replace(" ", "")
|
||||
# if connected input node was defined
|
||||
prev_node = nuke.createNode(
|
||||
"Input", "name {}".format(input_name))
|
||||
else:
|
||||
# generic input node connected to nothing
|
||||
prev_node = nuke.createNode(
|
||||
"Input", "name {}".format("rgba"))
|
||||
prev_node.hideControlPanel()
|
||||
|
||||
# creating pre-write nodes `prenodes`
|
||||
last_prenode = create_prenodes(
|
||||
prev_node,
|
||||
prenodes,
|
||||
plugin_name,
|
||||
subset,
|
||||
**kwargs
|
||||
)
|
||||
if last_prenode:
|
||||
prev_node = last_prenode
|
||||
|
||||
# creating write node
|
||||
write_node = now_node = add_write_node(
|
||||
"inside_{}".format(name),
|
||||
fpath,
|
||||
imageio_writes["knobs"],
|
||||
**data
|
||||
)
|
||||
write_node.hideControlPanel()
|
||||
# connect to previous node
|
||||
now_node.setInput(0, prev_node)
|
||||
|
||||
# switch actual node to previous
|
||||
prev_node = now_node
|
||||
|
||||
now_node = nuke.createNode("Output", "name Output1")
|
||||
now_node.hideControlPanel()
|
||||
|
||||
# connect to previous node
|
||||
now_node.setInput(0, prev_node)
|
||||
|
||||
# imprinting group node
|
||||
set_avalon_knob_data(GN, data["avalon"])
|
||||
add_publish_knob(GN)
|
||||
add_rendering_knobs(GN, farm)
|
||||
|
||||
if review:
|
||||
add_review_knob(GN)
|
||||
|
||||
# add divider
|
||||
GN.addKnob(nuke.Text_Knob('', 'Rendering'))
|
||||
|
||||
# Add linked knobs.
|
||||
linked_knob_names = []
|
||||
|
||||
# add input linked knobs and create group only if any input
|
||||
if linked_knobs:
|
||||
linked_knob_names.append("_grp-start_")
|
||||
linked_knob_names.extend(linked_knobs)
|
||||
linked_knob_names.append("_grp-end_")
|
||||
|
||||
linked_knob_names.append("Render")
|
||||
|
||||
for _k_name in linked_knob_names:
|
||||
if "_grp-start_" in _k_name:
|
||||
knob = nuke.Tab_Knob(
|
||||
"rnd_attr", "Rendering attributes", nuke.TABBEGINCLOSEDGROUP)
|
||||
GN.addKnob(knob)
|
||||
elif "_grp-end_" in _k_name:
|
||||
knob = nuke.Tab_Knob(
|
||||
"rnd_attr_end", "Rendering attributes", nuke.TABENDGROUP)
|
||||
GN.addKnob(knob)
|
||||
else:
|
||||
if "___" in _k_name:
|
||||
# add divider
|
||||
GN.addKnob(nuke.Text_Knob(""))
|
||||
else:
|
||||
# add linked knob by _k_name
|
||||
link = nuke.Link_Knob("")
|
||||
link.makeLink(write_node.name(), _k_name)
|
||||
link.setName(_k_name)
|
||||
|
||||
# make render
|
||||
if "Render" in _k_name:
|
||||
link.setLabel("Render Local")
|
||||
link.setFlag(0x1000)
|
||||
GN.addKnob(link)
|
||||
|
||||
# adding write to read button
|
||||
add_button_write_to_read(GN)
|
||||
|
||||
# adding write to read button
|
||||
add_button_clear_rendered(GN, os.path.dirname(fpath))
|
||||
|
||||
# Deadline tab.
|
||||
add_deadline_tab(GN)
|
||||
|
||||
# open the our Tab as default
|
||||
GN[_NODE_TAB_NAME].setFlag(0)
|
||||
|
||||
# set tile color
|
||||
tile_color = next(
|
||||
iter(
|
||||
k["value"] for k in imageio_writes["knobs"]
|
||||
if "tile_color" in k["name"]
|
||||
), [255, 0, 0, 255]
|
||||
)
|
||||
GN["tile_color"].setValue(
|
||||
color_gui_to_int(tile_color))
|
||||
|
||||
# finally add knob overrides
|
||||
set_node_knobs_from_settings(GN, knob_overrides, **kwargs)
|
||||
|
||||
return GN
|
||||
|
||||
|
||||
def create_write_node_legacy(
|
||||
name, data, input=None, prenodes=None,
|
||||
review=True, linked_knobs=None, farm=True
|
||||
):
|
||||
''' Creating write node which is group node
|
||||
|
||||
Arguments:
|
||||
|
|
@ -858,8 +1246,14 @@ def create_write_node(name, data, input=None, prenodes=None,
|
|||
Return:
|
||||
node (obj): group node with avalon data as Knobs
|
||||
'''
|
||||
knob_overrides = data.get("knobs", [])
|
||||
nodeclass = data["nodeclass"]
|
||||
creator = data["creator"]
|
||||
subset = data["subset"]
|
||||
|
||||
imageio_writes = get_created_node_imageio_setting(**data)
|
||||
imageio_writes = get_created_node_imageio_setting_legacy(
|
||||
nodeclass, creator, subset
|
||||
)
|
||||
for knob in imageio_writes["knobs"]:
|
||||
if knob["name"] == "file_type":
|
||||
representation = knob["value"]
|
||||
|
|
@ -981,7 +1375,8 @@ def create_write_node(name, data, input=None, prenodes=None,
|
|||
prev_node = now_node
|
||||
|
||||
# creating write node
|
||||
write_node = now_node = add_write_node(
|
||||
|
||||
write_node = now_node = add_write_node_legacy(
|
||||
"inside_{}".format(name),
|
||||
**_data
|
||||
)
|
||||
|
|
@ -1061,9 +1456,106 @@ def create_write_node(name, data, input=None, prenodes=None,
|
|||
tile_color = _data.get("tile_color", "0xff0000ff")
|
||||
GN["tile_color"].setValue(tile_color)
|
||||
|
||||
# overrie knob values from settings
|
||||
for knob in knob_overrides:
|
||||
knob_type = knob["type"]
|
||||
knob_name = knob["name"]
|
||||
knob_value = knob["value"]
|
||||
if knob_name not in GN.knobs():
|
||||
continue
|
||||
if not knob_value:
|
||||
continue
|
||||
|
||||
# set correctly knob types
|
||||
if knob_type == "string":
|
||||
knob_value = str(knob_value)
|
||||
if knob_type == "number":
|
||||
knob_value = int(knob_value)
|
||||
if knob_type == "decimal_number":
|
||||
knob_value = float(knob_value)
|
||||
if knob_type == "bool":
|
||||
knob_value = bool(knob_value)
|
||||
if knob_type in ["2d_vector", "3d_vector"]:
|
||||
knob_value = list(knob_value)
|
||||
|
||||
GN[knob_name].setValue(knob_value)
|
||||
|
||||
return GN
|
||||
|
||||
|
||||
def set_node_knobs_from_settings(node, knob_settings, **kwargs):
|
||||
""" Overriding knob values from settings
|
||||
|
||||
Using `schema_nuke_knob_inputs` for knob type definitions.
|
||||
|
||||
Args:
|
||||
node (nuke.Node): nuke node
|
||||
knob_settings (list): list of dict. Keys are `type`, `name`, `value`
|
||||
kwargs (dict)[optional]: keys for formatable knob settings
|
||||
"""
|
||||
for knob in knob_settings:
|
||||
log.debug("__ knob: {}".format(pformat(knob)))
|
||||
knob_type = knob["type"]
|
||||
knob_name = knob["name"]
|
||||
|
||||
if knob_name not in node.knobs():
|
||||
continue
|
||||
|
||||
# first deal with formatable knob settings
|
||||
if knob_type == "formatable":
|
||||
template = knob["template"]
|
||||
to_type = knob["to_type"]
|
||||
try:
|
||||
_knob_value = template.format(
|
||||
**kwargs
|
||||
)
|
||||
log.debug("__ knob_value0: {}".format(_knob_value))
|
||||
except KeyError as msg:
|
||||
log.warning("__ msg: {}".format(msg))
|
||||
raise KeyError(msg)
|
||||
|
||||
# convert value to correct type
|
||||
if to_type == "2d_vector":
|
||||
knob_value = _knob_value.split(";").split(",")
|
||||
else:
|
||||
knob_value = _knob_value
|
||||
|
||||
knob_type = to_type
|
||||
|
||||
else:
|
||||
knob_value = knob["value"]
|
||||
|
||||
if not knob_value:
|
||||
continue
|
||||
|
||||
# first convert string types to string
|
||||
# just to ditch unicode
|
||||
if isinstance(knob_value, six.text_type):
|
||||
knob_value = str(knob_value)
|
||||
|
||||
# set correctly knob types
|
||||
if knob_type == "bool":
|
||||
knob_value = bool(knob_value)
|
||||
elif knob_type == "decimal_number":
|
||||
knob_value = float(knob_value)
|
||||
elif knob_type == "number":
|
||||
knob_value = int(knob_value)
|
||||
elif knob_type == "text":
|
||||
knob_value = knob_value
|
||||
elif knob_type == "color_gui":
|
||||
knob_value = color_gui_to_int(knob_value)
|
||||
elif knob_type in ["2d_vector", "3d_vector", "color"]:
|
||||
knob_value = [float(v) for v in knob_value]
|
||||
|
||||
node[knob_name].setValue(knob_value)
|
||||
|
||||
|
||||
def color_gui_to_int(color_gui):
|
||||
hex_value = (
|
||||
"0x{0:0>2x}{1:0>2x}{2:0>2x}{3:0>2x}").format(*color_gui)
|
||||
return int(hex_value, 16)
|
||||
|
||||
|
||||
def add_rendering_knobs(node, farm=True):
|
||||
''' Adds additional rendering knobs to given node
|
||||
|
||||
|
|
@ -1364,15 +1856,11 @@ class WorkfileSettings(object):
|
|||
if avalon_knob_data.get("families"):
|
||||
families.append(avalon_knob_data.get("families"))
|
||||
|
||||
data_preset = {
|
||||
"nodeclass": avalon_knob_data["family"],
|
||||
"families": families,
|
||||
"creator": avalon_knob_data["creator"],
|
||||
"subset": avalon_knob_data["subset"]
|
||||
}
|
||||
|
||||
nuke_imageio_writes = get_created_node_imageio_setting(
|
||||
**data_preset)
|
||||
nuke_imageio_writes = get_imageio_node_setting(
|
||||
node_class=avalon_knob_data["family"],
|
||||
plugin_name=avalon_knob_data["creator"],
|
||||
subset=avalon_knob_data["subset"]
|
||||
)
|
||||
|
||||
log.debug("nuke_imageio_writes: `{}`".format(nuke_imageio_writes))
|
||||
|
||||
|
|
@ -1687,17 +2175,13 @@ def get_write_node_template_attr(node):
|
|||
|
||||
'''
|
||||
# get avalon data from node
|
||||
data = {"avalon": read_avalon_data(node)}
|
||||
|
||||
data_preset = {
|
||||
"nodeclass": data["avalon"]["family"],
|
||||
"families": [data["avalon"]["families"]],
|
||||
"creator": data["avalon"]["creator"],
|
||||
"subset": data["avalon"]["subset"]
|
||||
}
|
||||
|
||||
avalon_knob_data = read_avalon_data(node)
|
||||
# get template data
|
||||
nuke_imageio_writes = get_created_node_imageio_setting(**data_preset)
|
||||
nuke_imageio_writes = get_imageio_node_setting(
|
||||
node_class=avalon_knob_data["family"],
|
||||
plugin_name=avalon_knob_data["creator"],
|
||||
subset=avalon_knob_data["subset"]
|
||||
)
|
||||
|
||||
# collecting correct data
|
||||
correct_data = OrderedDict({
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ from .lib import (
|
|||
reset_selection,
|
||||
maintained_selection,
|
||||
set_avalon_knob_data,
|
||||
add_publish_knob
|
||||
add_publish_knob,
|
||||
get_nuke_imageio_settings
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -27,9 +28,6 @@ class OpenPypeCreator(LegacyCreator):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OpenPypeCreator, self).__init__(*args, **kwargs)
|
||||
self.presets = get_current_project_settings()["nuke"]["create"].get(
|
||||
self.__class__.__name__, {}
|
||||
)
|
||||
if check_subsetname_exists(
|
||||
nuke.allNodes(),
|
||||
self.data["subset"]):
|
||||
|
|
@ -605,6 +603,8 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
family = "render"
|
||||
icon = "sign-out"
|
||||
defaults = ["Main", "Mask"]
|
||||
knobs = []
|
||||
prenodes = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AbstractWriteRender, self).__init__(*args, **kwargs)
|
||||
|
|
@ -672,7 +672,8 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
"nodeclass": self.n_class,
|
||||
"families": [self.family],
|
||||
"avalon": self.data,
|
||||
"subset": self.data["subset"]
|
||||
"subset": self.data["subset"],
|
||||
"knobs": self.knobs
|
||||
}
|
||||
|
||||
# add creator data
|
||||
|
|
@ -680,21 +681,12 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
self.data.update(creator_data)
|
||||
write_data.update(creator_data)
|
||||
|
||||
if self.presets.get('fpath_template'):
|
||||
self.log.info("Adding template path from preset")
|
||||
write_data.update(
|
||||
{"fpath_template": self.presets["fpath_template"]}
|
||||
)
|
||||
else:
|
||||
self.log.info("Adding template path from plugin")
|
||||
write_data.update({
|
||||
"fpath_template":
|
||||
("{work}/" + self.family + "s/nuke/{subset}"
|
||||
"/{subset}.{frame}.{ext}")})
|
||||
|
||||
write_node = self._create_write_node(selected_node,
|
||||
inputs, outputs,
|
||||
write_data)
|
||||
write_node = self._create_write_node(
|
||||
selected_node,
|
||||
inputs,
|
||||
outputs,
|
||||
write_data
|
||||
)
|
||||
|
||||
# relinking to collected connections
|
||||
for i, input in enumerate(inputs):
|
||||
|
|
@ -709,6 +701,28 @@ class AbstractWriteRender(OpenPypeCreator):
|
|||
|
||||
return write_node
|
||||
|
||||
def is_legacy(self):
|
||||
"""Check if it needs to run legacy code
|
||||
|
||||
In case where `type` key is missing in singe
|
||||
knob it is legacy project anatomy.
|
||||
|
||||
Returns:
|
||||
bool: True if legacy
|
||||
"""
|
||||
imageio_nodes = get_nuke_imageio_settings()["nodes"]
|
||||
node = imageio_nodes["requiredNodes"][0]
|
||||
if "type" not in node["knobs"][0]:
|
||||
# if type is not yet in project anatomy
|
||||
return True
|
||||
elif next(iter(
|
||||
_k for _k in node["knobs"]
|
||||
if _k.get("type") == "__legacy__"
|
||||
), None):
|
||||
# in case someone re-saved anatomy
|
||||
# with old configuration
|
||||
return True
|
||||
|
||||
@abstractmethod
|
||||
def _create_write_node(self, selected_node, inputs, outputs, write_data):
|
||||
"""Family dependent implementation of Write node creation
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import nuke
|
||||
|
||||
from openpype.hosts.nuke.api import plugin
|
||||
from openpype.hosts.nuke.api.lib import create_write_node
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
create_write_node, create_write_node_legacy)
|
||||
|
||||
|
||||
class CreateWritePrerender(plugin.AbstractWriteRender):
|
||||
|
|
@ -12,22 +13,37 @@ class CreateWritePrerender(plugin.AbstractWriteRender):
|
|||
n_class = "Write"
|
||||
family = "prerender"
|
||||
icon = "sign-out"
|
||||
|
||||
# settings
|
||||
fpath_template = "{work}/render/nuke/{subset}/{subset}.{frame}.{ext}"
|
||||
defaults = ["Key01", "Bg01", "Fg01", "Branch01", "Part01"]
|
||||
reviewable = False
|
||||
use_range_limit = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateWritePrerender, self).__init__(*args, **kwargs)
|
||||
|
||||
def _create_write_node(self, selected_node, inputs, outputs, write_data):
|
||||
reviewable = self.presets.get("reviewable")
|
||||
write_node = create_write_node(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
prenodes=[],
|
||||
review=reviewable,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"])
|
||||
# add fpath_template
|
||||
write_data["fpath_template"] = self.fpath_template
|
||||
write_data["use_range_limit"] = self.use_range_limit
|
||||
|
||||
return write_node
|
||||
if not self.is_legacy():
|
||||
return create_write_node(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
review=self.reviewable,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"]
|
||||
)
|
||||
else:
|
||||
return create_write_node_legacy(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
review=self.reviewable,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"]
|
||||
)
|
||||
|
||||
def _modify_write_node(self, write_node):
|
||||
# open group node
|
||||
|
|
@ -38,7 +54,7 @@ class CreateWritePrerender(plugin.AbstractWriteRender):
|
|||
w_node = n
|
||||
write_node.end()
|
||||
|
||||
if self.presets.get("use_range_limit"):
|
||||
if self.use_range_limit:
|
||||
w_node["use_limit"].setValue(True)
|
||||
w_node["first"].setValue(nuke.root()["first_frame"].value())
|
||||
w_node["last"].setValue(nuke.root()["last_frame"].value())
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import nuke
|
||||
|
||||
from openpype.hosts.nuke.api import plugin
|
||||
from openpype.hosts.nuke.api.lib import create_write_node
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
create_write_node, create_write_node_legacy)
|
||||
|
||||
|
||||
class CreateWriteRender(plugin.AbstractWriteRender):
|
||||
|
|
@ -12,12 +13,36 @@ class CreateWriteRender(plugin.AbstractWriteRender):
|
|||
n_class = "Write"
|
||||
family = "render"
|
||||
icon = "sign-out"
|
||||
|
||||
# settings
|
||||
fpath_template = "{work}/render/nuke/{subset}/{subset}.{frame}.{ext}"
|
||||
defaults = ["Main", "Mask"]
|
||||
prenodes = {
|
||||
"Reformat01": {
|
||||
"nodeclass": "Reformat",
|
||||
"dependent": None,
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "resize",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "black_outside",
|
||||
"value": True
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateWriteRender, self).__init__(*args, **kwargs)
|
||||
|
||||
def _create_write_node(self, selected_node, inputs, outputs, write_data):
|
||||
# add fpath_template
|
||||
write_data["fpath_template"] = self.fpath_template
|
||||
|
||||
# add reformat node to cut off all outside of format bounding box
|
||||
# get width and height
|
||||
try:
|
||||
|
|
@ -26,25 +51,36 @@ class CreateWriteRender(plugin.AbstractWriteRender):
|
|||
actual_format = nuke.root().knob('format').value()
|
||||
width, height = (actual_format.width(), actual_format.height())
|
||||
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "Reformat01",
|
||||
"class": "Reformat",
|
||||
"knobs": [
|
||||
("resize", 0),
|
||||
("black_outside", 1),
|
||||
],
|
||||
"dependent": None
|
||||
}
|
||||
]
|
||||
if not self.is_legacy():
|
||||
return create_write_node(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
prenodes=self.prenodes,
|
||||
**{
|
||||
"width": width,
|
||||
"height": height
|
||||
}
|
||||
)
|
||||
else:
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "Reformat01",
|
||||
"class": "Reformat",
|
||||
"knobs": [
|
||||
("resize", 0),
|
||||
("black_outside", 1),
|
||||
],
|
||||
"dependent": None
|
||||
}
|
||||
]
|
||||
|
||||
write_node = create_write_node(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
prenodes=_prenodes)
|
||||
|
||||
return write_node
|
||||
return create_write_node_legacy(
|
||||
self.data["subset"],
|
||||
write_data,
|
||||
input=selected_node,
|
||||
prenodes=_prenodes
|
||||
)
|
||||
|
||||
def _modify_write_node(self, write_node):
|
||||
return write_node
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import nuke
|
||||
|
||||
from openpype.hosts.nuke.api import plugin
|
||||
from openpype.hosts.nuke.api.lib import create_write_node
|
||||
from openpype.hosts.nuke.api.lib import (
|
||||
create_write_node, create_write_node_legacy)
|
||||
|
||||
|
||||
class CreateWriteStill(plugin.AbstractWriteRender):
|
||||
|
|
@ -12,42 +13,69 @@ class CreateWriteStill(plugin.AbstractWriteRender):
|
|||
n_class = "Write"
|
||||
family = "still"
|
||||
icon = "image"
|
||||
|
||||
# settings
|
||||
fpath_template = "{work}/render/nuke/{subset}/{subset}.{ext}"
|
||||
defaults = [
|
||||
"ImageFrame{:0>4}".format(nuke.frame()),
|
||||
"MPFrame{:0>4}".format(nuke.frame()),
|
||||
"LayoutFrame{:0>4}".format(nuke.frame())
|
||||
"ImageFrame",
|
||||
"MPFrame",
|
||||
"LayoutFrame"
|
||||
]
|
||||
prenodes = {
|
||||
"FrameHold01": {
|
||||
"nodeclass": "FrameHold",
|
||||
"dependent": None,
|
||||
"knobs": [
|
||||
{
|
||||
"type": "formatable",
|
||||
"name": "first_frame",
|
||||
"template": "{frame}",
|
||||
"to_type": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateWriteStill, self).__init__(*args, **kwargs)
|
||||
|
||||
def _create_write_node(self, selected_node, inputs, outputs, write_data):
|
||||
# explicitly reset template to 'renders', not same as other 2 writes
|
||||
write_data.update({
|
||||
"fpath_template": (
|
||||
"{work}/renders/nuke/{subset}/{subset}.{ext}")})
|
||||
# add fpath_template
|
||||
write_data["fpath_template"] = self.fpath_template
|
||||
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "FrameHold01",
|
||||
"class": "FrameHold",
|
||||
"knobs": [
|
||||
("first_frame", nuke.frame())
|
||||
],
|
||||
"dependent": None
|
||||
}
|
||||
]
|
||||
|
||||
write_node = create_write_node(
|
||||
self.name,
|
||||
write_data,
|
||||
input=selected_node,
|
||||
review=False,
|
||||
prenodes=_prenodes,
|
||||
farm=False,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"])
|
||||
|
||||
return write_node
|
||||
if not self.is_legacy():
|
||||
return create_write_node(
|
||||
self.name,
|
||||
write_data,
|
||||
input=selected_node,
|
||||
review=False,
|
||||
prenodes=self.prenodes,
|
||||
farm=False,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"],
|
||||
**{
|
||||
"frame": nuke.frame()
|
||||
}
|
||||
)
|
||||
else:
|
||||
_prenodes = [
|
||||
{
|
||||
"name": "FrameHold01",
|
||||
"class": "FrameHold",
|
||||
"knobs": [
|
||||
("first_frame", nuke.frame())
|
||||
],
|
||||
"dependent": None
|
||||
}
|
||||
]
|
||||
return create_write_node_legacy(
|
||||
self.name,
|
||||
write_data,
|
||||
input=selected_node,
|
||||
review=False,
|
||||
prenodes=_prenodes,
|
||||
farm=False,
|
||||
linked_knobs=["channels", "___", "first", "last", "use_limit"]
|
||||
)
|
||||
|
||||
def _modify_write_node(self, write_node):
|
||||
write_node.begin()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ log = Logger().get_logger(__name__)
|
|||
|
||||
|
||||
class SetFrameRangeLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range excluding pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
@ -43,7 +43,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
|
|||
|
||||
|
||||
class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
"""Set frame range including pre- and post-handles"""
|
||||
|
||||
families = ["animation",
|
||||
"camera",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,16 @@ class PSItem(object):
|
|||
color_code = attr.ib(default=None) # color code of layer
|
||||
instance_id = attr.ib(default=None)
|
||||
|
||||
@property
|
||||
def clean_name(self):
|
||||
"""Returns layer name without publish icon highlight
|
||||
|
||||
Returns:
|
||||
(str)
|
||||
"""
|
||||
return (self.name.replace(PhotoshopServerStub.PUBLISH_ICON, '')
|
||||
.replace(PhotoshopServerStub.LOADED_ICON, ''))
|
||||
|
||||
|
||||
class PhotoshopServerStub:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import pyblish.api
|
|||
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.hosts.photoshop import api as photoshop
|
||||
from openpype.settings import get_project_settings
|
||||
|
||||
|
||||
class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
||||
|
|
@ -49,6 +50,12 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
asset_name = context.data["asset"]
|
||||
task_name = context.data["task"]
|
||||
variant = context.data["variant"]
|
||||
project_name = context.data["projectEntity"]["name"]
|
||||
|
||||
naming_conventions = get_project_settings(project_name).get(
|
||||
"photoshop", {}).get(
|
||||
"publish", {}).get(
|
||||
"ValidateNaming", {})
|
||||
|
||||
stub = photoshop.stub()
|
||||
layers = stub.get_layers()
|
||||
|
|
@ -83,6 +90,9 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
subset = resolved_subset_template.format(
|
||||
**prepare_template_data(fill_pairs))
|
||||
|
||||
subset = self._clean_subset_name(stub, naming_conventions,
|
||||
subset, layer)
|
||||
|
||||
if subset in existing_subset_names:
|
||||
self.log.info(
|
||||
"Subset {} already created, skipping.".format(subset))
|
||||
|
|
@ -141,6 +151,7 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
instance.data["task"] = task_name
|
||||
instance.data["subset"] = subset
|
||||
instance.data["layer"] = layer
|
||||
instance.data["families"] = []
|
||||
|
||||
return instance
|
||||
|
||||
|
|
@ -186,3 +197,21 @@ class CollectColorCodedInstances(pyblish.api.ContextPlugin):
|
|||
self.log.debug("resolved_subset_template {}".format(
|
||||
resolved_subset_template))
|
||||
return family, resolved_subset_template
|
||||
|
||||
def _clean_subset_name(self, stub, naming_conventions, subset, layer):
|
||||
"""Cleans invalid characters from subset name and layer name."""
|
||||
if re.search(naming_conventions["invalid_chars"], subset):
|
||||
subset = re.sub(
|
||||
naming_conventions["invalid_chars"],
|
||||
naming_conventions["replace_char"],
|
||||
subset
|
||||
)
|
||||
layer_name = re.sub(
|
||||
naming_conventions["invalid_chars"],
|
||||
naming_conventions["replace_char"],
|
||||
layer.clean_name
|
||||
)
|
||||
layer.name = layer_name
|
||||
stub.rename_layer(layer.id, layer_name)
|
||||
|
||||
return subset
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ class ValidateNamingRepair(pyblish.api.Action):
|
|||
|
||||
layer_name = re.sub(invalid_chars,
|
||||
replace_char,
|
||||
current_layer_state.name)
|
||||
current_layer_state.clean_name)
|
||||
layer_name = stub.PUBLISH_ICON + layer_name
|
||||
|
||||
stub.rename_layer(current_layer_state.id, layer_name)
|
||||
|
||||
|
|
@ -73,13 +74,17 @@ class ValidateNaming(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
help_msg = ' Use Repair action (A) in Pyblish to fix it.'
|
||||
msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"],
|
||||
help_msg)
|
||||
|
||||
formatting_data = {"msg": msg}
|
||||
if re.search(self.invalid_chars, instance.data["name"]):
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data)
|
||||
layer = instance.data.get("layer")
|
||||
if layer:
|
||||
msg = "Name \"{}\" is not allowed.{}".format(layer.clean_name,
|
||||
help_msg)
|
||||
|
||||
formatting_data = {"msg": msg}
|
||||
if re.search(self.invalid_chars, layer.clean_name):
|
||||
raise PublishXmlValidationError(self, msg,
|
||||
formatting_data=formatting_data
|
||||
)
|
||||
|
||||
msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"],
|
||||
help_msg)
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
import copy
|
||||
import pyblish.api
|
||||
from pprint import pformat
|
||||
|
||||
|
||||
class CollectBatchInstances(pyblish.api.InstancePlugin):
|
||||
"""Collect all available instances for batch publish."""
|
||||
|
||||
label = "Collect Batch Instances"
|
||||
order = pyblish.api.CollectorOrder + 0.489
|
||||
hosts = ["standalonepublisher"]
|
||||
families = ["background_batch"]
|
||||
|
||||
# presets
|
||||
default_subset_task = {
|
||||
"background_batch": "background"
|
||||
}
|
||||
subsets = {
|
||||
"background_batch": {
|
||||
"backgroundLayout": {
|
||||
"task": "background",
|
||||
"family": "backgroundLayout"
|
||||
},
|
||||
"backgroundComp": {
|
||||
"task": "background",
|
||||
"family": "backgroundComp"
|
||||
},
|
||||
"workfileBackground": {
|
||||
"task": "background",
|
||||
"family": "workfile"
|
||||
}
|
||||
}
|
||||
}
|
||||
unchecked_by_default = []
|
||||
|
||||
def process(self, instance):
|
||||
context = instance.context
|
||||
asset_name = instance.data["asset"]
|
||||
family = instance.data["family"]
|
||||
|
||||
default_task_name = self.default_subset_task.get(family)
|
||||
for subset_name, subset_data in self.subsets[family].items():
|
||||
instance_name = f"{asset_name}_{subset_name}"
|
||||
task_name = subset_data.get("task") or default_task_name
|
||||
|
||||
# create new instance
|
||||
new_instance = context.create_instance(instance_name)
|
||||
|
||||
# add original instance data except name key
|
||||
for key, value in instance.data.items():
|
||||
if key not in ["name"]:
|
||||
# Make sure value is copy since value may be object which
|
||||
# can be shared across all new created objects
|
||||
new_instance.data[key] = copy.deepcopy(value)
|
||||
|
||||
# add subset data from preset
|
||||
new_instance.data.update(subset_data)
|
||||
|
||||
new_instance.data["label"] = instance_name
|
||||
new_instance.data["subset"] = subset_name
|
||||
new_instance.data["task"] = task_name
|
||||
|
||||
if subset_name in self.unchecked_by_default:
|
||||
new_instance.data["publish"] = False
|
||||
|
||||
self.log.info(f"Created new instance: {instance_name}")
|
||||
self.log.debug(f"_ inst_data: {pformat(new_instance.data)}")
|
||||
|
||||
# delete original instance
|
||||
context.remove(instance)
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
import os
|
||||
import json
|
||||
import copy
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import legacy_io
|
||||
|
||||
PSDImage = None
|
||||
|
||||
|
||||
class ExtractBGForComp(openpype.api.Extractor):
|
||||
label = "Extract Background for Compositing"
|
||||
families = ["backgroundComp"]
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
new_instance_family = "background"
|
||||
|
||||
# Presetable
|
||||
allowed_group_names = [
|
||||
"OL", "BG", "MG", "FG", "SB", "UL", "SKY", "Field Guide", "Field_Guide",
|
||||
"ANIM"
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
# Check if python module `psd_tools` is installed
|
||||
try:
|
||||
global PSDImage
|
||||
from psd_tools import PSDImage
|
||||
except Exception:
|
||||
raise AssertionError(
|
||||
"BUG: Python module `psd-tools` is not installed!"
|
||||
)
|
||||
|
||||
self.allowed_group_names = [
|
||||
name.lower()
|
||||
for name in self.allowed_group_names
|
||||
]
|
||||
|
||||
self.redo_global_plugins(instance)
|
||||
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
self.log.info("There are no representations on instance.")
|
||||
return
|
||||
|
||||
if not instance.data.get("transfers"):
|
||||
instance.data["transfers"] = []
|
||||
|
||||
# Prepare staging dir
|
||||
staging_dir = self.staging_dir(instance)
|
||||
if not os.path.exists(staging_dir):
|
||||
os.makedirs(staging_dir)
|
||||
|
||||
for repre in tuple(repres):
|
||||
# Skip all files without .psd extension
|
||||
repre_ext = repre["ext"].lower()
|
||||
if repre_ext.startswith("."):
|
||||
repre_ext = repre_ext[1:]
|
||||
|
||||
if repre_ext != "psd":
|
||||
continue
|
||||
|
||||
# Prepare publish dir for transfers
|
||||
publish_dir = instance.data["publishDir"]
|
||||
|
||||
# Prepare json filepath where extracted metadata are stored
|
||||
json_filename = "{}.json".format(instance.name)
|
||||
json_full_path = os.path.join(staging_dir, json_filename)
|
||||
|
||||
self.log.debug(f"`staging_dir` is \"{staging_dir}\"")
|
||||
|
||||
# Prepare new repre data
|
||||
new_repre = {
|
||||
"name": "json",
|
||||
"ext": "json",
|
||||
"files": json_filename,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
|
||||
# TODO add check of list
|
||||
psd_filename = repre["files"]
|
||||
psd_folder_path = repre["stagingDir"]
|
||||
psd_filepath = os.path.join(psd_folder_path, psd_filename)
|
||||
self.log.debug(f"psd_filepath: \"{psd_filepath}\"")
|
||||
psd_object = PSDImage.open(psd_filepath)
|
||||
|
||||
json_data, transfers = self.export_compositing_images(
|
||||
psd_object, staging_dir, publish_dir
|
||||
)
|
||||
self.log.info("Json file path: {}".format(json_full_path))
|
||||
with open(json_full_path, "w") as json_filestream:
|
||||
json.dump(json_data, json_filestream, indent=4)
|
||||
|
||||
instance.data["transfers"].extend(transfers)
|
||||
instance.data["representations"].remove(repre)
|
||||
instance.data["representations"].append(new_repre)
|
||||
|
||||
def export_compositing_images(self, psd_object, output_dir, publish_dir):
|
||||
json_data = {
|
||||
"__schema_version__": 1,
|
||||
"children": []
|
||||
}
|
||||
transfers = []
|
||||
for main_idx, main_layer in enumerate(psd_object):
|
||||
if (
|
||||
not main_layer.is_visible()
|
||||
or main_layer.name.lower() not in self.allowed_group_names
|
||||
or not main_layer.is_group
|
||||
):
|
||||
continue
|
||||
|
||||
export_layers = []
|
||||
layers_idx = 0
|
||||
for layer in main_layer:
|
||||
# TODO this way may be added also layers next to "ADJ"
|
||||
if layer.name.lower() == "adj":
|
||||
for _layer in layer:
|
||||
export_layers.append((layers_idx, _layer))
|
||||
layers_idx += 1
|
||||
|
||||
else:
|
||||
export_layers.append((layers_idx, layer))
|
||||
layers_idx += 1
|
||||
|
||||
if not export_layers:
|
||||
continue
|
||||
|
||||
main_layer_data = {
|
||||
"index": main_idx,
|
||||
"name": main_layer.name,
|
||||
"children": []
|
||||
}
|
||||
|
||||
for layer_idx, layer in export_layers:
|
||||
has_size = layer.width > 0 and layer.height > 0
|
||||
if not has_size:
|
||||
self.log.debug((
|
||||
"Skipping layer \"{}\" because does "
|
||||
"not have any content."
|
||||
).format(layer.name))
|
||||
continue
|
||||
|
||||
main_layer_name = main_layer.name.replace(" ", "_")
|
||||
layer_name = layer.name.replace(" ", "_")
|
||||
|
||||
filename = "{:0>2}_{}_{:0>2}_{}.png".format(
|
||||
main_idx + 1, main_layer_name, layer_idx + 1, layer_name
|
||||
)
|
||||
layer_data = {
|
||||
"index": layer_idx,
|
||||
"name": layer.name,
|
||||
"filename": filename
|
||||
}
|
||||
output_filepath = os.path.join(output_dir, filename)
|
||||
dst_filepath = os.path.join(publish_dir, filename)
|
||||
transfers.append((output_filepath, dst_filepath))
|
||||
|
||||
pil_object = layer.composite(viewport=psd_object.viewbox)
|
||||
pil_object.save(output_filepath, "PNG")
|
||||
|
||||
main_layer_data["children"].append(layer_data)
|
||||
|
||||
if main_layer_data["children"]:
|
||||
json_data["children"].append(main_layer_data)
|
||||
|
||||
return json_data, transfers
|
||||
|
||||
def redo_global_plugins(self, instance):
|
||||
# TODO do this in collection phase
|
||||
# Copy `families` and check if `family` is not in current families
|
||||
families = instance.data.get("families") or list()
|
||||
if families:
|
||||
families = list(set(families))
|
||||
|
||||
if self.new_instance_family in families:
|
||||
families.remove(self.new_instance_family)
|
||||
|
||||
self.log.debug(
|
||||
"Setting new instance families {}".format(str(families))
|
||||
)
|
||||
instance.data["families"] = families
|
||||
|
||||
# Override instance data with new information
|
||||
instance.data["family"] = self.new_instance_family
|
||||
|
||||
subset_name = instance.data["anatomyData"]["subset"]
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
latest_version = self.find_last_version(subset_name, asset_doc)
|
||||
version_number = 1
|
||||
if latest_version is not None:
|
||||
version_number += latest_version
|
||||
|
||||
instance.data["latestVersion"] = latest_version
|
||||
instance.data["version"] = version_number
|
||||
|
||||
# Same data apply to anatomy data
|
||||
instance.data["anatomyData"].update({
|
||||
"family": self.new_instance_family,
|
||||
"version": version_number
|
||||
})
|
||||
|
||||
# Redo publish and resources dir
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
template_data.update({
|
||||
"frame": "FRAME_TEMP",
|
||||
"representation": "TEMP"
|
||||
})
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
if "folder" in anatomy.templates["publish"]:
|
||||
publish_folder = anatomy_filled["publish"]["folder"]
|
||||
else:
|
||||
publish_folder = os.path.dirname(anatomy_filled["publish"]["path"])
|
||||
|
||||
publish_folder = os.path.normpath(publish_folder)
|
||||
resources_folder = os.path.join(publish_folder, "resources")
|
||||
|
||||
instance.data["publishDir"] = publish_folder
|
||||
instance.data["resourcesDir"] = resources_folder
|
||||
|
||||
self.log.debug("publishDir: \"{}\"".format(publish_folder))
|
||||
self.log.debug("resourcesDir: \"{}\"".format(resources_folder))
|
||||
|
||||
def find_last_version(self, subset_name, asset_doc):
|
||||
subset_doc = legacy_io.find_one({
|
||||
"type": "subset",
|
||||
"name": subset_name,
|
||||
"parent": asset_doc["_id"]
|
||||
})
|
||||
|
||||
if subset_doc is None:
|
||||
self.log.debug("Subset entity does not exist yet.")
|
||||
else:
|
||||
version_doc = legacy_io.find_one(
|
||||
{
|
||||
"type": "version",
|
||||
"parent": subset_doc["_id"]
|
||||
},
|
||||
sort=[("name", -1)]
|
||||
)
|
||||
if version_doc:
|
||||
return int(version_doc["name"])
|
||||
return None
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
import os
|
||||
import copy
|
||||
import json
|
||||
|
||||
import pyblish.api
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import legacy_io
|
||||
|
||||
PSDImage = None
|
||||
|
||||
|
||||
class ExtractBGMainGroups(openpype.api.Extractor):
|
||||
label = "Extract Background Layout"
|
||||
order = pyblish.api.ExtractorOrder + 0.02
|
||||
families = ["backgroundLayout"]
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
new_instance_family = "background"
|
||||
|
||||
# Presetable
|
||||
allowed_group_names = [
|
||||
"OL", "BG", "MG", "FG", "UL", "SB", "SKY", "Field Guide", "Field_Guide",
|
||||
"ANIM"
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
# Check if python module `psd_tools` is installed
|
||||
try:
|
||||
global PSDImage
|
||||
from psd_tools import PSDImage
|
||||
except Exception:
|
||||
raise AssertionError(
|
||||
"BUG: Python module `psd-tools` is not installed!"
|
||||
)
|
||||
|
||||
self.allowed_group_names = [
|
||||
name.lower()
|
||||
for name in self.allowed_group_names
|
||||
]
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
self.log.info("There are no representations on instance.")
|
||||
return
|
||||
|
||||
self.redo_global_plugins(instance)
|
||||
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
self.log.info("There are no representations on instance.")
|
||||
return
|
||||
|
||||
if not instance.data.get("transfers"):
|
||||
instance.data["transfers"] = []
|
||||
|
||||
# Prepare staging dir
|
||||
staging_dir = self.staging_dir(instance)
|
||||
if not os.path.exists(staging_dir):
|
||||
os.makedirs(staging_dir)
|
||||
|
||||
# Prepare publish dir for transfers
|
||||
publish_dir = instance.data["publishDir"]
|
||||
|
||||
for repre in tuple(repres):
|
||||
# Skip all files without .psd extension
|
||||
repre_ext = repre["ext"].lower()
|
||||
if repre_ext.startswith("."):
|
||||
repre_ext = repre_ext[1:]
|
||||
|
||||
if repre_ext != "psd":
|
||||
continue
|
||||
|
||||
# Prepare json filepath where extracted metadata are stored
|
||||
json_filename = "{}.json".format(instance.name)
|
||||
json_full_path = os.path.join(staging_dir, json_filename)
|
||||
|
||||
self.log.debug(f"`staging_dir` is \"{staging_dir}\"")
|
||||
|
||||
# Prepare new repre data
|
||||
new_repre = {
|
||||
"name": "json",
|
||||
"ext": "json",
|
||||
"files": json_filename,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
|
||||
# TODO add check of list
|
||||
psd_filename = repre["files"]
|
||||
psd_folder_path = repre["stagingDir"]
|
||||
psd_filepath = os.path.join(psd_folder_path, psd_filename)
|
||||
self.log.debug(f"psd_filepath: \"{psd_filepath}\"")
|
||||
psd_object = PSDImage.open(psd_filepath)
|
||||
|
||||
json_data, transfers = self.export_compositing_images(
|
||||
psd_object, staging_dir, publish_dir
|
||||
)
|
||||
self.log.info("Json file path: {}".format(json_full_path))
|
||||
with open(json_full_path, "w") as json_filestream:
|
||||
json.dump(json_data, json_filestream, indent=4)
|
||||
|
||||
instance.data["transfers"].extend(transfers)
|
||||
instance.data["representations"].remove(repre)
|
||||
instance.data["representations"].append(new_repre)
|
||||
|
||||
def export_compositing_images(self, psd_object, output_dir, publish_dir):
|
||||
json_data = {
|
||||
"__schema_version__": 1,
|
||||
"children": []
|
||||
}
|
||||
output_ext = ".png"
|
||||
|
||||
to_export = []
|
||||
for layer_idx, layer in enumerate(psd_object):
|
||||
layer_name = layer.name.replace(" ", "_")
|
||||
if (
|
||||
not layer.is_visible()
|
||||
or layer_name.lower() not in self.allowed_group_names
|
||||
):
|
||||
continue
|
||||
|
||||
has_size = layer.width > 0 and layer.height > 0
|
||||
if not has_size:
|
||||
self.log.debug((
|
||||
"Skipping layer \"{}\" because does not have any content."
|
||||
).format(layer.name))
|
||||
continue
|
||||
|
||||
filebase = "{:0>2}_{}".format(layer_idx, layer_name)
|
||||
if layer_name.lower() == "anim":
|
||||
if not layer.is_group:
|
||||
self.log.warning("ANIM layer is not a group layer.")
|
||||
continue
|
||||
|
||||
children = []
|
||||
for anim_idx, anim_layer in enumerate(layer):
|
||||
anim_layer_name = anim_layer.name.replace(" ", "_")
|
||||
filename = "{}_{:0>2}_{}{}".format(
|
||||
filebase, anim_idx, anim_layer_name, output_ext
|
||||
)
|
||||
children.append({
|
||||
"index": anim_idx,
|
||||
"name": anim_layer.name,
|
||||
"filename": filename
|
||||
})
|
||||
to_export.append((anim_layer, filename))
|
||||
|
||||
json_data["children"].append({
|
||||
"index": layer_idx,
|
||||
"name": layer.name,
|
||||
"children": children
|
||||
})
|
||||
continue
|
||||
|
||||
filename = filebase + output_ext
|
||||
json_data["children"].append({
|
||||
"index": layer_idx,
|
||||
"name": layer.name,
|
||||
"filename": filename
|
||||
})
|
||||
to_export.append((layer, filename))
|
||||
|
||||
transfers = []
|
||||
for layer, filename in to_export:
|
||||
output_filepath = os.path.join(output_dir, filename)
|
||||
dst_filepath = os.path.join(publish_dir, filename)
|
||||
transfers.append((output_filepath, dst_filepath))
|
||||
|
||||
pil_object = layer.composite(viewport=psd_object.viewbox)
|
||||
pil_object.save(output_filepath, "PNG")
|
||||
|
||||
return json_data, transfers
|
||||
|
||||
def redo_global_plugins(self, instance):
|
||||
# TODO do this in collection phase
|
||||
# Copy `families` and check if `family` is not in current families
|
||||
families = instance.data.get("families") or list()
|
||||
if families:
|
||||
families = list(set(families))
|
||||
|
||||
if self.new_instance_family in families:
|
||||
families.remove(self.new_instance_family)
|
||||
|
||||
self.log.debug(
|
||||
"Setting new instance families {}".format(str(families))
|
||||
)
|
||||
instance.data["families"] = families
|
||||
|
||||
# Override instance data with new information
|
||||
instance.data["family"] = self.new_instance_family
|
||||
|
||||
subset_name = instance.data["anatomyData"]["subset"]
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
latest_version = self.find_last_version(subset_name, asset_doc)
|
||||
version_number = 1
|
||||
if latest_version is not None:
|
||||
version_number += latest_version
|
||||
|
||||
instance.data["latestVersion"] = latest_version
|
||||
instance.data["version"] = version_number
|
||||
|
||||
# Same data apply to anatomy data
|
||||
instance.data["anatomyData"].update({
|
||||
"family": self.new_instance_family,
|
||||
"version": version_number
|
||||
})
|
||||
|
||||
# Redo publish and resources dir
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
template_data.update({
|
||||
"frame": "FRAME_TEMP",
|
||||
"representation": "TEMP"
|
||||
})
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
if "folder" in anatomy.templates["publish"]:
|
||||
publish_folder = anatomy_filled["publish"]["folder"]
|
||||
else:
|
||||
publish_folder = os.path.dirname(anatomy_filled["publish"]["path"])
|
||||
|
||||
publish_folder = os.path.normpath(publish_folder)
|
||||
resources_folder = os.path.join(publish_folder, "resources")
|
||||
|
||||
instance.data["publishDir"] = publish_folder
|
||||
instance.data["resourcesDir"] = resources_folder
|
||||
|
||||
self.log.debug("publishDir: \"{}\"".format(publish_folder))
|
||||
self.log.debug("resourcesDir: \"{}\"".format(resources_folder))
|
||||
|
||||
def find_last_version(self, subset_name, asset_doc):
|
||||
subset_doc = legacy_io.find_one({
|
||||
"type": "subset",
|
||||
"name": subset_name,
|
||||
"parent": asset_doc["_id"]
|
||||
})
|
||||
|
||||
if subset_doc is None:
|
||||
self.log.debug("Subset entity does not exist yet.")
|
||||
else:
|
||||
version_doc = legacy_io.find_one(
|
||||
{
|
||||
"type": "version",
|
||||
"parent": subset_doc["_id"]
|
||||
},
|
||||
sort=[("name", -1)]
|
||||
)
|
||||
if version_doc:
|
||||
return int(version_doc["name"])
|
||||
return None
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
import os
|
||||
import copy
|
||||
import pyblish.api
|
||||
|
||||
import openpype.api
|
||||
from openpype.pipeline import legacy_io
|
||||
|
||||
PSDImage = None
|
||||
|
||||
|
||||
class ExtractImagesFromPSD(openpype.api.Extractor):
|
||||
# PLUGIN is not currently enabled because was decided to use different
|
||||
# approach
|
||||
enabled = False
|
||||
active = False
|
||||
label = "Extract Images from PSD"
|
||||
order = pyblish.api.ExtractorOrder + 0.02
|
||||
families = ["backgroundLayout"]
|
||||
hosts = ["standalonepublisher"]
|
||||
|
||||
new_instance_family = "image"
|
||||
ignored_instance_data_keys = ("name", "label", "stagingDir", "version")
|
||||
# Presetable
|
||||
allowed_group_names = [
|
||||
"OL", "BG", "MG", "FG", "UL", "SKY", "Field Guide", "Field_Guide",
|
||||
"ANIM"
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
# Check if python module `psd_tools` is installed
|
||||
try:
|
||||
global PSDImage
|
||||
from psd_tools import PSDImage
|
||||
except Exception:
|
||||
raise AssertionError(
|
||||
"BUG: Python module `psd-tools` is not installed!"
|
||||
)
|
||||
|
||||
self.allowed_group_names = [
|
||||
name.lower()
|
||||
for name in self.allowed_group_names
|
||||
]
|
||||
repres = instance.data.get("representations")
|
||||
if not repres:
|
||||
self.log.info("There are no representations on instance.")
|
||||
return
|
||||
|
||||
for repre in tuple(repres):
|
||||
# Skip all files without .psd extension
|
||||
repre_ext = repre["ext"].lower()
|
||||
if repre_ext.startswith("."):
|
||||
repre_ext = repre_ext[1:]
|
||||
|
||||
if repre_ext != "psd":
|
||||
continue
|
||||
|
||||
# TODO add check of list of "files" value
|
||||
psd_filename = repre["files"]
|
||||
psd_folder_path = repre["stagingDir"]
|
||||
psd_filepath = os.path.join(psd_folder_path, psd_filename)
|
||||
self.log.debug(f"psd_filepath: \"{psd_filepath}\"")
|
||||
psd_object = PSDImage.open(psd_filepath)
|
||||
|
||||
self.create_new_instances(instance, psd_object)
|
||||
|
||||
# Remove the instance from context
|
||||
instance.context.remove(instance)
|
||||
|
||||
def create_new_instances(self, instance, psd_object):
|
||||
asset_doc = instance.data["assetEntity"]
|
||||
for layer in psd_object:
|
||||
if (
|
||||
not layer.is_visible()
|
||||
or layer.name.lower() not in self.allowed_group_names
|
||||
):
|
||||
continue
|
||||
|
||||
has_size = layer.width > 0 and layer.height > 0
|
||||
if not has_size:
|
||||
self.log.debug((
|
||||
"Skipping layer \"{}\" because does "
|
||||
"not have any content."
|
||||
).format(layer.name))
|
||||
continue
|
||||
|
||||
layer_name = layer.name.replace(" ", "_")
|
||||
instance_name = subset_name = f"image{layer_name}"
|
||||
self.log.info(
|
||||
f"Creating new instance with name \"{instance_name}\""
|
||||
)
|
||||
new_instance = instance.context.create_instance(instance_name)
|
||||
for key, value in instance.data.items():
|
||||
if key not in self.ignored_instance_data_keys:
|
||||
new_instance.data[key] = copy.deepcopy(value)
|
||||
|
||||
new_instance.data["label"] = " ".join(
|
||||
(new_instance.data["asset"], instance_name)
|
||||
)
|
||||
|
||||
# Find latest version
|
||||
latest_version = self.find_last_version(subset_name, asset_doc)
|
||||
version_number = 1
|
||||
if latest_version is not None:
|
||||
version_number += latest_version
|
||||
|
||||
self.log.info(
|
||||
"Next version of instance \"{}\" will be {}".format(
|
||||
instance_name, version_number
|
||||
)
|
||||
)
|
||||
|
||||
# Set family and subset
|
||||
new_instance.data["family"] = self.new_instance_family
|
||||
new_instance.data["subset"] = subset_name
|
||||
new_instance.data["version"] = version_number
|
||||
new_instance.data["latestVersion"] = latest_version
|
||||
|
||||
new_instance.data["anatomyData"].update({
|
||||
"subset": subset_name,
|
||||
"family": self.new_instance_family,
|
||||
"version": version_number
|
||||
})
|
||||
|
||||
# Copy `families` and check if `family` is not in current families
|
||||
families = new_instance.data.get("families") or list()
|
||||
if families:
|
||||
families = list(set(families))
|
||||
|
||||
if self.new_instance_family in families:
|
||||
families.remove(self.new_instance_family)
|
||||
new_instance.data["families"] = families
|
||||
|
||||
# Prepare staging dir for new instance
|
||||
staging_dir = self.staging_dir(new_instance)
|
||||
|
||||
output_filename = "{}.png".format(layer_name)
|
||||
output_filepath = os.path.join(staging_dir, output_filename)
|
||||
pil_object = layer.composite(viewport=psd_object.viewbox)
|
||||
pil_object.save(output_filepath, "PNG")
|
||||
|
||||
new_repre = {
|
||||
"name": "png",
|
||||
"ext": "png",
|
||||
"files": output_filename,
|
||||
"stagingDir": staging_dir
|
||||
}
|
||||
self.log.debug(
|
||||
"Creating new representation: {}".format(new_repre)
|
||||
)
|
||||
new_instance.data["representations"] = [new_repre]
|
||||
|
||||
def find_last_version(self, subset_name, asset_doc):
|
||||
subset_doc = legacy_io.find_one({
|
||||
"type": "subset",
|
||||
"name": subset_name,
|
||||
"parent": asset_doc["_id"]
|
||||
})
|
||||
|
||||
if subset_doc is None:
|
||||
self.log.debug("Subset entity does not exist yet.")
|
||||
else:
|
||||
version_doc = legacy_io.find_one(
|
||||
{
|
||||
"type": "version",
|
||||
"parent": subset_doc["_id"]
|
||||
},
|
||||
sort=[("name", -1)]
|
||||
)
|
||||
if version_doc:
|
||||
return int(version_doc["name"])
|
||||
return None
|
||||
|
|
@ -2,7 +2,10 @@ import os
|
|||
import tempfile
|
||||
import pyblish.api
|
||||
import openpype.api
|
||||
import openpype.lib
|
||||
from openpype.lib import (
|
||||
get_ffmpeg_tool_path,
|
||||
get_ffprobe_streams,
|
||||
)
|
||||
|
||||
|
||||
class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
||||
|
|
@ -71,7 +74,7 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
full_thumbnail_path = tempfile.mkstemp(suffix=".jpg")[1]
|
||||
self.log.info("output {}".format(full_thumbnail_path))
|
||||
|
||||
ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
|
||||
ffmpeg_path = get_ffmpeg_tool_path("ffmpeg")
|
||||
|
||||
ffmpeg_args = self.ffmpeg_args or {}
|
||||
|
||||
|
|
@ -110,6 +113,13 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
|
||||
# remove thumbnail key from origin repre
|
||||
thumbnail_repre.pop("thumbnail")
|
||||
streams = get_ffprobe_streams(full_thumbnail_path)
|
||||
width = height = None
|
||||
for stream in streams:
|
||||
if "width" in stream and "height" in stream:
|
||||
width = stream["width"]
|
||||
height = stream["height"]
|
||||
break
|
||||
|
||||
filename = os.path.basename(full_thumbnail_path)
|
||||
staging_dir = staging_dir or os.path.dirname(full_thumbnail_path)
|
||||
|
|
@ -122,6 +132,9 @@ class ExtractThumbnailSP(pyblish.api.InstancePlugin):
|
|||
"stagingDir": staging_dir,
|
||||
"tags": ["thumbnail"],
|
||||
}
|
||||
if width and height:
|
||||
representation["width"] = width
|
||||
representation["height"] = height
|
||||
|
||||
# # add Delete tag when temp file was rendered
|
||||
if not is_jpeg:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ from openpype.pipeline import (
|
|||
Creator,
|
||||
CreatedInstance
|
||||
)
|
||||
from openpype.lib import (
|
||||
FileDef,
|
||||
BoolDef,
|
||||
)
|
||||
from openpype.lib import FileDef
|
||||
|
||||
from .pipeline import (
|
||||
list_instances,
|
||||
|
|
@ -43,7 +40,6 @@ class TrayPublishCreator(Creator):
|
|||
class SettingsCreator(TrayPublishCreator):
|
||||
create_allow_context_change = True
|
||||
|
||||
enable_review = False
|
||||
extensions = []
|
||||
|
||||
def collect_instances(self):
|
||||
|
|
@ -67,19 +63,15 @@ class SettingsCreator(TrayPublishCreator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
output = []
|
||||
|
||||
file_def = FileDef(
|
||||
"filepath",
|
||||
folders=False,
|
||||
extensions=self.extensions,
|
||||
allow_sequences=self.allow_sequences,
|
||||
label="Filepath",
|
||||
)
|
||||
output.append(file_def)
|
||||
if self.enable_review:
|
||||
output.append(BoolDef("review", label="Review"))
|
||||
return output
|
||||
return [
|
||||
FileDef(
|
||||
"filepath",
|
||||
folders=False,
|
||||
extensions=self.extensions,
|
||||
allow_sequences=self.allow_sequences,
|
||||
label="Filepath",
|
||||
)
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_settings(cls, item_data):
|
||||
|
|
@ -97,7 +89,6 @@ class SettingsCreator(TrayPublishCreator):
|
|||
"icon": item_data["icon"],
|
||||
"description": item_data["description"],
|
||||
"detailed_description": item_data["detailed_description"],
|
||||
"enable_review": item_data["enable_review"],
|
||||
"extensions": item_data["extensions"],
|
||||
"allow_sequences": item_data["allow_sequences"],
|
||||
"default_variants": item_data["default_variants"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import pyblish.api
|
||||
from openpype.lib import BoolDef
|
||||
from openpype.pipeline import OpenPypePyblishPluginMixin
|
||||
|
||||
|
||||
class CollectReviewFamily(
|
||||
pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin
|
||||
):
|
||||
"""Add review family."""
|
||||
|
||||
label = "Collect Review Family"
|
||||
order = pyblish.api.CollectorOrder - 0.49
|
||||
|
||||
hosts = ["traypublisher"]
|
||||
families = [
|
||||
"image",
|
||||
"render",
|
||||
"plate",
|
||||
"review"
|
||||
]
|
||||
|
||||
def process(self, instance):
|
||||
values = self.get_attr_values_from_data(instance.data)
|
||||
if values.get("add_review_family"):
|
||||
instance.data["families"].append("review")
|
||||
|
||||
@classmethod
|
||||
def get_attribute_defs(cls):
|
||||
return [
|
||||
BoolDef("add_review_family", label="Review", default=True)
|
||||
]
|
||||
|
|
@ -22,10 +22,6 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
|
|||
repres = instance.data["representations"]
|
||||
|
||||
creator_attributes = instance.data["creator_attributes"]
|
||||
|
||||
if creator_attributes.get("review"):
|
||||
instance.data["families"].append("review")
|
||||
|
||||
filepath_item = creator_attributes["filepath"]
|
||||
self.log.info(filepath_item)
|
||||
filepaths = [
|
||||
|
|
@ -34,9 +30,11 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
|
|||
]
|
||||
|
||||
instance.data["sourceFilepaths"] = filepaths
|
||||
instance.data["stagingDir"] = filepath_item["directory"]
|
||||
|
||||
filenames = filepath_item["filenames"]
|
||||
ext = os.path.splitext(filenames[0])[-1]
|
||||
_, ext = os.path.splitext(filenames[0])
|
||||
ext = ext[1:]
|
||||
if len(filenames) == 1:
|
||||
filenames = filenames[0]
|
||||
|
||||
|
|
@ -46,3 +44,7 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
|
|||
"stagingDir": filepath_item["directory"],
|
||||
"files": filenames
|
||||
})
|
||||
|
||||
self.log.debug("Created Simple Settings instance {}".format(
|
||||
instance.data
|
||||
))
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ def composite_rendered_layers(
|
|||
layer_ids_by_position[layer_position] = layer["layer_id"]
|
||||
|
||||
# Sort layer positions
|
||||
sorted_positions = tuple(sorted(layer_ids_by_position.keys()))
|
||||
sorted_positions = tuple(reversed(sorted(layer_ids_by_position.keys())))
|
||||
# Prepare variable where filepaths without any rendered content
|
||||
# - transparent will be created
|
||||
transparent_filepaths = set()
|
||||
|
|
|
|||
|
|
@ -1012,8 +1012,8 @@ class ApplicationLaunchContext:
|
|||
self.log.debug("Discovery of launch hooks started.")
|
||||
|
||||
paths = self.paths_to_launch_hooks()
|
||||
self.log.debug("Paths where will look for launch hooks:{}".format(
|
||||
"\n- ".join(paths)
|
||||
self.log.debug("Paths searched for launch hooks:\n{}".format(
|
||||
"\n".join("- {}".format(path) for path in paths)
|
||||
))
|
||||
|
||||
all_classes = {
|
||||
|
|
@ -1023,7 +1023,7 @@ class ApplicationLaunchContext:
|
|||
for path in paths:
|
||||
if not os.path.exists(path):
|
||||
self.log.info(
|
||||
"Path to launch hooks does not exists: \"{}\"".format(path)
|
||||
"Path to launch hooks does not exist: \"{}\"".format(path)
|
||||
)
|
||||
continue
|
||||
|
||||
|
|
@ -1044,13 +1044,14 @@ class ApplicationLaunchContext:
|
|||
hook = klass(self)
|
||||
if not hook.is_valid:
|
||||
self.log.debug(
|
||||
"Hook is not valid for current launch context."
|
||||
"Skipped hook invalid for current launch context: "
|
||||
"{}".format(klass.__name__)
|
||||
)
|
||||
continue
|
||||
|
||||
if inspect.isabstract(hook):
|
||||
self.log.debug("Skipped abstract hook: {}".format(
|
||||
str(hook)
|
||||
klass.__name__
|
||||
))
|
||||
continue
|
||||
|
||||
|
|
@ -1062,7 +1063,8 @@ class ApplicationLaunchContext:
|
|||
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Initialization of hook failed. {}".format(str(klass)),
|
||||
"Initialization of hook failed: "
|
||||
"{}".format(klass.__name__),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -316,6 +316,7 @@ class FileDefItem(object):
|
|||
self.is_sequence = False
|
||||
self.template = None
|
||||
self.frames = []
|
||||
self.is_empty = True
|
||||
|
||||
self.set_filenames(filenames, frames, template)
|
||||
|
||||
|
|
@ -323,7 +324,9 @@ class FileDefItem(object):
|
|||
return json.dumps(self.to_dict())
|
||||
|
||||
def __repr__(self):
|
||||
if self.is_sequence:
|
||||
if self.is_empty:
|
||||
filename = "< empty >"
|
||||
elif self.is_sequence:
|
||||
filename = self.template
|
||||
else:
|
||||
filename = self.filenames[0]
|
||||
|
|
@ -335,6 +338,9 @@ class FileDefItem(object):
|
|||
|
||||
@property
|
||||
def label(self):
|
||||
if self.is_empty:
|
||||
return None
|
||||
|
||||
if not self.is_sequence:
|
||||
return self.filenames[0]
|
||||
|
||||
|
|
@ -386,6 +392,8 @@ class FileDefItem(object):
|
|||
|
||||
@property
|
||||
def ext(self):
|
||||
if self.is_empty:
|
||||
return None
|
||||
_, ext = os.path.splitext(self.filenames[0])
|
||||
if ext:
|
||||
return ext
|
||||
|
|
@ -393,6 +401,9 @@ class FileDefItem(object):
|
|||
|
||||
@property
|
||||
def is_dir(self):
|
||||
if self.is_empty:
|
||||
return False
|
||||
|
||||
# QUESTION a better way how to define folder (in init argument?)
|
||||
if self.ext:
|
||||
return False
|
||||
|
|
@ -411,6 +422,7 @@ class FileDefItem(object):
|
|||
if is_sequence and not template:
|
||||
raise ValueError("Missing template for sequence")
|
||||
|
||||
self.is_empty = len(filenames) == 0
|
||||
self.filenames = filenames
|
||||
self.template = template
|
||||
self.frames = frames
|
||||
|
|
@ -560,11 +572,7 @@ class FileDef(AbtractAttrDef):
|
|||
# Change horizontal label
|
||||
is_label_horizontal = kwargs.get("is_label_horizontal")
|
||||
if is_label_horizontal is None:
|
||||
if single_item:
|
||||
is_label_horizontal = True
|
||||
else:
|
||||
is_label_horizontal = False
|
||||
kwargs["is_label_horizontal"] = is_label_horizontal
|
||||
kwargs["is_label_horizontal"] = False
|
||||
|
||||
self.single_item = single_item
|
||||
self.folders = folders
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class Terminal:
|
|||
r"\*\*\* WRN": _SB + _LY + r"*** WRN" + _RST,
|
||||
r" \- ": _SB + _LY + r" - " + _RST,
|
||||
r"\[ ": _SB + _LG + r"[ " + _RST,
|
||||
r"\]": _SB + _LG + r"]" + _RST,
|
||||
r" \]": _SB + _LG + r" ]" + _RST,
|
||||
r"{": _LG + r"{",
|
||||
r"}": r"}" + _RST,
|
||||
r"\(": _LY + r"(",
|
||||
|
|
|
|||
|
|
@ -493,8 +493,9 @@ def convert_for_ffmpeg(
|
|||
erase_reason = "has too long value ({} chars).".format(
|
||||
len(attr_value)
|
||||
)
|
||||
erase_attribute = True
|
||||
|
||||
if erase_attribute:
|
||||
if not erase_attribute:
|
||||
for char in NOT_ALLOWED_FFMPEG_CHARS:
|
||||
if char in attr_value:
|
||||
erase_attribute = True
|
||||
|
|
@ -623,8 +624,9 @@ def convert_input_paths_for_ffmpeg(
|
|||
erase_reason = "has too long value ({} chars).".format(
|
||||
len(attr_value)
|
||||
)
|
||||
erase_attribute = True
|
||||
|
||||
if erase_attribute:
|
||||
if not erase_attribute:
|
||||
for char in NOT_ALLOWED_FFMPEG_CHARS:
|
||||
if char in attr_value:
|
||||
erase_attribute = True
|
||||
|
|
|
|||
|
|
@ -290,49 +290,16 @@ def _load_modules():
|
|||
|
||||
log = PypeLogger.get_logger("ModulesLoader")
|
||||
|
||||
current_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
processed_paths = set()
|
||||
processed_paths.add(current_dir)
|
||||
# Import default modules imported from 'openpype.modules'
|
||||
for filename in os.listdir(current_dir):
|
||||
# Ignore filenames
|
||||
if (
|
||||
filename in IGNORED_FILENAMES
|
||||
or filename in IGNORED_DEFAULT_FILENAMES
|
||||
):
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(current_dir, filename)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
|
||||
if os.path.isdir(fullpath):
|
||||
# Check existence of init fil
|
||||
init_path = os.path.join(fullpath, "__init__.py")
|
||||
if not os.path.exists(init_path):
|
||||
log.debug((
|
||||
"Module directory does not contan __init__.py file {}"
|
||||
).format(fullpath))
|
||||
continue
|
||||
|
||||
elif ext not in (".py", ):
|
||||
continue
|
||||
|
||||
try:
|
||||
import_str = "openpype.modules.{}".format(basename)
|
||||
new_import_str = "{}.{}".format(modules_key, basename)
|
||||
default_module = __import__(import_str, fromlist=("", ))
|
||||
sys.modules[new_import_str] = default_module
|
||||
setattr(openpype_modules, basename, default_module)
|
||||
|
||||
except Exception:
|
||||
msg = (
|
||||
"Failed to import default module '{}'."
|
||||
).format(basename)
|
||||
log.error(msg, exc_info=True)
|
||||
|
||||
# Look for OpenPype modules in paths defined with `get_module_dirs`
|
||||
# - dynamically imported OpenPype modules and addons
|
||||
for dirpath in get_module_dirs():
|
||||
module_dirs = get_module_dirs()
|
||||
# Add current directory at first place
|
||||
# - has small differences in import logic
|
||||
current_dir = os.path.abspath(os.path.dirname(__file__))
|
||||
module_dirs.insert(0, current_dir)
|
||||
|
||||
processed_paths = set()
|
||||
for dirpath in module_dirs:
|
||||
# Skip already processed paths
|
||||
if dirpath in processed_paths:
|
||||
continue
|
||||
|
|
@ -344,20 +311,29 @@ def _load_modules():
|
|||
).format(dirpath))
|
||||
continue
|
||||
|
||||
is_in_current_dir = dirpath == current_dir
|
||||
for filename in os.listdir(dirpath):
|
||||
# Ignore filenames
|
||||
if filename in IGNORED_FILENAMES:
|
||||
continue
|
||||
|
||||
if (
|
||||
is_in_current_dir
|
||||
and filename in IGNORED_DEFAULT_FILENAMES
|
||||
):
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(dirpath, filename)
|
||||
basename, ext = os.path.splitext(filename)
|
||||
|
||||
# Validations
|
||||
if os.path.isdir(fullpath):
|
||||
# Check existence of init fil
|
||||
# Check existence of init file
|
||||
init_path = os.path.join(fullpath, "__init__.py")
|
||||
if not os.path.exists(init_path):
|
||||
log.debug((
|
||||
"Module directory does not contan __init__.py file {}"
|
||||
"Module directory does not contain __init__.py"
|
||||
" file {}"
|
||||
).format(fullpath))
|
||||
continue
|
||||
|
||||
|
|
@ -367,27 +343,29 @@ def _load_modules():
|
|||
# TODO add more logic how to define if folder is module or not
|
||||
# - check manifest and content of manifest
|
||||
try:
|
||||
if os.path.isdir(fullpath):
|
||||
# Module without init file can't be used as OpenPype module
|
||||
# because the module class could not be imported
|
||||
init_file = os.path.join(fullpath, "__init__.py")
|
||||
if not os.path.exists(init_file):
|
||||
log.info((
|
||||
"Skipping module directory because of"
|
||||
" missing \"__init__.py\" file. \"{}\""
|
||||
).format(fullpath))
|
||||
continue
|
||||
# Don't import dynamically current directory modules
|
||||
if is_in_current_dir:
|
||||
import_str = "openpype.modules.{}".format(basename)
|
||||
new_import_str = "{}.{}".format(modules_key, basename)
|
||||
default_module = __import__(import_str, fromlist=("", ))
|
||||
sys.modules[new_import_str] = default_module
|
||||
setattr(openpype_modules, basename, default_module)
|
||||
|
||||
elif os.path.isdir(fullpath):
|
||||
import_module_from_dirpath(dirpath, filename, modules_key)
|
||||
|
||||
elif ext in (".py", ):
|
||||
else:
|
||||
module = import_filepath(fullpath)
|
||||
setattr(openpype_modules, basename, module)
|
||||
|
||||
except Exception:
|
||||
log.error(
|
||||
"Failed to import '{}'.".format(fullpath),
|
||||
exc_info=True
|
||||
)
|
||||
if is_in_current_dir:
|
||||
msg = "Failed to import default module '{}'.".format(
|
||||
basename
|
||||
)
|
||||
else:
|
||||
msg = "Failed to import module '{}'.".format(fullpath)
|
||||
log.error(msg, exc_info=True)
|
||||
|
||||
|
||||
class _OpenPypeInterfaceMeta(ABCMeta):
|
||||
|
|
|
|||
|
|
@ -440,7 +440,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
|
||||
output_filename_0 = filename_0
|
||||
|
||||
dirname = os.path.dirname(output_filename_0)
|
||||
# this is needed because renderman handles directory and file
|
||||
# prefixes separately
|
||||
if self._instance.data["renderer"] == "renderman":
|
||||
dirname = os.path.dirname(output_filename_0)
|
||||
|
||||
# Create render folder ----------------------------------------------
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -569,7 +569,7 @@ class DeleteOldVersions(BaseAction):
|
|||
context["frame"] = self.sequence_splitter
|
||||
sequence_path = os.path.normpath(
|
||||
StringTemplate.format_strict_template(
|
||||
context, template
|
||||
template, context
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import json
|
|||
import copy
|
||||
import pyblish.api
|
||||
|
||||
from openpype.lib import get_ffprobe_streams
|
||||
from openpype.lib.profiles_filtering import filter_profiles
|
||||
|
||||
|
||||
|
|
@ -142,6 +143,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
|
|||
# Create thumbnail components
|
||||
# TODO what if there is multiple thumbnails?
|
||||
first_thumbnail_component = None
|
||||
first_thumbnail_component_repre = None
|
||||
for repre in thumbnail_representations:
|
||||
published_path = repre.get("published_path")
|
||||
if not published_path:
|
||||
|
|
@ -169,12 +171,43 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin):
|
|||
src_components_to_add.append(copy.deepcopy(thumbnail_item))
|
||||
# Create copy of first thumbnail
|
||||
if first_thumbnail_component is None:
|
||||
first_thumbnail_component = copy.deepcopy(thumbnail_item)
|
||||
first_thumbnail_component_repre = repre
|
||||
first_thumbnail_component = thumbnail_item
|
||||
# Set location
|
||||
thumbnail_item["component_location"] = ftrack_server_location
|
||||
# Add item to component list
|
||||
component_list.append(thumbnail_item)
|
||||
|
||||
if first_thumbnail_component is not None:
|
||||
width = first_thumbnail_component_repre.get("width")
|
||||
height = first_thumbnail_component_repre.get("height")
|
||||
if not width or not height:
|
||||
component_path = first_thumbnail_component["component_path"]
|
||||
streams = []
|
||||
try:
|
||||
streams = get_ffprobe_streams(component_path)
|
||||
except Exception:
|
||||
self.log.debug((
|
||||
"Failed to retrieve information about intput {}"
|
||||
).format(component_path))
|
||||
|
||||
for stream in streams:
|
||||
if "width" in stream and "height" in stream:
|
||||
width = stream["width"]
|
||||
height = stream["height"]
|
||||
break
|
||||
|
||||
if width and height:
|
||||
component_data = first_thumbnail_component["component_data"]
|
||||
component_data["name"] = "ftrackreview-image"
|
||||
component_data["metadata"] = {
|
||||
"ftr_meta": json.dumps({
|
||||
"width": width,
|
||||
"height": height,
|
||||
"format": "image"
|
||||
})
|
||||
}
|
||||
|
||||
# Create review components
|
||||
# Change asset name of each new component for review
|
||||
is_first_review_repre = True
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def install():
|
|||
|
||||
session = session_data_from_environment(context_keys=True)
|
||||
|
||||
session["schema"] = "openpype:session-2.0"
|
||||
session["schema"] = "openpype:session-3.0"
|
||||
try:
|
||||
schema.validate(session)
|
||||
except schema.ValidationError as e:
|
||||
|
|
|
|||
|
|
@ -55,36 +55,49 @@
|
|||
"nukeNodeClass": "Write",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "file_type",
|
||||
"value": "exr"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "datatype",
|
||||
"value": "16 bit half"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "compression",
|
||||
"value": "Zip (1 scanline)"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "autocrop",
|
||||
"value": "True"
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": "color_gui",
|
||||
"name": "tile_color",
|
||||
"value": "0xff0000ff"
|
||||
"value": [
|
||||
186,
|
||||
35,
|
||||
35,
|
||||
255
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "channels",
|
||||
"value": "rgb"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "colorspace",
|
||||
"value": "linear"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "create_directories",
|
||||
"value": "True"
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -95,36 +108,49 @@
|
|||
"nukeNodeClass": "Write",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "file_type",
|
||||
"value": "exr"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "datatype",
|
||||
"value": "16 bit half"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "compression",
|
||||
"value": "Zip (1 scanline)"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "autocrop",
|
||||
"value": "False"
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": "color_gui",
|
||||
"name": "tile_color",
|
||||
"value": "0xadab1dff"
|
||||
"value": [
|
||||
171,
|
||||
171,
|
||||
10,
|
||||
255
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "channels",
|
||||
"value": "rgb"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "colorspace",
|
||||
"value": "linear"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "create_directories",
|
||||
"value": "True"
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -135,32 +161,44 @@
|
|||
"nukeNodeClass": "Write",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "file_type",
|
||||
"value": "tiff"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "datatype",
|
||||
"value": "16 bit"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "compression",
|
||||
"value": "Deflate"
|
||||
},
|
||||
{
|
||||
"type": "color_gui",
|
||||
"name": "tile_color",
|
||||
"value": "0x23ff00ff"
|
||||
"value": [
|
||||
56,
|
||||
162,
|
||||
7,
|
||||
255
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "channels",
|
||||
"value": "rgb"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "colorspace",
|
||||
"value": "sRGB"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "create_directories",
|
||||
"value": "True"
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -170,7 +208,7 @@
|
|||
"regexInputs": {
|
||||
"inputs": [
|
||||
{
|
||||
"regex": "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]",
|
||||
"regex": "(beauty).*(?=.exr)",
|
||||
"colorspace": "linear"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -21,10 +21,29 @@
|
|||
"defaults": [
|
||||
"Main",
|
||||
"Mask"
|
||||
]
|
||||
],
|
||||
"knobs": [],
|
||||
"prenodes": {
|
||||
"Reformat01": {
|
||||
"nodeclass": "Reformat",
|
||||
"dependent": "",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "text",
|
||||
"name": "resize",
|
||||
"value": "none"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "black_outside",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateWritePrerender": {
|
||||
"fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}",
|
||||
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}",
|
||||
"use_range_limit": true,
|
||||
"defaults": [
|
||||
"Key01",
|
||||
|
|
@ -33,7 +52,32 @@
|
|||
"Branch01",
|
||||
"Part01"
|
||||
],
|
||||
"reviewable": false
|
||||
"reviewable": false,
|
||||
"knobs": [],
|
||||
"prenodes": {}
|
||||
},
|
||||
"CreateWriteStill": {
|
||||
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{ext}",
|
||||
"defaults": [
|
||||
"ImageFrame",
|
||||
"MPFrame",
|
||||
"LayoutFrame"
|
||||
],
|
||||
"knobs": [],
|
||||
"prenodes": {
|
||||
"FrameHold01": {
|
||||
"nodeclass": "FrameHold",
|
||||
"dependent": "",
|
||||
"knobs": [
|
||||
{
|
||||
"type": "formatable",
|
||||
"name": "first_frame",
|
||||
"template": "{frame}",
|
||||
"to_type": "number"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
|
|
@ -129,17 +173,17 @@
|
|||
"reformat_node_add": false,
|
||||
"reformat_node_config": [
|
||||
{
|
||||
"type": "string",
|
||||
"type": "text",
|
||||
"name": "type",
|
||||
"value": "to format"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"type": "text",
|
||||
"name": "format",
|
||||
"value": "HD_1080"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"type": "text",
|
||||
"name": "filter",
|
||||
"value": "Lanczos6"
|
||||
},
|
||||
|
|
@ -220,11 +264,12 @@
|
|||
"repre_names": [
|
||||
"exr",
|
||||
"dpx",
|
||||
"mov"
|
||||
"mov",
|
||||
"mp4",
|
||||
"h264"
|
||||
],
|
||||
"loaders": [
|
||||
"LoadSequence",
|
||||
"LoadMov"
|
||||
"LoadClip"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"enable_review": false,
|
||||
"description": "Publish workfile backup",
|
||||
"detailed_description": "",
|
||||
"allow_sequences": true,
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": false,
|
||||
"collapsible": true,
|
||||
"key": "CreateWriteRender",
|
||||
"label": "CreateWriteRender",
|
||||
"is_group": true,
|
||||
|
|
@ -104,12 +104,53 @@
|
|||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "prenodes",
|
||||
"label": "Pre write nodes",
|
||||
"type": "dict-modifiable",
|
||||
"highlight_content": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "nodeclass",
|
||||
"label": "Node class",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "dependent",
|
||||
"label": "Outside node dependency",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": false,
|
||||
"collapsible": true,
|
||||
"key": "CreateWritePrerender",
|
||||
"label": "CreateWritePrerender",
|
||||
"is_group": true,
|
||||
|
|
@ -136,6 +177,110 @@
|
|||
"type": "boolean",
|
||||
"key": "reviewable",
|
||||
"label": "Add reviewable toggle"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "prenodes",
|
||||
"label": "Pre write nodes",
|
||||
"type": "dict-modifiable",
|
||||
"highlight_content": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "nodeclass",
|
||||
"label": "Node class",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "dependent",
|
||||
"label": "Outside node dependency",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateWriteStill",
|
||||
"label": "CreateWriteStill",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "fpath_template",
|
||||
"label": "Path template"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Subset name defaults",
|
||||
"object_type": {
|
||||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "prenodes",
|
||||
"label": "Pre write nodes",
|
||||
"type": "dict-modifiable",
|
||||
"highlight_content": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "nodeclass",
|
||||
"label": "Node class",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"key": "dependent",
|
||||
"label": "Outside node dependency",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Node knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,12 +45,6 @@
|
|||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enable_review",
|
||||
"label": "Enable review",
|
||||
"tooltip": "Allow to create review from source file/s.\nFiles must be supported to be able create review."
|
||||
},
|
||||
{
|
||||
"type": "separator"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -272,29 +272,12 @@
|
|||
"label": "Nuke Node Class"
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Knobs",
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"children": [
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "knobs",
|
||||
"type": "list",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
"label": "Knobs",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -333,29 +316,12 @@
|
|||
"object_type": "text"
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Knobs overrides",
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"children": [
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"key": "knobs",
|
||||
"type": "list",
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
"label": "Knobs overrides",
|
||||
"key": "knobs"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,108 +253,12 @@
|
|||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "Reformat Node Knobs",
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"children": [
|
||||
"type": "schema_template",
|
||||
"name": "template_nuke_knob_inputs",
|
||||
"template_data": [
|
||||
{
|
||||
"type": "list",
|
||||
"key": "reformat_node_config",
|
||||
"object_type": {
|
||||
"type": "dict-conditional",
|
||||
"enum_key": "type",
|
||||
"enum_label": "Type",
|
||||
"enum_children": [
|
||||
{
|
||||
"key": "string",
|
||||
"label": "String",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "bool",
|
||||
"label": "Boolean",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "number",
|
||||
"label": "Number",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"object_types": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "number",
|
||||
"default": 1,
|
||||
"decimal": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "list_numbers",
|
||||
"label": "2 Numbers",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"object_types": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "x",
|
||||
"default": 1,
|
||||
"decimal": 4
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"label": "Reformat Node Knobs",
|
||||
"key": "reformat_node_config"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,275 @@
|
|||
[
|
||||
{
|
||||
"type": "collapsible-wrap",
|
||||
"label": "{label}",
|
||||
"collapsible": true,
|
||||
"collapsed": true,
|
||||
"children": [{
|
||||
"type": "list",
|
||||
"key": "{key}",
|
||||
"object_type": {
|
||||
"type": "dict-conditional",
|
||||
"enum_key": "type",
|
||||
"enum_label": "Type",
|
||||
"enum_children": [
|
||||
{
|
||||
"key": "text",
|
||||
"label": "Text",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "formatable",
|
||||
"label": "Formate from template",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "template",
|
||||
"label": "Template",
|
||||
"placeholder": "{{key}} or {{key}};{{key}}"
|
||||
},
|
||||
{
|
||||
"type": "enum",
|
||||
"key": "to_type",
|
||||
"label": "Knob type",
|
||||
"enum_items": [
|
||||
{
|
||||
"text": "Text"
|
||||
},
|
||||
{
|
||||
"number": "Number"
|
||||
},
|
||||
{
|
||||
"decimal_number": "Decimal number"
|
||||
},
|
||||
{
|
||||
"2d_vector": "2D vector"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "color_gui",
|
||||
"label": "Color GUI",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"use_alpha": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "bool",
|
||||
"label": "Boolean",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "number",
|
||||
"label": "Number",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "value",
|
||||
"default": 1,
|
||||
"decimal": 0,
|
||||
"maximum": 99999999
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "decimal_number",
|
||||
"label": "Decimal number",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "value",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "2d_vector",
|
||||
"label": "2D vector",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"object_types": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "x",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "3d_vector",
|
||||
"label": "3D vector",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"object_types": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "x",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "color",
|
||||
"label": "Color",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "list-strict",
|
||||
"key": "value",
|
||||
"label": "Value",
|
||||
"object_types": [
|
||||
{
|
||||
"type": "number",
|
||||
"key": "x",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "x",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"key": "y",
|
||||
"default": 1,
|
||||
"decimal": 4,
|
||||
"maximum": 99999999
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "__legacy__",
|
||||
"label": "_ Legacy type _",
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"key": "name",
|
||||
"label": "Name"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "value",
|
||||
"label": "Value"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
|
@ -291,6 +291,22 @@ def _system_settings_backwards_compatible_conversion(studio_overrides):
|
|||
}
|
||||
|
||||
|
||||
def _project_anatomy_backwards_compatible_conversion(project_anatomy):
|
||||
# Backwards compatibility of node settings in Nuke 3.9.x - 3.10.0
|
||||
# - source PR - https://github.com/pypeclub/OpenPype/pull/3143
|
||||
value = project_anatomy
|
||||
for key in ("imageio", "nuke", "nodes", "requiredNodes"):
|
||||
if key not in value:
|
||||
return
|
||||
value = value[key]
|
||||
|
||||
for item in value:
|
||||
for node in item.get("knobs") or []:
|
||||
if "type" in node:
|
||||
break
|
||||
node["type"] = "__legacy__"
|
||||
|
||||
|
||||
@require_handler
|
||||
def get_studio_system_settings_overrides(return_version=False):
|
||||
output = _SETTINGS_HANDLER.get_studio_system_settings_overrides(
|
||||
|
|
@ -326,7 +342,9 @@ def get_project_settings_overrides(project_name, return_version=False):
|
|||
|
||||
@require_handler
|
||||
def get_project_anatomy_overrides(project_name):
|
||||
return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
|
||||
output = _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
|
||||
_project_anatomy_backwards_compatible_conversion(output)
|
||||
return output
|
||||
|
||||
|
||||
@require_handler
|
||||
|
|
|
|||
|
|
@ -856,18 +856,31 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
}
|
||||
|
||||
/* New Create/Publish UI */
|
||||
#CreatorDetailedDescription {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
padding-top: 5px;
|
||||
background: transparent;
|
||||
border: 1px solid {color:border};
|
||||
}
|
||||
|
||||
#CreateDialogHelpButton {
|
||||
background: rgba(255, 255, 255, 31);
|
||||
border-top-left-radius: 0.2em;
|
||||
border-bottom-left-radius: 0.2em;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
padding: 3px 3px 3px 3px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#CreateDialogHelpButton:hover {
|
||||
background: rgba(255, 255, 255, 63);
|
||||
}
|
||||
#CreateDialogHelpButton QWidget {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#PublishLogConsole {
|
||||
font-family: "Noto Sans Mono";
|
||||
|
|
@ -1014,7 +1027,44 @@ VariantInputsWidget QToolButton {
|
|||
border-left: 1px solid {color:border};
|
||||
}
|
||||
|
||||
#TasksCombobox[state="invalid"], #AssetNameInput[state="invalid"] {
|
||||
#AssetNameInputWidget {
|
||||
background: {color:bg-inputs};
|
||||
border: 1px solid {color:border};
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
#AssetNameInputWidget QWidget {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#AssetNameInputButton {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
padding: 0px;
|
||||
qproperty-iconSize: 11px 11px;
|
||||
border-left: 1px solid {color:border};
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#AssetNameInput {
|
||||
border-bottom-right-radius: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#AssetNameInputWidget:hover {
|
||||
border-color: {color:border-hover};
|
||||
}
|
||||
#AssetNameInputWidget:focus{
|
||||
border-color: {color:border-focus};
|
||||
}
|
||||
#AssetNameInputWidget:disabled {
|
||||
background: {color:bg-inputs-disabled};
|
||||
}
|
||||
|
||||
#TasksCombobox[state="invalid"], #AssetNameInputWidget[state="invalid"], #AssetNameInputButton[state="invalid"] {
|
||||
border-color: {color:publisher:error};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from openpype.tools.utils.assets_widget import (
|
|||
|
||||
class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
|
||||
current_context_required = QtCore.Signal()
|
||||
header_height_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, controller, parent):
|
||||
self._controller = controller
|
||||
|
|
@ -27,6 +28,27 @@ class CreateDialogAssetsWidget(SingleSelectAssetsWidget):
|
|||
self._last_selection = None
|
||||
self._enabled = None
|
||||
|
||||
self._last_filter_height = None
|
||||
|
||||
def _check_header_height(self):
|
||||
"""Catch header height changes.
|
||||
|
||||
Label on top of creaters should have same height so Creators view has
|
||||
same offset.
|
||||
"""
|
||||
height = self.header_widget.height()
|
||||
if height != self._last_filter_height:
|
||||
self._last_filter_height = height
|
||||
self.header_height_changed.emit(height)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(CreateDialogAssetsWidget, self).resizeEvent(event)
|
||||
self._check_header_height()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(CreateDialogAssetsWidget, self).showEvent(event)
|
||||
self._check_header_height()
|
||||
|
||||
def _on_current_asset_click(self):
|
||||
self.current_context_required.emit()
|
||||
|
||||
|
|
@ -71,6 +93,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel):
|
|||
Uses controller to load asset hierarchy. All asset documents are stored by
|
||||
their parents.
|
||||
"""
|
||||
|
||||
def __init__(self, controller):
|
||||
super(AssetsHierarchyModel, self).__init__()
|
||||
self._controller = controller
|
||||
|
|
@ -143,6 +166,7 @@ class AssetsHierarchyModel(QtGui.QStandardItemModel):
|
|||
|
||||
class AssetsDialog(QtWidgets.QDialog):
|
||||
"""Dialog to select asset for a context of instance."""
|
||||
|
||||
def __init__(self, controller, parent):
|
||||
super(AssetsDialog, self).__init__(parent)
|
||||
self.setWindowTitle("Select asset")
|
||||
|
|
@ -196,9 +220,26 @@ class AssetsDialog(QtWidgets.QDialog):
|
|||
# - adds ability to call reset on multiple places without repeating
|
||||
self._soft_reset_enabled = True
|
||||
|
||||
self._first_show = True
|
||||
self._default_height = 500
|
||||
|
||||
def _on_first_show(self):
|
||||
center = self.rect().center()
|
||||
size = self.size()
|
||||
size.setHeight(self._default_height)
|
||||
|
||||
self.resize(size)
|
||||
new_pos = self.mapToGlobal(center)
|
||||
new_pos.setX(new_pos.x() - int(self.width() / 2))
|
||||
new_pos.setY(new_pos.y() - int(self.height() / 2))
|
||||
self.move(new_pos)
|
||||
|
||||
def showEvent(self, event):
|
||||
"""Refresh asset model on show."""
|
||||
super(AssetsDialog, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self._on_first_show()
|
||||
# Refresh on show
|
||||
self.reset(False)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import re
|
|||
import traceback
|
||||
import copy
|
||||
|
||||
import qtawesome
|
||||
try:
|
||||
import commonmark
|
||||
except Exception:
|
||||
|
|
@ -15,7 +16,8 @@ from openpype.pipeline.create import (
|
|||
)
|
||||
from openpype.tools.utils import (
|
||||
ErrorMessageBox,
|
||||
MessageOverlayObject
|
||||
MessageOverlayObject,
|
||||
ClickableFrame,
|
||||
)
|
||||
|
||||
from .widgets import IconValuePixmapLabel
|
||||
|
|
@ -114,6 +116,8 @@ class CreateErrorMessageBox(ErrorMessageBox):
|
|||
|
||||
# TODO add creator identifier/label to details
|
||||
class CreatorShortDescWidget(QtWidgets.QWidget):
|
||||
height_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(CreatorShortDescWidget, self).__init__(parent=parent)
|
||||
|
||||
|
|
@ -152,6 +156,22 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
|
|||
self._family_label = family_label
|
||||
self._description_label = description_label
|
||||
|
||||
self._last_height = None
|
||||
|
||||
def _check_height_change(self):
|
||||
height = self.height()
|
||||
if height != self._last_height:
|
||||
self._last_height = height
|
||||
self.height_changed.emit(height)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(CreatorShortDescWidget, self).showEvent(event)
|
||||
self._check_height_change()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(CreatorShortDescWidget, self).resizeEvent(event)
|
||||
self._check_height_change()
|
||||
|
||||
def set_plugin(self, plugin=None):
|
||||
if not plugin:
|
||||
self._icon_widget.set_icon_def(None)
|
||||
|
|
@ -168,13 +188,43 @@ class CreatorShortDescWidget(QtWidgets.QWidget):
|
|||
self._description_label.setText(description)
|
||||
|
||||
|
||||
class HelpButton(QtWidgets.QPushButton):
|
||||
resized = QtCore.Signal()
|
||||
class HelpButton(ClickableFrame):
|
||||
resized = QtCore.Signal(int)
|
||||
question_mark_icon_name = "fa.question"
|
||||
help_icon_name = "fa.question-circle"
|
||||
hide_icon_name = "fa.angle-left"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(HelpButton, self).__init__(*args, **kwargs)
|
||||
self.setObjectName("CreateDialogHelpButton")
|
||||
|
||||
question_mark_label = QtWidgets.QLabel(self)
|
||||
help_widget = QtWidgets.QWidget(self)
|
||||
|
||||
help_question = QtWidgets.QLabel(help_widget)
|
||||
help_label = QtWidgets.QLabel("Help", help_widget)
|
||||
hide_icon = QtWidgets.QLabel(help_widget)
|
||||
|
||||
help_layout = QtWidgets.QHBoxLayout(help_widget)
|
||||
help_layout.setContentsMargins(0, 0, 5, 0)
|
||||
help_layout.addWidget(help_question, 0)
|
||||
help_layout.addWidget(help_label, 0)
|
||||
help_layout.addStretch(1)
|
||||
help_layout.addWidget(hide_icon, 0)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(question_mark_label, 0)
|
||||
layout.addWidget(help_widget, 1)
|
||||
|
||||
help_widget.setVisible(False)
|
||||
|
||||
self._question_mark_label = question_mark_label
|
||||
self._help_widget = help_widget
|
||||
self._help_question = help_question
|
||||
self._hide_icon = hide_icon
|
||||
|
||||
self._expanded = None
|
||||
self.set_expanded()
|
||||
|
||||
|
|
@ -184,31 +234,56 @@ class HelpButton(QtWidgets.QPushButton):
|
|||
return
|
||||
expanded = False
|
||||
self._expanded = expanded
|
||||
if expanded:
|
||||
text = "<"
|
||||
self._help_widget.setVisible(expanded)
|
||||
self._update_content()
|
||||
|
||||
def _update_content(self):
|
||||
width = self.get_icon_width()
|
||||
if self._expanded:
|
||||
question_mark_pix = QtGui.QPixmap(width, width)
|
||||
question_mark_pix.fill(QtCore.Qt.transparent)
|
||||
|
||||
else:
|
||||
text = "?"
|
||||
self.setText(text)
|
||||
question_mark_icon = qtawesome.icon(
|
||||
self.question_mark_icon_name, color=QtCore.Qt.white
|
||||
)
|
||||
question_mark_pix = question_mark_icon.pixmap(width, width)
|
||||
|
||||
self._update_size()
|
||||
hide_icon = qtawesome.icon(
|
||||
self.hide_icon_name, color=QtCore.Qt.white
|
||||
)
|
||||
help_question_icon = qtawesome.icon(
|
||||
self.help_icon_name, color=QtCore.Qt.white
|
||||
)
|
||||
self._question_mark_label.setPixmap(question_mark_pix)
|
||||
self._question_mark_label.setMaximumWidth(width)
|
||||
self._hide_icon.setPixmap(hide_icon.pixmap(width, width))
|
||||
self._help_question.setPixmap(help_question_icon.pixmap(width, width))
|
||||
|
||||
def _update_size(self):
|
||||
new_size = self.minimumSizeHint()
|
||||
if self.size() != new_size:
|
||||
self.resize(new_size)
|
||||
self.resized.emit()
|
||||
def get_icon_width(self):
|
||||
metrics = self.fontMetrics()
|
||||
return metrics.height()
|
||||
|
||||
def set_pos_and_size(self, pos_x, pos_y, width, height):
|
||||
update_icon = self.height() != height
|
||||
self.move(pos_x, pos_y)
|
||||
self.resize(width, height)
|
||||
|
||||
if update_icon:
|
||||
self._update_content()
|
||||
self.updateGeometry()
|
||||
|
||||
def showEvent(self, event):
|
||||
super(HelpButton, self).showEvent(event)
|
||||
self._update_size()
|
||||
self.resized.emit(self.height())
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(HelpButton, self).resizeEvent(event)
|
||||
self._update_size()
|
||||
self.resized.emit(self.height())
|
||||
|
||||
|
||||
class CreateDialog(QtWidgets.QDialog):
|
||||
default_size = (900, 500)
|
||||
default_size = (1000, 560)
|
||||
|
||||
def __init__(
|
||||
self, controller, asset_name=None, task_name=None, parent=None
|
||||
|
|
@ -255,6 +330,14 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
context_layout.addWidget(tasks_widget, 1)
|
||||
|
||||
# --- Creators view ---
|
||||
creators_header_widget = QtWidgets.QWidget(self)
|
||||
header_label_widget = QtWidgets.QLabel(
|
||||
"Choose family:", creators_header_widget
|
||||
)
|
||||
creators_header_layout = QtWidgets.QHBoxLayout(creators_header_widget)
|
||||
creators_header_layout.setContentsMargins(0, 0, 0, 0)
|
||||
creators_header_layout.addWidget(header_label_widget, 1)
|
||||
|
||||
creators_view = QtWidgets.QListView(self)
|
||||
creators_model = QtGui.QStandardItemModel()
|
||||
creators_view.setModel(creators_model)
|
||||
|
|
@ -271,7 +354,6 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
|
||||
variant_hints_menu = QtWidgets.QMenu(variant_widget)
|
||||
variant_hints_group = QtWidgets.QActionGroup(variant_hints_menu)
|
||||
# variant_hints_btn.setMenu(variant_hints_menu)
|
||||
|
||||
variant_layout = QtWidgets.QHBoxLayout(variant_widget)
|
||||
variant_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -282,9 +364,6 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
subset_name_input = QtWidgets.QLineEdit(self)
|
||||
subset_name_input.setEnabled(False)
|
||||
|
||||
create_btn = QtWidgets.QPushButton("Create", self)
|
||||
create_btn.setEnabled(False)
|
||||
|
||||
form_layout = QtWidgets.QFormLayout()
|
||||
form_layout.addRow("Variant:", variant_widget)
|
||||
form_layout.addRow("Subset:", subset_name_input)
|
||||
|
|
@ -292,10 +371,9 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
mid_widget = QtWidgets.QWidget(self)
|
||||
mid_layout = QtWidgets.QVBoxLayout(mid_widget)
|
||||
mid_layout.setContentsMargins(0, 0, 0, 0)
|
||||
mid_layout.addWidget(QtWidgets.QLabel("Choose family:", self))
|
||||
mid_layout.addWidget(creators_header_widget, 0)
|
||||
mid_layout.addWidget(creators_view, 1)
|
||||
mid_layout.addLayout(form_layout, 0)
|
||||
mid_layout.addWidget(create_btn, 0)
|
||||
# ------------
|
||||
|
||||
# --- Creator short info and attr defs ---
|
||||
|
|
@ -305,31 +383,62 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
creator_attrs_widget
|
||||
)
|
||||
|
||||
separator_widget = QtWidgets.QWidget(self)
|
||||
separator_widget.setObjectName("Separator")
|
||||
separator_widget.setMinimumHeight(2)
|
||||
separator_widget.setMaximumHeight(2)
|
||||
attr_separator_widget = QtWidgets.QWidget(self)
|
||||
attr_separator_widget.setObjectName("Separator")
|
||||
attr_separator_widget.setMinimumHeight(1)
|
||||
attr_separator_widget.setMaximumHeight(1)
|
||||
|
||||
# Precreate attributes widget
|
||||
pre_create_widget = PreCreateWidget(creator_attrs_widget)
|
||||
|
||||
# Create button
|
||||
create_btn_wrapper = QtWidgets.QWidget(creator_attrs_widget)
|
||||
create_btn = QtWidgets.QPushButton("Create", create_btn_wrapper)
|
||||
create_btn.setEnabled(False)
|
||||
|
||||
create_btn_wrap_layout = QtWidgets.QHBoxLayout(create_btn_wrapper)
|
||||
create_btn_wrap_layout.setContentsMargins(0, 0, 0, 0)
|
||||
create_btn_wrap_layout.addStretch(1)
|
||||
create_btn_wrap_layout.addWidget(create_btn, 0)
|
||||
|
||||
creator_attrs_layout = QtWidgets.QVBoxLayout(creator_attrs_widget)
|
||||
creator_attrs_layout.setContentsMargins(0, 0, 0, 0)
|
||||
creator_attrs_layout.addWidget(creator_short_desc_widget, 0)
|
||||
creator_attrs_layout.addWidget(separator_widget, 0)
|
||||
creator_attrs_layout.addWidget(attr_separator_widget, 0)
|
||||
creator_attrs_layout.addWidget(pre_create_widget, 1)
|
||||
creator_attrs_layout.addWidget(create_btn_wrapper, 0)
|
||||
# -------------------------------------
|
||||
|
||||
# --- Detailed information about creator ---
|
||||
# Detailed description of creator
|
||||
detail_description_widget = QtWidgets.QTextEdit(self)
|
||||
detail_description_widget.setObjectName("InfoText")
|
||||
detail_description_widget.setTextInteractionFlags(
|
||||
detail_description_widget = QtWidgets.QWidget(self)
|
||||
|
||||
detail_placoholder_widget = QtWidgets.QWidget(
|
||||
detail_description_widget
|
||||
)
|
||||
detail_placoholder_widget.setAttribute(
|
||||
QtCore.Qt.WA_TranslucentBackground
|
||||
)
|
||||
|
||||
detail_description_input = QtWidgets.QTextEdit(
|
||||
detail_description_widget
|
||||
)
|
||||
detail_description_input.setObjectName("CreatorDetailedDescription")
|
||||
detail_description_input.setTextInteractionFlags(
|
||||
QtCore.Qt.TextBrowserInteraction
|
||||
)
|
||||
detail_description_widget.setVisible(False)
|
||||
# -------------------------------------------
|
||||
|
||||
detail_description_layout = QtWidgets.QVBoxLayout(
|
||||
detail_description_widget
|
||||
)
|
||||
detail_description_layout.setContentsMargins(0, 0, 0, 0)
|
||||
detail_description_layout.setSpacing(0)
|
||||
detail_description_layout.addWidget(detail_placoholder_widget, 0)
|
||||
detail_description_layout.addWidget(detail_description_input, 1)
|
||||
|
||||
detail_description_widget.setVisible(False)
|
||||
|
||||
# -------------------------------------------
|
||||
splitter_widget = QtWidgets.QSplitter(self)
|
||||
splitter_widget.addWidget(context_widget)
|
||||
splitter_widget.addWidget(mid_widget)
|
||||
|
|
@ -344,17 +453,27 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
layout.addWidget(splitter_widget, 1)
|
||||
|
||||
# Floating help button
|
||||
# - Create this button as last to be fully visible
|
||||
help_btn = HelpButton(self)
|
||||
|
||||
prereq_timer = QtCore.QTimer()
|
||||
prereq_timer.setInterval(50)
|
||||
prereq_timer.setSingleShot(True)
|
||||
|
||||
desc_width_anim_timer = QtCore.QTimer()
|
||||
desc_width_anim_timer.setInterval(10)
|
||||
|
||||
prereq_timer.timeout.connect(self._on_prereq_timer)
|
||||
|
||||
desc_width_anim_timer.timeout.connect(self._on_desc_animation)
|
||||
|
||||
help_btn.clicked.connect(self._on_help_btn)
|
||||
help_btn.resized.connect(self._on_help_btn_resize)
|
||||
|
||||
assets_widget.header_height_changed.connect(
|
||||
self._on_asset_filter_height_change
|
||||
)
|
||||
|
||||
create_btn.clicked.connect(self._on_create)
|
||||
variant_widget.resized.connect(self._on_variant_widget_resize)
|
||||
variant_input.returnPressed.connect(self._on_create)
|
||||
|
|
@ -369,6 +488,10 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
self._on_current_session_context_request
|
||||
)
|
||||
tasks_widget.task_changed.connect(self._on_task_change)
|
||||
creator_short_desc_widget.height_changed.connect(
|
||||
self._on_description_height_change
|
||||
)
|
||||
splitter_widget.splitterMoved.connect(self._on_splitter_move)
|
||||
|
||||
controller.add_plugins_refresh_callback(self._on_plugins_refresh)
|
||||
|
||||
|
|
@ -387,18 +510,33 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
self.variant_hints_menu = variant_hints_menu
|
||||
self.variant_hints_group = variant_hints_group
|
||||
|
||||
self._creators_header_widget = creators_header_widget
|
||||
self.creators_model = creators_model
|
||||
self.creators_view = creators_view
|
||||
self.create_btn = create_btn
|
||||
|
||||
self._creator_short_desc_widget = creator_short_desc_widget
|
||||
self._pre_create_widget = pre_create_widget
|
||||
self._attr_separator_widget = attr_separator_widget
|
||||
|
||||
self._detail_placoholder_widget = detail_placoholder_widget
|
||||
self._detail_description_widget = detail_description_widget
|
||||
self._detail_description_input = detail_description_input
|
||||
self._help_btn = help_btn
|
||||
|
||||
self._prereq_timer = prereq_timer
|
||||
self._first_show = True
|
||||
|
||||
# Description animation
|
||||
self._description_size_policy = detail_description_widget.sizePolicy()
|
||||
self._desc_width_anim_timer = desc_width_anim_timer
|
||||
self._desc_widget_step = 0
|
||||
self._last_description_width = None
|
||||
self._last_full_width = 0
|
||||
self._expected_description_width = 0
|
||||
self._last_desc_max_width = None
|
||||
self._other_widgets_widths = []
|
||||
|
||||
def _emit_message(self, message):
|
||||
self._overlay_object.add_message(message)
|
||||
|
||||
|
|
@ -465,6 +603,10 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
def _invalidate_prereq(self):
|
||||
self._prereq_timer.start()
|
||||
|
||||
def _on_asset_filter_height_change(self, height):
|
||||
self._creators_header_widget.setMinimumHeight(height)
|
||||
self._creators_header_widget.setMaximumHeight(height)
|
||||
|
||||
def _on_prereq_timer(self):
|
||||
prereq_available = True
|
||||
creator_btn_tooltips = []
|
||||
|
|
@ -595,6 +737,12 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
if self._task_name:
|
||||
self._tasks_widget.select_task_name(self._task_name)
|
||||
|
||||
def _on_description_height_change(self):
|
||||
# Use separator's 'y' position as height
|
||||
height = self._attr_separator_widget.y()
|
||||
self._detail_placoholder_widget.setMinimumHeight(height)
|
||||
self._detail_placoholder_widget.setMaximumHeight(height)
|
||||
|
||||
def _on_creator_item_change(self, new_index, _old_index):
|
||||
identifier = None
|
||||
if new_index.isValid():
|
||||
|
|
@ -602,54 +750,192 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
self._set_creator_by_identifier(identifier)
|
||||
|
||||
def _update_help_btn(self):
|
||||
pos_x = self.width() - self._help_btn.width()
|
||||
point = self._creator_short_desc_widget.rect().topRight()
|
||||
mapped_point = self._creator_short_desc_widget.mapTo(self, point)
|
||||
pos_y = mapped_point.y()
|
||||
self._help_btn.move(max(0, pos_x), max(0, pos_y))
|
||||
short_desc_rect = self._creator_short_desc_widget.rect()
|
||||
|
||||
def _on_help_btn_resize(self):
|
||||
# point = short_desc_rect.topRight()
|
||||
point = short_desc_rect.center()
|
||||
mapped_point = self._creator_short_desc_widget.mapTo(self, point)
|
||||
# pos_y = mapped_point.y()
|
||||
center_pos_y = mapped_point.y()
|
||||
icon_width = self._help_btn.get_icon_width()
|
||||
|
||||
_height = int(icon_width * 2.5)
|
||||
height = min(_height, short_desc_rect.height())
|
||||
pos_y = center_pos_y - int(height / 2)
|
||||
|
||||
pos_x = self.width() - icon_width
|
||||
if self._detail_placoholder_widget.isVisible():
|
||||
pos_x -= (
|
||||
self._detail_placoholder_widget.width()
|
||||
+ self._splitter_widget.handle(3).width()
|
||||
)
|
||||
|
||||
width = self.width() - pos_x
|
||||
|
||||
self._help_btn.set_pos_and_size(
|
||||
max(0, pos_x), max(0, pos_y),
|
||||
width, height
|
||||
)
|
||||
|
||||
def _on_help_btn_resize(self, height):
|
||||
if self._creator_short_desc_widget.height() != height:
|
||||
self._update_help_btn()
|
||||
|
||||
def _on_splitter_move(self, *args):
|
||||
self._update_help_btn()
|
||||
|
||||
def _on_help_btn(self):
|
||||
if self._desc_width_anim_timer.isActive():
|
||||
return
|
||||
|
||||
final_size = self.size()
|
||||
cur_sizes = self._splitter_widget.sizes()
|
||||
spacing = self._splitter_widget.handleWidth()
|
||||
|
||||
if self._desc_widget_step == 0:
|
||||
now_visible = self._detail_description_widget.isVisible()
|
||||
else:
|
||||
now_visible = self._desc_widget_step > 0
|
||||
|
||||
sizes = []
|
||||
for idx, value in enumerate(cur_sizes):
|
||||
if idx < 3:
|
||||
sizes.append(value)
|
||||
|
||||
now_visible = self._detail_description_widget.isVisible()
|
||||
self._last_full_width = final_size.width()
|
||||
self._other_widgets_widths = list(sizes)
|
||||
|
||||
if now_visible:
|
||||
width = final_size.width() - (
|
||||
spacing + self._detail_description_widget.width()
|
||||
)
|
||||
cur_desc_width = self._detail_description_widget.width()
|
||||
if cur_desc_width < 1:
|
||||
cur_desc_width = 2
|
||||
step_size = int(cur_desc_width / 5)
|
||||
if step_size < 1:
|
||||
step_size = 1
|
||||
|
||||
step_size *= -1
|
||||
expected_width = 0
|
||||
desc_width = cur_desc_width - 1
|
||||
width = final_size.width() - 1
|
||||
min_max = desc_width
|
||||
self._last_description_width = cur_desc_width
|
||||
|
||||
else:
|
||||
last_size = self._detail_description_widget.sizeHint().width()
|
||||
width = final_size.width() + spacing + last_size
|
||||
sizes.append(last_size)
|
||||
self._detail_description_widget.setVisible(True)
|
||||
handle = self._splitter_widget.handle(3)
|
||||
desc_width = handle.sizeHint().width()
|
||||
if self._last_description_width:
|
||||
expected_width = self._last_description_width
|
||||
else:
|
||||
hint = self._detail_description_widget.sizeHint()
|
||||
expected_width = hint.width()
|
||||
|
||||
width = final_size.width() + desc_width
|
||||
step_size = int(expected_width / 5)
|
||||
if step_size < 1:
|
||||
step_size = 1
|
||||
min_max = 0
|
||||
|
||||
if self._last_desc_max_width is None:
|
||||
self._last_desc_max_width = (
|
||||
self._detail_description_widget.maximumWidth()
|
||||
)
|
||||
self._detail_description_widget.setMinimumWidth(min_max)
|
||||
self._detail_description_widget.setMaximumWidth(min_max)
|
||||
self._expected_description_width = expected_width
|
||||
self._desc_widget_step = step_size
|
||||
|
||||
self._desc_width_anim_timer.start()
|
||||
|
||||
sizes.append(desc_width)
|
||||
|
||||
final_size.setWidth(width)
|
||||
|
||||
self._detail_description_widget.setVisible(not now_visible)
|
||||
self._splitter_widget.setSizes(sizes)
|
||||
self.resize(final_size)
|
||||
|
||||
self._help_btn.set_expanded(not now_visible)
|
||||
|
||||
def _on_desc_animation(self):
|
||||
current_width = self._detail_description_widget.width()
|
||||
|
||||
desc_width = None
|
||||
last_step = False
|
||||
growing = self._desc_widget_step > 0
|
||||
|
||||
# Growing
|
||||
if growing:
|
||||
if current_width < self._expected_description_width:
|
||||
desc_width = current_width + self._desc_widget_step
|
||||
if desc_width >= self._expected_description_width:
|
||||
desc_width = self._expected_description_width
|
||||
last_step = True
|
||||
|
||||
# Decreasing
|
||||
elif self._desc_widget_step < 0:
|
||||
if current_width > self._expected_description_width:
|
||||
desc_width = current_width + self._desc_widget_step
|
||||
if desc_width <= self._expected_description_width:
|
||||
desc_width = self._expected_description_width
|
||||
last_step = True
|
||||
|
||||
if desc_width is None:
|
||||
self._desc_widget_step = 0
|
||||
self._desc_width_anim_timer.stop()
|
||||
return
|
||||
|
||||
if last_step and not growing:
|
||||
self._detail_description_widget.setVisible(False)
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
width = self._last_full_width
|
||||
handle_width = self._splitter_widget.handle(3).width()
|
||||
if growing:
|
||||
width += (handle_width + desc_width)
|
||||
else:
|
||||
width -= self._last_description_width
|
||||
if last_step:
|
||||
width -= handle_width
|
||||
else:
|
||||
width += desc_width
|
||||
|
||||
if not last_step or growing:
|
||||
self._detail_description_widget.setMaximumWidth(desc_width)
|
||||
self._detail_description_widget.setMinimumWidth(desc_width)
|
||||
|
||||
window_size = self.size()
|
||||
window_size.setWidth(width)
|
||||
self.resize(window_size)
|
||||
if not last_step:
|
||||
return
|
||||
|
||||
self._desc_widget_step = 0
|
||||
self._desc_width_anim_timer.stop()
|
||||
|
||||
if not growing:
|
||||
return
|
||||
|
||||
self._detail_description_widget.setMinimumWidth(0)
|
||||
self._detail_description_widget.setMaximumWidth(
|
||||
self._last_desc_max_width
|
||||
)
|
||||
self._detail_description_widget.setSizePolicy(
|
||||
self._description_size_policy
|
||||
)
|
||||
|
||||
sizes = list(self._other_widgets_widths)
|
||||
sizes.append(desc_width)
|
||||
self._splitter_widget.setSizes(sizes)
|
||||
|
||||
def _set_creator_detailed_text(self, creator):
|
||||
if not creator:
|
||||
self._detail_description_widget.setPlainText("")
|
||||
self._detail_description_input.setPlainText("")
|
||||
return
|
||||
detailed_description = creator.get_detail_description() or ""
|
||||
if commonmark:
|
||||
html = commonmark.commonmark(detailed_description)
|
||||
self._detail_description_widget.setHtml(html)
|
||||
self._detail_description_input.setHtml(html)
|
||||
else:
|
||||
self._detail_description_widget.setMarkdown(detailed_description)
|
||||
self._detail_description_input.setMarkdown(detailed_description)
|
||||
|
||||
def _set_creator_by_identifier(self, identifier):
|
||||
creator = self.controller.manual_creators.get(identifier)
|
||||
|
|
@ -806,6 +1092,21 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
self.variant_input.setProperty("state", state)
|
||||
self.variant_input.style().polish(self.variant_input)
|
||||
|
||||
def _on_first_show(self):
|
||||
center = self.rect().center()
|
||||
|
||||
width, height = self.default_size
|
||||
self.resize(width, height)
|
||||
part = int(width / 7)
|
||||
self._splitter_widget.setSizes(
|
||||
[part * 2, part * 2, width - (part * 4)]
|
||||
)
|
||||
|
||||
new_pos = self.mapToGlobal(center)
|
||||
new_pos.setX(new_pos.x() - int(self.width() / 2))
|
||||
new_pos.setY(new_pos.y() - int(self.height() / 2))
|
||||
self.move(new_pos)
|
||||
|
||||
def moveEvent(self, event):
|
||||
super(CreateDialog, self).moveEvent(event)
|
||||
self._last_pos = self.pos()
|
||||
|
|
@ -814,13 +1115,7 @@ class CreateDialog(QtWidgets.QDialog):
|
|||
super(CreateDialog, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
width, height = self.default_size
|
||||
self.resize(width, height)
|
||||
|
||||
third_size = int(width / 3)
|
||||
self._splitter_widget.setSizes(
|
||||
[third_size, third_size, width - (2 * third_size)]
|
||||
)
|
||||
self._on_first_show()
|
||||
|
||||
if self._last_pos is not None:
|
||||
self.move(self._last_pos)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ from openpype.tools.utils import (
|
|||
PlaceholderLineEdit,
|
||||
IconButton,
|
||||
PixmapLabel,
|
||||
BaseClickableFrame
|
||||
BaseClickableFrame,
|
||||
set_style_property,
|
||||
)
|
||||
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
|
||||
from .assets_widget import AssetsDialog
|
||||
|
|
@ -344,21 +345,42 @@ class AssetsField(BaseClickableFrame):
|
|||
|
||||
def __init__(self, controller, parent):
|
||||
super(AssetsField, self).__init__(parent)
|
||||
self.setObjectName("AssetNameInputWidget")
|
||||
|
||||
dialog = AssetsDialog(controller, self)
|
||||
# Don't use 'self' for parent!
|
||||
# - this widget has specific styles
|
||||
dialog = AssetsDialog(controller, parent)
|
||||
|
||||
name_input = ClickableLineEdit(self)
|
||||
name_input.setObjectName("AssetNameInput")
|
||||
|
||||
icon_name = "fa.window-maximize"
|
||||
icon = qtawesome.icon(icon_name, color="white")
|
||||
icon_btn = QtWidgets.QPushButton(self)
|
||||
icon_btn.setIcon(icon)
|
||||
icon_btn.setObjectName("AssetNameInputButton")
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(name_input, 1)
|
||||
layout.addWidget(icon_btn, 0)
|
||||
|
||||
# Make sure all widgets are vertically extended to highest widget
|
||||
for widget in (
|
||||
name_input,
|
||||
icon_btn
|
||||
):
|
||||
size_policy = widget.sizePolicy()
|
||||
size_policy.setVerticalPolicy(size_policy.MinimumExpanding)
|
||||
widget.setSizePolicy(size_policy)
|
||||
name_input.clicked.connect(self._mouse_release_callback)
|
||||
icon_btn.clicked.connect(self._mouse_release_callback)
|
||||
dialog.finished.connect(self._on_dialog_finish)
|
||||
|
||||
self._dialog = dialog
|
||||
self._name_input = name_input
|
||||
self._icon_btn = icon_btn
|
||||
|
||||
self._origin_value = []
|
||||
self._origin_selection = []
|
||||
|
|
@ -406,10 +428,9 @@ class AssetsField(BaseClickableFrame):
|
|||
self._set_state_property(state)
|
||||
|
||||
def _set_state_property(self, state):
|
||||
current_value = self._name_input.property("state")
|
||||
if current_value != state:
|
||||
self._name_input.setProperty("state", state)
|
||||
self._name_input.style().polish(self._name_input)
|
||||
set_style_property(self, "state", state)
|
||||
set_style_property(self._name_input, "state", state)
|
||||
set_style_property(self._icon_btn, "state", state)
|
||||
|
||||
def is_valid(self):
|
||||
"""Is asset valid."""
|
||||
|
|
@ -842,6 +863,8 @@ class VariantInputWidget(PlaceholderLineEdit):
|
|||
|
||||
self._ignore_value_change = True
|
||||
|
||||
self._has_value_changed = False
|
||||
|
||||
self._origin_value = list(variants)
|
||||
self._current_value = list(variants)
|
||||
|
||||
|
|
@ -892,11 +915,23 @@ class MultipleItemWidget(QtWidgets.QWidget):
|
|||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(view)
|
||||
|
||||
model.rowsInserted.connect(self._on_insert)
|
||||
|
||||
self._view = view
|
||||
self._model = model
|
||||
|
||||
self._value = []
|
||||
|
||||
def _on_insert(self):
|
||||
self._update_size()
|
||||
|
||||
def _update_size(self):
|
||||
model = self._view.model()
|
||||
if model.rowCount() == 0:
|
||||
return
|
||||
height = self._view.sizeHintForRow(0)
|
||||
self.setMaximumHeight(height + (2 * self._view.spacing()))
|
||||
|
||||
def showEvent(self, event):
|
||||
super(MultipleItemWidget, self).showEvent(event)
|
||||
tmp_item = None
|
||||
|
|
@ -904,13 +939,15 @@ class MultipleItemWidget(QtWidgets.QWidget):
|
|||
# Add temp item to be able calculate maximum height of widget
|
||||
tmp_item = QtGui.QStandardItem("tmp")
|
||||
self._model.appendRow(tmp_item)
|
||||
|
||||
height = self._view.sizeHintForRow(0)
|
||||
self.setMaximumHeight(height + (2 * self._view.spacing()))
|
||||
self._update_size()
|
||||
|
||||
if tmp_item is not None:
|
||||
self._model.clear()
|
||||
|
||||
def resizeEvent(self, event):
|
||||
super(MultipleItemWidget, self).resizeEvent(event)
|
||||
self._update_size()
|
||||
|
||||
def set_value(self, value=None):
|
||||
"""Set value/s of currently selected instance."""
|
||||
if value is None:
|
||||
|
|
@ -1235,7 +1272,11 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
)
|
||||
|
||||
content_widget = QtWidgets.QWidget(self._scroll_area)
|
||||
content_layout = QtWidgets.QFormLayout(content_widget)
|
||||
content_layout = QtWidgets.QGridLayout(content_widget)
|
||||
content_layout.setColumnStretch(0, 0)
|
||||
content_layout.setColumnStretch(1, 1)
|
||||
|
||||
row = 0
|
||||
for attr_def, attr_instances, values in result:
|
||||
widget = create_widget_for_attr_def(attr_def, content_widget)
|
||||
if attr_def.is_value_def:
|
||||
|
|
@ -1246,10 +1287,28 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
|
|||
else:
|
||||
widget.set_value(values, True)
|
||||
|
||||
label = attr_def.label or attr_def.key
|
||||
content_layout.addRow(label, widget)
|
||||
widget.value_changed.connect(self._input_value_changed)
|
||||
expand_cols = 2
|
||||
if attr_def.is_value_def and attr_def.is_label_horizontal:
|
||||
expand_cols = 1
|
||||
|
||||
col_num = 2 - expand_cols
|
||||
|
||||
label = attr_def.label or attr_def.key
|
||||
if label:
|
||||
label_widget = QtWidgets.QLabel(label, self)
|
||||
content_layout.addWidget(
|
||||
label_widget, row, 0, 1, expand_cols
|
||||
)
|
||||
if not attr_def.is_label_horizontal:
|
||||
row += 1
|
||||
|
||||
content_layout.addWidget(
|
||||
widget, row, col_num, 1, expand_cols
|
||||
)
|
||||
|
||||
row += 1
|
||||
|
||||
widget.value_changed.connect(self._input_value_changed)
|
||||
self._attr_def_id_to_instances[attr_def.id] = attr_instances
|
||||
self._attr_def_id_to_attr_def[attr_def.id] = attr_def
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class DropDataFrame(QtWidgets.QFrame):
|
|||
}
|
||||
|
||||
sequence_types = [
|
||||
".bgeo", ".vdb"
|
||||
".bgeo", ".vdb", ".bgeosc", ".bgeogz"
|
||||
]
|
||||
|
||||
def __init__(self, parent):
|
||||
|
|
|
|||
|
|
@ -589,10 +589,12 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
view = AssetsView(self)
|
||||
view.setModel(proxy)
|
||||
|
||||
header_widget = QtWidgets.QWidget(self)
|
||||
|
||||
current_asset_icon = qtawesome.icon(
|
||||
"fa.arrow-down", color=get_default_tools_icon_color()
|
||||
)
|
||||
current_asset_btn = QtWidgets.QPushButton(self)
|
||||
current_asset_btn = QtWidgets.QPushButton(header_widget)
|
||||
current_asset_btn.setIcon(current_asset_icon)
|
||||
current_asset_btn.setToolTip("Go to Asset from current Session")
|
||||
# Hide by default
|
||||
|
|
@ -601,25 +603,35 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
refresh_icon = qtawesome.icon(
|
||||
"fa.refresh", color=get_default_tools_icon_color()
|
||||
)
|
||||
refresh_btn = QtWidgets.QPushButton(self)
|
||||
refresh_btn = QtWidgets.QPushButton(header_widget)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
refresh_btn.setToolTip("Refresh items")
|
||||
|
||||
filter_input = PlaceholderLineEdit(self)
|
||||
filter_input = PlaceholderLineEdit(header_widget)
|
||||
filter_input.setPlaceholderText("Filter assets..")
|
||||
|
||||
# Header
|
||||
header_layout = QtWidgets.QHBoxLayout()
|
||||
header_layout = QtWidgets.QHBoxLayout(header_widget)
|
||||
header_layout.setContentsMargins(0, 0, 0, 0)
|
||||
header_layout.addWidget(filter_input)
|
||||
header_layout.addWidget(current_asset_btn)
|
||||
header_layout.addWidget(refresh_btn)
|
||||
|
||||
# Make header widgets expand vertically if there is a place
|
||||
for widget in (
|
||||
current_asset_btn,
|
||||
refresh_btn,
|
||||
filter_input,
|
||||
):
|
||||
size_policy = widget.sizePolicy()
|
||||
size_policy.setVerticalPolicy(size_policy.MinimumExpanding)
|
||||
widget.setSizePolicy(size_policy)
|
||||
|
||||
# Layout
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(4)
|
||||
layout.addLayout(header_layout)
|
||||
layout.addWidget(view)
|
||||
layout.addWidget(header_widget, 0)
|
||||
layout.addWidget(view, 1)
|
||||
|
||||
# Signals/Slots
|
||||
filter_input.textChanged.connect(self._on_filter_text_change)
|
||||
|
|
@ -630,6 +642,8 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
current_asset_btn.clicked.connect(self._on_current_asset_click)
|
||||
view.doubleClicked.connect(self.double_clicked)
|
||||
|
||||
self._header_widget = header_widget
|
||||
self._filter_input = filter_input
|
||||
self._refresh_btn = refresh_btn
|
||||
self._current_asset_btn = current_asset_btn
|
||||
self._model = model
|
||||
|
|
@ -637,8 +651,14 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
self._view = view
|
||||
self._last_project_name = None
|
||||
|
||||
self._last_btns_height = None
|
||||
|
||||
self.model_selection = {}
|
||||
|
||||
@property
|
||||
def header_widget(self):
|
||||
return self._header_widget
|
||||
|
||||
def _create_source_model(self):
|
||||
model = AssetModel(dbcon=self.dbcon, parent=self)
|
||||
model.refreshed.connect(self._on_model_refresh)
|
||||
|
|
@ -669,6 +689,7 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
This separation gives ability to override this method and use it
|
||||
in differnt way.
|
||||
"""
|
||||
|
||||
self.set_current_session_asset()
|
||||
|
||||
def set_current_session_asset(self):
|
||||
|
|
@ -681,6 +702,7 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
Some tools may have their global refresh button or do not support
|
||||
refresh at all.
|
||||
"""
|
||||
|
||||
if visible is None:
|
||||
visible = not self._refresh_btn.isVisible()
|
||||
self._refresh_btn.setVisible(visible)
|
||||
|
|
@ -690,6 +712,7 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
|
||||
Not all tools support using of current context asset.
|
||||
"""
|
||||
|
||||
if visible is None:
|
||||
visible = not self._current_asset_btn.isVisible()
|
||||
self._current_asset_btn.setVisible(visible)
|
||||
|
|
@ -723,6 +746,7 @@ class AssetsWidget(QtWidgets.QWidget):
|
|||
so if you're modifying model keep in mind that this method should be
|
||||
called when refresh is done.
|
||||
"""
|
||||
|
||||
self._proxy.sort(0)
|
||||
self._set_loading_state(loading=False, empty=not has_item)
|
||||
self.refreshed.emit()
|
||||
|
|
@ -767,6 +791,7 @@ class SingleSelectAssetsWidget(AssetsWidget):
|
|||
|
||||
Contain single selection specific api methods.
|
||||
"""
|
||||
|
||||
def get_selected_asset_id(self):
|
||||
"""Currently selected asset id."""
|
||||
selection_model = self._view.selectionModel()
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class FilesModel(QtGui.QStandardItemModel):
|
|||
item = QtGui.QStandardItem()
|
||||
item_id = str(uuid.uuid4())
|
||||
item.setData(item_id, ITEM_ID_ROLE)
|
||||
item.setData(file_item.label, ITEM_LABEL_ROLE)
|
||||
item.setData(file_item.label or "< empty >", ITEM_LABEL_ROLE)
|
||||
item.setData(file_item.filenames, FILENAMES_ROLE)
|
||||
item.setData(file_item.directory, DIRPATH_ROLE)
|
||||
item.setData(icon_pixmap, ITEM_ICON_ROLE)
|
||||
|
|
@ -251,7 +251,7 @@ class FilesProxyModel(QtCore.QSortFilterProxyModel):
|
|||
|
||||
|
||||
class ItemWidget(QtWidgets.QWidget):
|
||||
split_requested = QtCore.Signal(str)
|
||||
context_menu_requested = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(
|
||||
self, item_id, label, pixmap_icon, is_sequence, multivalue, parent=None
|
||||
|
|
@ -316,19 +316,9 @@ class ItemWidget(QtWidgets.QWidget):
|
|||
self._update_btn_size()
|
||||
|
||||
def _on_actions_clicked(self):
|
||||
menu = QtWidgets.QMenu(self._split_btn)
|
||||
|
||||
action = QtWidgets.QAction("Split sequence", menu)
|
||||
action.triggered.connect(self._on_split_sequence)
|
||||
|
||||
menu.addAction(action)
|
||||
|
||||
pos = self._split_btn.rect().bottomLeft()
|
||||
point = self._split_btn.mapToGlobal(pos)
|
||||
menu.popup(point)
|
||||
|
||||
def _on_split_sequence(self):
|
||||
self.split_requested.emit(self._item_id)
|
||||
self.context_menu_requested.emit(point)
|
||||
|
||||
|
||||
class InViewButton(IconButton):
|
||||
|
|
@ -339,6 +329,7 @@ class FilesView(QtWidgets.QListView):
|
|||
"""View showing instances and their groups."""
|
||||
|
||||
remove_requested = QtCore.Signal()
|
||||
context_menu_requested = QtCore.Signal(QtCore.QPoint)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(FilesView, self).__init__(*args, **kwargs)
|
||||
|
|
@ -347,6 +338,7 @@ class FilesView(QtWidgets.QListView):
|
|||
self.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.ExtendedSelection
|
||||
)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
remove_btn = InViewButton(self)
|
||||
pix_enabled = paint_image_with_color(
|
||||
|
|
@ -361,6 +353,7 @@ class FilesView(QtWidgets.QListView):
|
|||
remove_btn.setEnabled(False)
|
||||
|
||||
remove_btn.clicked.connect(self._on_remove_clicked)
|
||||
self.customContextMenuRequested.connect(self._on_context_menu_request)
|
||||
|
||||
self._remove_btn = remove_btn
|
||||
|
||||
|
|
@ -397,6 +390,12 @@ class FilesView(QtWidgets.QListView):
|
|||
selected_item_ids.add(instance_id)
|
||||
return selected_item_ids
|
||||
|
||||
def has_selected_sequence(self):
|
||||
for index in self.selectionModel().selectedIndexes():
|
||||
if index.data(IS_SEQUENCE_ROLE):
|
||||
return True
|
||||
return False
|
||||
|
||||
def event(self, event):
|
||||
if event.type() == QtCore.QEvent.KeyPress:
|
||||
if (
|
||||
|
|
@ -408,6 +407,12 @@ class FilesView(QtWidgets.QListView):
|
|||
|
||||
return super(FilesView, self).event(event)
|
||||
|
||||
def _on_context_menu_request(self, pos):
|
||||
index = self.indexAt(pos)
|
||||
if index.isValid():
|
||||
point = self.viewport().mapToGlobal(pos)
|
||||
self.context_menu_requested.emit(point)
|
||||
|
||||
def _on_selection_change(self):
|
||||
self._remove_btn.setEnabled(self.has_selected_item_ids())
|
||||
|
||||
|
|
@ -456,6 +461,9 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
files_proxy_model.rowsInserted.connect(self._on_rows_inserted)
|
||||
files_proxy_model.rowsRemoved.connect(self._on_rows_removed)
|
||||
files_view.remove_requested.connect(self._on_remove_requested)
|
||||
files_view.context_menu_requested.connect(
|
||||
self._on_context_menu_requested
|
||||
)
|
||||
self._in_set_value = False
|
||||
self._single_item = single_item
|
||||
self._multivalue = False
|
||||
|
|
@ -504,7 +512,9 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
return file_items
|
||||
if file_items:
|
||||
return file_items[0]
|
||||
return FileDefItem.create_empty_item()
|
||||
|
||||
empty_item = FileDefItem.create_empty_item()
|
||||
return empty_item.to_dict()
|
||||
|
||||
def set_filters(self, folders_allowed, exts_filter):
|
||||
self._files_proxy_model.set_allow_folders(folders_allowed)
|
||||
|
|
@ -527,7 +537,9 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
is_sequence,
|
||||
self._multivalue
|
||||
)
|
||||
widget.split_requested.connect(self._on_split_request)
|
||||
widget.context_menu_requested.connect(
|
||||
self._on_context_menu_requested
|
||||
)
|
||||
self._files_view.setIndexWidget(index, widget)
|
||||
self._files_proxy_model.setData(
|
||||
index, widget.sizeHint(), QtCore.Qt.SizeHintRole
|
||||
|
|
@ -559,17 +571,22 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
if not self._in_set_value:
|
||||
self.value_changed.emit()
|
||||
|
||||
def _on_split_request(self, item_id):
|
||||
def _on_split_request(self):
|
||||
if self._multivalue:
|
||||
return
|
||||
|
||||
file_item = self._files_model.get_file_item_by_id(item_id)
|
||||
if not file_item:
|
||||
item_ids = self._files_view.get_selected_item_ids()
|
||||
if not item_ids:
|
||||
return
|
||||
|
||||
new_items = file_item.split_sequence()
|
||||
self._remove_item_by_ids([item_id])
|
||||
self._add_filepaths(new_items)
|
||||
for item_id in item_ids:
|
||||
file_item = self._files_model.get_file_item_by_id(item_id)
|
||||
if not file_item:
|
||||
return
|
||||
|
||||
new_items = file_item.split_sequence()
|
||||
self._add_filepaths(new_items)
|
||||
self._remove_item_by_ids(item_ids)
|
||||
|
||||
def _on_remove_requested(self):
|
||||
if self._multivalue:
|
||||
|
|
@ -579,6 +596,23 @@ class FilesWidget(QtWidgets.QFrame):
|
|||
if items_to_delete:
|
||||
self._remove_item_by_ids(items_to_delete)
|
||||
|
||||
def _on_context_menu_requested(self, pos):
|
||||
if self._multivalue:
|
||||
return
|
||||
|
||||
menu = QtWidgets.QMenu(self._files_view)
|
||||
|
||||
if self._files_view.has_selected_sequence():
|
||||
split_action = QtWidgets.QAction("Split sequence", menu)
|
||||
split_action.triggered.connect(self._on_split_request)
|
||||
menu.addAction(split_action)
|
||||
|
||||
remove_action = QtWidgets.QAction("Remove", menu)
|
||||
remove_action.triggered.connect(self._on_remove_requested)
|
||||
menu.addAction(remove_action)
|
||||
|
||||
menu.popup(pos)
|
||||
|
||||
def sizeHint(self):
|
||||
# Get size hints of widget and visible widgets
|
||||
result = super(FilesWidget, self).sizeHint()
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ class AttributeDefinitionsWidget(QtWidgets.QWidget):
|
|||
layout.deleteLater()
|
||||
|
||||
new_layout = QtWidgets.QGridLayout()
|
||||
new_layout.setColumnStretch(0, 0)
|
||||
new_layout.setColumnStretch(1, 1)
|
||||
self.setLayout(new_layout)
|
||||
|
||||
def set_attr_defs(self, attr_defs):
|
||||
|
|
|
|||
|
|
@ -1,494 +0,0 @@
|
|||
import os
|
||||
import getpass
|
||||
import platform
|
||||
|
||||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
from avalon import style
|
||||
import ftrack_api
|
||||
|
||||
|
||||
class Project_name_getUI(QtWidgets.QWidget):
|
||||
'''
|
||||
Project setting ui: here all the neceserry ui widgets are created
|
||||
they are going to be used i later proces for dynamic linking of project
|
||||
in list to project's attributes
|
||||
'''
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(Project_name_getUI, self).__init__(parent)
|
||||
|
||||
self.platform = platform.system()
|
||||
self.new_index = 0
|
||||
# get projects from ftrack
|
||||
self.session = ftrack_api.Session()
|
||||
self.projects_from_ft = self.session.query(
|
||||
'Project where status is active')
|
||||
self.disks_from_ft = self.session.query('Disk')
|
||||
self.schemas_from_ft = self.session.query('ProjectSchema')
|
||||
self.projects = self._get_projects_ftrack()
|
||||
|
||||
# define window geometry
|
||||
self.setWindowTitle('Set project attributes')
|
||||
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.resize(550, 340)
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
|
||||
# define disk combobox widget
|
||||
self.disks = self._get_all_disks()
|
||||
self.disk_combobox_label = QtWidgets.QLabel('Destination storage:')
|
||||
self.disk_combobox = QtWidgets.QComboBox()
|
||||
|
||||
# define schema combobox widget
|
||||
self.schemas = self._get_all_schemas()
|
||||
self.schema_combobox_label = QtWidgets.QLabel('Project schema:')
|
||||
self.schema_combobox = QtWidgets.QComboBox()
|
||||
|
||||
# define fps widget
|
||||
self.fps_label = QtWidgets.QLabel('Fps:')
|
||||
self.fps_label.setAlignment(
|
||||
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.fps = QtWidgets.QLineEdit()
|
||||
|
||||
# define project dir widget
|
||||
self.project_dir_label = QtWidgets.QLabel('Project dir:')
|
||||
self.project_dir_label.setAlignment(
|
||||
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.project_dir = QtWidgets.QLineEdit()
|
||||
|
||||
self.project_path_label = QtWidgets.QLabel(
|
||||
'Project_path (if not then created):')
|
||||
self.project_path_label.setAlignment(
|
||||
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
||||
project_path_font = QtGui.QFont(
|
||||
"Helvetica [Cronyx]", 12, QtGui.QFont.Bold)
|
||||
self.project_path = QtWidgets.QLabel()
|
||||
self.project_path.setObjectName('nom_plan_label')
|
||||
self.project_path.setStyleSheet(
|
||||
'QtWidgets.QLabel#nom_plan_label {color: red}')
|
||||
self.project_path.setAlignment(
|
||||
QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
|
||||
self.project_path.setFont(project_path_font)
|
||||
|
||||
# define handles widget
|
||||
self.handles_label = QtWidgets.QLabel('Handles:')
|
||||
self.handles_label.setAlignment(
|
||||
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.handles = QtWidgets.QLineEdit()
|
||||
|
||||
# define resolution widget
|
||||
self.resolution_w_label = QtWidgets.QLabel('W:')
|
||||
self.resolution_w = QtWidgets.QLineEdit()
|
||||
self.resolution_h_label = QtWidgets.QLabel('H:')
|
||||
self.resolution_h = QtWidgets.QLineEdit()
|
||||
|
||||
devider = QtWidgets.QFrame()
|
||||
# devider.Shape(QFrame.HLine)
|
||||
devider.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
devider.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
|
||||
self.generate_lines()
|
||||
|
||||
# define push buttons
|
||||
self.set_pushbutton = QtWidgets.QPushButton('Set project')
|
||||
self.cancel_pushbutton = QtWidgets.QPushButton('Cancel')
|
||||
|
||||
# definition of layouts
|
||||
############################################
|
||||
action_layout = QtWidgets.QHBoxLayout()
|
||||
action_layout.addWidget(self.set_pushbutton)
|
||||
action_layout.addWidget(self.cancel_pushbutton)
|
||||
|
||||
# schema property
|
||||
schema_layout = QtWidgets.QGridLayout()
|
||||
schema_layout.addWidget(self.schema_combobox, 0, 1)
|
||||
schema_layout.addWidget(self.schema_combobox_label, 0, 0)
|
||||
|
||||
# storage property
|
||||
storage_layout = QtWidgets.QGridLayout()
|
||||
storage_layout.addWidget(self.disk_combobox, 0, 1)
|
||||
storage_layout.addWidget(self.disk_combobox_label, 0, 0)
|
||||
|
||||
# fps property
|
||||
fps_layout = QtWidgets.QGridLayout()
|
||||
fps_layout.addWidget(self.fps, 1, 1)
|
||||
fps_layout.addWidget(self.fps_label, 1, 0)
|
||||
|
||||
# project dir property
|
||||
project_dir_layout = QtWidgets.QGridLayout()
|
||||
project_dir_layout.addWidget(self.project_dir, 1, 1)
|
||||
project_dir_layout.addWidget(self.project_dir_label, 1, 0)
|
||||
|
||||
# project path property
|
||||
project_path_layout = QtWidgets.QGridLayout()
|
||||
spacer_1_item = QtWidgets.QSpacerItem(10, 10)
|
||||
project_path_layout.addItem(spacer_1_item, 0, 1)
|
||||
project_path_layout.addWidget(self.project_path_label, 1, 1)
|
||||
project_path_layout.addWidget(self.project_path, 2, 1)
|
||||
spacer_2_item = QtWidgets.QSpacerItem(20, 20)
|
||||
project_path_layout.addItem(spacer_2_item, 3, 1)
|
||||
|
||||
# handles property
|
||||
handles_layout = QtWidgets.QGridLayout()
|
||||
handles_layout.addWidget(self.handles, 1, 1)
|
||||
handles_layout.addWidget(self.handles_label, 1, 0)
|
||||
|
||||
# resolution property
|
||||
resolution_layout = QtWidgets.QGridLayout()
|
||||
resolution_layout.addWidget(self.resolution_w_label, 1, 1)
|
||||
resolution_layout.addWidget(self.resolution_w, 2, 1)
|
||||
resolution_layout.addWidget(self.resolution_h_label, 1, 2)
|
||||
resolution_layout.addWidget(self.resolution_h, 2, 2)
|
||||
|
||||
# form project property layout
|
||||
p_layout = QtWidgets.QGridLayout()
|
||||
p_layout.addLayout(storage_layout, 1, 0)
|
||||
p_layout.addLayout(schema_layout, 2, 0)
|
||||
p_layout.addLayout(project_dir_layout, 3, 0)
|
||||
p_layout.addLayout(fps_layout, 4, 0)
|
||||
p_layout.addLayout(handles_layout, 5, 0)
|
||||
p_layout.addLayout(resolution_layout, 6, 0)
|
||||
p_layout.addWidget(devider, 7, 0)
|
||||
spacer_item = QtWidgets.QSpacerItem(
|
||||
150,
|
||||
40,
|
||||
QtWidgets.QSizePolicy.Minimum,
|
||||
QtWidgets.QSizePolicy.Expanding
|
||||
)
|
||||
p_layout.addItem(spacer_item, 8, 0)
|
||||
|
||||
# form with list to one layout with project property
|
||||
list_layout = QtWidgets.QGridLayout()
|
||||
list_layout.addLayout(p_layout, 1, 0)
|
||||
list_layout.addWidget(self.listWidget, 1, 1)
|
||||
|
||||
root_layout = QtWidgets.QVBoxLayout()
|
||||
root_layout.addLayout(project_path_layout)
|
||||
root_layout.addWidget(devider)
|
||||
root_layout.addLayout(list_layout)
|
||||
root_layout.addLayout(action_layout)
|
||||
|
||||
self.setLayout(root_layout)
|
||||
|
||||
def generate_lines(self):
|
||||
'''
|
||||
Will generate lines of project list
|
||||
'''
|
||||
|
||||
self.listWidget = QtWidgets.QListWidget()
|
||||
for self.index, p in enumerate(self.projects):
|
||||
item = QtWidgets.QListWidgetItem("{full_name}".format(**p))
|
||||
# item.setSelected(False)
|
||||
self.listWidget.addItem(item)
|
||||
print(self.listWidget.indexFromItem(item))
|
||||
# self.listWidget.setCurrentItem(self.listWidget.itemFromIndex(1))
|
||||
|
||||
# add options to schemas widget
|
||||
self.schema_combobox.addItems(self.schemas)
|
||||
|
||||
# add options to disk widget
|
||||
self.disk_combobox.addItems(self.disks)
|
||||
|
||||
# populate content of project info widgets
|
||||
self.projects[1] = self._fill_project_attributes_widgets(p, None)
|
||||
|
||||
def _fill_project_attributes_widgets(self, p=None, index=None):
|
||||
'''
|
||||
will generate actual informations wich are saved on ftrack
|
||||
'''
|
||||
|
||||
if index is None:
|
||||
self.new_index = 1
|
||||
|
||||
if not p:
|
||||
pass
|
||||
# change schema selection
|
||||
for i, schema in enumerate(self.schemas):
|
||||
if p['project_schema']['name'] in schema:
|
||||
break
|
||||
self.schema_combobox.setCurrentIndex(i)
|
||||
|
||||
disk_name, disk_path = self._build_disk_path()
|
||||
for i, disk in enumerate(self.disks):
|
||||
if disk_name in disk:
|
||||
break
|
||||
# change disk selection
|
||||
self.disk_combobox.setCurrentIndex(i)
|
||||
|
||||
# change project_dir selection
|
||||
if "{root}".format(**p):
|
||||
self.project_dir.setPlaceholderText("{root}".format(**p))
|
||||
else:
|
||||
print("not root so it was replaced with name")
|
||||
self.project_dir.setPlaceholderText("{name}".format(**p))
|
||||
p['root'] = p['name']
|
||||
|
||||
# set project path to show where it will be created
|
||||
self.project_path.setText(
|
||||
os.path.join(self.disks[i].split(' ')[-1],
|
||||
self.project_dir.text()))
|
||||
|
||||
# change fps selection
|
||||
self.fps.setPlaceholderText("{custom_attributes[fps]}".format(**p))
|
||||
|
||||
# change handles selection
|
||||
self.handles.setPlaceholderText(
|
||||
"{custom_attributes[handles]}".format(**p))
|
||||
|
||||
# change resolution selection
|
||||
self.resolution_w.setPlaceholderText(
|
||||
"{custom_attributes[resolution_width]}".format(**p))
|
||||
self.resolution_h.setPlaceholderText(
|
||||
"{custom_attributes[resolution_height]}".format(**p))
|
||||
|
||||
self.update_disk()
|
||||
|
||||
return p
|
||||
|
||||
def fix_project_path_literals(self, dir):
|
||||
return dir.replace(' ', '_').lower()
|
||||
|
||||
def update_disk(self):
|
||||
disk = self.disk_combobox.currentText().split(' ')[-1]
|
||||
|
||||
dir = self.project_dir.text()
|
||||
if not dir:
|
||||
dir = "{root}".format(**self.projects[self.new_index])
|
||||
self.projects[self.new_index]['project_path'] = os.path.normpath(
|
||||
self.fix_project_path_literals(os.path.join(disk, dir)))
|
||||
else:
|
||||
self.projects[self.new_index]['project_path'] = os.path.normpath(
|
||||
self.fix_project_path_literals(os.path.join(disk, dir)))
|
||||
|
||||
self.projects[self.new_index]['disk'] = self.disks_from_ft[
|
||||
self.disk_combobox.currentIndex()]
|
||||
self.projects[self.new_index]['disk_id'] = self.projects[
|
||||
self.new_index]['disk']['id']
|
||||
|
||||
# set project path to show where it will be created
|
||||
self.project_path.setText(
|
||||
self.projects[self.new_index]['project_path'])
|
||||
|
||||
def update_resolution(self):
|
||||
# update all values in resolution
|
||||
if self.resolution_w.text():
|
||||
self.projects[self.new_index]['custom_attributes'][
|
||||
"resolutionWidth"] = int(self.resolution_w.text())
|
||||
if self.resolution_h.text():
|
||||
self.projects[self.new_index]['custom_attributes'][
|
||||
"resolutionHeight"] = int(self.resolution_h.text())
|
||||
|
||||
def _update_attributes_by_list_selection(self):
|
||||
# generate actual selection index
|
||||
self.new_index = self.listWidget.currentRow()
|
||||
self.project_dir.setText('')
|
||||
self.fps.setText('')
|
||||
self.handles.setText('')
|
||||
self.resolution_w.setText('')
|
||||
self.resolution_h.setText('')
|
||||
|
||||
# update project properities widgets and write changes
|
||||
# into project dictionaries
|
||||
self.projects[self.new_index] = self._fill_project_attributes_widgets(
|
||||
self.projects[self.new_index], self.new_index)
|
||||
|
||||
self.update_disk()
|
||||
|
||||
def _build_disk_path(self):
|
||||
if self.platform == "Windows":
|
||||
print(self.projects[self.index].keys())
|
||||
print(self.projects[self.new_index]['disk'])
|
||||
return self.projects[self.new_index]['disk'][
|
||||
'name'], self.projects[self.new_index]['disk']['windows']
|
||||
else:
|
||||
return self.projects[self.new_index]['disk'][
|
||||
'name'], self.projects[self.new_index]['disk']['unix']
|
||||
|
||||
def _get_all_schemas(self):
|
||||
schemas_list = []
|
||||
|
||||
for s in self.schemas_from_ft:
|
||||
# print d.keys()
|
||||
# if 'Pokus' in s['name']:
|
||||
# continue
|
||||
schemas_list.append('{}'.format(s['name']))
|
||||
print("\nschemas in ftrack: {}\n".format(schemas_list))
|
||||
return schemas_list
|
||||
|
||||
def _get_all_disks(self):
|
||||
disks_list = []
|
||||
for d in self.disks_from_ft:
|
||||
# print d.keys()
|
||||
if self.platform == "Windows":
|
||||
if 'Local drive' in d['name']:
|
||||
d['windows'] = os.path.join(d['windows'],
|
||||
os.getenv('USERNAME')
|
||||
or os.getenv('USER')
|
||||
or os.getenv('LOGNAME'))
|
||||
disks_list.append('"{}" at {}'.format(d['name'], d['windows']))
|
||||
else:
|
||||
if 'Local drive' in d['name']:
|
||||
d['unix'] = os.path.join(d['unix'], getpass.getuser())
|
||||
disks_list.append('"{}" at {}'.format(d['name'], d['unix']))
|
||||
return disks_list
|
||||
|
||||
def _get_projects_ftrack(self):
|
||||
|
||||
projects_lst = []
|
||||
for project in self.projects_from_ft:
|
||||
# print project.keys()
|
||||
projects_dict = {}
|
||||
|
||||
for k in project.keys():
|
||||
''' # TODO: delete this in production version '''
|
||||
|
||||
# if 'test' not in project['name']:
|
||||
# continue
|
||||
|
||||
# print '{}: {}\n'.format(k, project[k])
|
||||
|
||||
if '_link' == k:
|
||||
# print project[k]
|
||||
content = project[k]
|
||||
for kc in content[0].keys():
|
||||
if content[0]['name']:
|
||||
content[0][kc] = content[0][kc].encode(
|
||||
'ascii', 'ignore').decode('ascii')
|
||||
print('{}: {}\n'.format(kc, content[0][kc]))
|
||||
projects_dict[k] = content
|
||||
print(project[k])
|
||||
print(projects_dict[k])
|
||||
elif 'root' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'disk' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'name' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k].encode(
|
||||
'ascii', 'ignore').decode('ascii')
|
||||
elif 'disk_id' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'id' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'full_name' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k].encode(
|
||||
'ascii', 'ignore').decode('ascii')
|
||||
elif 'project_schema_id' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'project_schema' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
elif 'custom_attributes' == k:
|
||||
print('{}: {}\n'.format(k, project[k]))
|
||||
projects_dict[k] = project[k]
|
||||
else:
|
||||
pass
|
||||
|
||||
if projects_dict:
|
||||
projects_lst.append(projects_dict)
|
||||
|
||||
return projects_lst
|
||||
|
||||
|
||||
class Project_name_get(Project_name_getUI):
|
||||
def __init__(self, parent=None):
|
||||
super(Project_name_get, self).__init__(parent)
|
||||
# self.input_project_name.textChanged.connect(self.input_project_name.placeholderText)
|
||||
|
||||
self.set_pushbutton.clicked.connect(lambda: self.execute())
|
||||
self.cancel_pushbutton.clicked.connect(self.close)
|
||||
|
||||
self.listWidget.itemSelectionChanged.connect(
|
||||
self._update_attributes_by_list_selection)
|
||||
self.disk_combobox.currentIndexChanged.connect(self.update_disk)
|
||||
self.schema_combobox.currentIndexChanged.connect(self.update_schema)
|
||||
self.project_dir.textChanged.connect(self.update_disk)
|
||||
self.fps.textChanged.connect(self.update_fps)
|
||||
self.handles.textChanged.connect(self.update_handles)
|
||||
self.resolution_w.textChanged.connect(self.update_resolution)
|
||||
self.resolution_h.textChanged.connect(self.update_resolution)
|
||||
|
||||
def update_handles(self):
|
||||
self.projects[self.new_index]['custom_attributes']['handles'] = int(
|
||||
self.handles.text())
|
||||
|
||||
def update_fps(self):
|
||||
self.projects[self.new_index]['custom_attributes']['fps'] = int(
|
||||
self.fps.text())
|
||||
|
||||
def update_schema(self):
|
||||
self.projects[self.new_index]['project_schema'] = self.schemas_from_ft[
|
||||
self.schema_combobox.currentIndex()]
|
||||
self.projects[self.new_index]['project_schema_id'] = self.projects[
|
||||
self.new_index]['project_schema']['id']
|
||||
|
||||
def execute(self):
|
||||
# import ft_utils
|
||||
# import hiero
|
||||
# get the project which has been selected
|
||||
print("well and what")
|
||||
# set the project as context and create entity
|
||||
# entity is task created with the name of user which is creating it
|
||||
|
||||
# get the project_path and create dir if there is not any
|
||||
print(self.projects[self.new_index]['project_path'].replace(
|
||||
self.disk_combobox.currentText().split(' ')[-1].lower(), ''))
|
||||
|
||||
# get the schema and recreate a starting project regarding the selection
|
||||
# set_hiero_template(project_schema=self.projects[self.new_index][
|
||||
# 'project_schema']['name'])
|
||||
|
||||
# set all project properities
|
||||
# project = hiero.core.Project()
|
||||
# project.setFramerate(
|
||||
# int(self.projects[self.new_index]['custom_attributes']['fps']))
|
||||
# project.projectRoot()
|
||||
# print 'handles: {}'.format(self.projects[self.new_index]['custom_attributes']['handles'])
|
||||
# print 'resolution_width: {}'.format(self.projects[self.new_index]['custom_attributes']["resolutionWidth"])
|
||||
# print 'resolution_width: {}'.format(self.projects[self.new_index]['custom_attributes']["resolutionHeight"])
|
||||
# print "<< {}".format(self.projects[self.new_index])
|
||||
|
||||
# get path for the hrox file
|
||||
# root = context.data('ftrackData')['Project']['root']
|
||||
# hrox_script_path = ft_utils.getPathsYaml(taskid, templateList=templates, root=root)
|
||||
|
||||
# save the hrox into the correct path
|
||||
self.session.commit()
|
||||
self.close()
|
||||
|
||||
#
|
||||
# def set_hiero_template(project_schema=None):
|
||||
# import hiero
|
||||
# hiero.core.closeAllProjects()
|
||||
# hiero_plugin_path = [
|
||||
# p for p in os.environ['HIERO_PLUGIN_PATH'].split(';')
|
||||
# if 'hiero_plugin_path' in p
|
||||
# ][0]
|
||||
# path = os.path.normpath(
|
||||
# os.path.join(hiero_plugin_path, 'Templates', project_schema + '.hrox'))
|
||||
# print('---> path to template: {}'.format(path))
|
||||
# return hiero.core.openProject(path)
|
||||
|
||||
|
||||
# def set_out_ft_session():
|
||||
# session = ftrack_api.Session()
|
||||
# projects_to_ft = session.query('Project where status is active')
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
panel = Project_name_get()
|
||||
panel.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
81
schema/session-3.0.json
Normal file
81
schema/session-3.0.json
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:session-3.0",
|
||||
"description": "The Avalon environment",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"AVALON_PROJECT",
|
||||
"AVALON_ASSET"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"AVALON_PROJECTS": {
|
||||
"description": "Absolute path to root of project directories",
|
||||
"type": "string",
|
||||
"example": "/nas/projects"
|
||||
},
|
||||
"AVALON_PROJECT": {
|
||||
"description": "Name of project",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Hulk"
|
||||
},
|
||||
"AVALON_ASSET": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"AVALON_TASK": {
|
||||
"description": "Name of task",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "modeling"
|
||||
},
|
||||
"AVALON_APP": {
|
||||
"description": "Name of host",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "maya2016"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "avalon",
|
||||
"default": "avalon"
|
||||
},
|
||||
"AVALON_LABEL": {
|
||||
"description": "Nice name of Avalon, used in e.g. graphical user interfaces",
|
||||
"type": "string",
|
||||
"example": "Mindbender",
|
||||
"default": "Avalon"
|
||||
},
|
||||
"AVALON_TIMEOUT": {
|
||||
"description": "Wherever there is a need for a timeout, this is the default value.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*$",
|
||||
"default": "1000",
|
||||
"example": "1000"
|
||||
},
|
||||
"AVALON_INSTANCE_ID": {
|
||||
"description": "Unique identifier for instances in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.instance",
|
||||
"example": "avalon.instance"
|
||||
},
|
||||
"AVALON_CONTAINER_ID": {
|
||||
"description": "Unique identifier for a loaded representation in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.container",
|
||||
"example": "avalon.container"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
setup.py
4
setup.py
|
|
@ -106,7 +106,9 @@ install_requires = [
|
|||
"dns",
|
||||
# Python defaults (cx_Freeze skip them by default)
|
||||
"dbm",
|
||||
"sqlite3"
|
||||
"sqlite3",
|
||||
"dataclasses",
|
||||
"timeit"
|
||||
]
|
||||
|
||||
includes = []
|
||||
|
|
|
|||
105
start.py
105
start.py
|
|
@ -897,6 +897,56 @@ def _bootstrap_from_code(use_version, use_staging):
|
|||
return version_path
|
||||
|
||||
|
||||
def _boot_validate_versions(use_version, local_version):
|
||||
_print(f">>> Validating version [ {use_version} ]")
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=True)
|
||||
openpype_versions += bootstrap.find_openpype(include_zips=True,
|
||||
staging=False)
|
||||
v: OpenPypeVersion
|
||||
found = [v for v in openpype_versions if str(v) == use_version]
|
||||
if not found:
|
||||
_print(f"!!! Version [ {use_version} ] not found.")
|
||||
list_versions(openpype_versions, local_version)
|
||||
sys.exit(1)
|
||||
|
||||
# print result
|
||||
version_path = bootstrap.get_version_path_from_list(
|
||||
use_version, openpype_versions
|
||||
)
|
||||
valid, message = bootstrap.validate_openpype_version(version_path)
|
||||
_print("{}{}".format(">>> " if valid else "!!! ", message))
|
||||
|
||||
|
||||
def _boot_print_versions(use_staging, local_version, openpype_root):
|
||||
if not use_staging:
|
||||
_print("--- This will list only non-staging versions detected.")
|
||||
_print(" To see staging versions, use --use-staging argument.")
|
||||
else:
|
||||
_print("--- This will list only staging versions detected.")
|
||||
_print(" To see other version, omit --use-staging argument.")
|
||||
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=use_staging)
|
||||
if getattr(sys, 'frozen', False):
|
||||
local_version = bootstrap.get_version(Path(openpype_root))
|
||||
else:
|
||||
local_version = OpenPypeVersion.get_installed_version_str()
|
||||
|
||||
list_versions(openpype_versions, local_version)
|
||||
|
||||
|
||||
def _boot_handle_missing_version(local_version, use_staging, message):
|
||||
_print(message)
|
||||
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
|
||||
openpype_versions = bootstrap.find_openpype(
|
||||
include_zips=True, staging=use_staging
|
||||
)
|
||||
list_versions(openpype_versions, local_version)
|
||||
else:
|
||||
igniter.show_message_dialog("Version not found", message)
|
||||
|
||||
|
||||
def boot():
|
||||
"""Bootstrap OpenPype."""
|
||||
|
||||
|
|
@ -966,30 +1016,7 @@ def boot():
|
|||
local_version = OpenPypeVersion.get_installed_version_str()
|
||||
|
||||
if "validate" in commands:
|
||||
_print(f">>> Validating version [ {use_version} ]")
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=True)
|
||||
openpype_versions += bootstrap.find_openpype(include_zips=True,
|
||||
staging=False)
|
||||
v: OpenPypeVersion
|
||||
found = [v for v in openpype_versions if str(v) == use_version]
|
||||
if not found:
|
||||
_print(f"!!! Version [ {use_version} ] not found.")
|
||||
list_versions(openpype_versions, local_version)
|
||||
sys.exit(1)
|
||||
|
||||
# print result
|
||||
result = bootstrap.validate_openpype_version(
|
||||
bootstrap.get_version_path_from_list(
|
||||
use_version, openpype_versions))
|
||||
|
||||
_print("{}{}".format(
|
||||
">>> " if result[0] else "!!! ",
|
||||
bootstrap.validate_openpype_version(
|
||||
bootstrap.get_version_path_from_list(
|
||||
use_version, openpype_versions)
|
||||
)[1])
|
||||
)
|
||||
_boot_validate_versions(use_version, local_version)
|
||||
sys.exit(1)
|
||||
|
||||
if not openpype_path:
|
||||
|
|
@ -999,21 +1026,7 @@ def boot():
|
|||
os.environ["OPENPYPE_PATH"] = openpype_path
|
||||
|
||||
if "print_versions" in commands:
|
||||
if not use_staging:
|
||||
_print("--- This will list only non-staging versions detected.")
|
||||
_print(" To see staging versions, use --use-staging argument.")
|
||||
else:
|
||||
_print("--- This will list only staging versions detected.")
|
||||
_print(" To see other version, omit --use-staging argument.")
|
||||
_openpype_root = OPENPYPE_ROOT
|
||||
openpype_versions = bootstrap.find_openpype(include_zips=True,
|
||||
staging=use_staging)
|
||||
if getattr(sys, 'frozen', False):
|
||||
local_version = bootstrap.get_version(Path(_openpype_root))
|
||||
else:
|
||||
local_version = OpenPypeVersion.get_installed_version_str()
|
||||
|
||||
list_versions(openpype_versions, local_version)
|
||||
_boot_print_versions(use_staging, local_version, OPENPYPE_ROOT)
|
||||
sys.exit(1)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
|
|
@ -1026,12 +1039,7 @@ def boot():
|
|||
try:
|
||||
version_path = _find_frozen_openpype(use_version, use_staging)
|
||||
except OpenPypeVersionNotFound as exc:
|
||||
message = str(exc)
|
||||
_print(message)
|
||||
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
|
||||
list_versions(openpype_versions, local_version)
|
||||
else:
|
||||
igniter.show_message_dialog("Version not found", message)
|
||||
_boot_handle_missing_version(local_version, use_staging, str(exc))
|
||||
sys.exit(1)
|
||||
|
||||
except RuntimeError as e:
|
||||
|
|
@ -1050,12 +1058,7 @@ def boot():
|
|||
version_path = _bootstrap_from_code(use_version, use_staging)
|
||||
|
||||
except OpenPypeVersionNotFound as exc:
|
||||
message = str(exc)
|
||||
_print(message)
|
||||
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
|
||||
list_versions(openpype_versions, local_version)
|
||||
else:
|
||||
igniter.show_message_dialog("Version not found", message)
|
||||
_boot_handle_missing_version(local_version, use_staging, str(exc))
|
||||
sys.exit(1)
|
||||
|
||||
# set this to point either to `python` from venv in case of live code
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ class ModuleUnitTest(BaseTest):
|
|||
|
||||
Database prepared from dumps with 'db_setup' fixture.
|
||||
"""
|
||||
from avalon.api import AvalonMongoDB
|
||||
from openpype.pipeline import AvalonMongoDB
|
||||
dbcon = AvalonMongoDB()
|
||||
dbcon.Session["AVALON_PROJECT"] = self.TEST_PROJECT_NAME
|
||||
yield dbcon
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue