[Automated] Merged develop into main

This commit is contained in:
ynbot 2023-10-11 05:24:10 +02:00 committed by GitHub
commit 9722fa248b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 421 additions and 46 deletions

View file

@ -35,6 +35,7 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.17.2-nightly.3
- 3.17.2-nightly.2
- 3.17.2-nightly.1
- 3.17.1
@ -134,7 +135,6 @@ body:
- 3.14.10
- 3.14.10-nightly.9
- 3.14.10-nightly.8
- 3.14.10-nightly.7
validations:
required: true
- type: dropdown

View file

@ -422,7 +422,7 @@ def failed_json_default(value):
class ServerCreateOperation(CreateOperation):
"""Opeartion to create an entity.
"""Operation to create an entity.
Args:
project_name (str): On which project operation will happen.
@ -634,7 +634,7 @@ class ServerUpdateOperation(UpdateOperation):
class ServerDeleteOperation(DeleteOperation):
"""Opeartion to delete an entity.
"""Operation to delete an entity.
Args:
project_name (str): On which project operation will happen.
@ -647,7 +647,7 @@ class ServerDeleteOperation(DeleteOperation):
self._session = session
if entity_type == "asset":
entity_type == "folder"
entity_type = "folder"
elif entity_type == "hero_version":
entity_type = "version"

View file

@ -2,7 +2,7 @@ import subprocess
from openpype.lib.applications import PreLaunchHook, LaunchTypes
class LaunchFoundryAppsWindows(PreLaunchHook):
class LaunchNewConsoleApps(PreLaunchHook):
"""Foundry applications have specific way how to launch them.
Nuke is executed "like" python process so it is required to pass
@ -13,13 +13,15 @@ class LaunchFoundryAppsWindows(PreLaunchHook):
# Should be as last hook because must change launch arguments to string
order = 1000
app_groups = {"nuke", "nukeassist", "nukex", "hiero", "nukestudio"}
app_groups = {
"nuke", "nukeassist", "nukex", "hiero", "nukestudio", "mayapy"
}
platforms = {"windows"}
launch_types = {LaunchTypes.local}
def execute(self):
# Change `creationflags` to CREATE_NEW_CONSOLE
# - on Windows nuke will create new window using its console
# - on Windows some apps will create new window using its console
# Set `stdout` and `stderr` to None so new created console does not
# have redirected output to DEVNULL in build
self.launch_context.kwargs.update({

View file

@ -31,7 +31,7 @@ class InstallPySideToBlender(PreLaunchHook):
def inner_execute(self):
# Get blender's python directory
version_regex = re.compile(r"^[2-3]\.[0-9]+$")
version_regex = re.compile(r"^[2-4]\.[0-9]+$")
platform = system().lower()
executable = self.launch_context.executable.executable_path

View file

@ -26,8 +26,7 @@ class CacheModelLoader(plugin.AssetLoader):
Note:
At least for now it only supports Alembic files.
"""
families = ["model", "pointcache"]
families = ["model", "pointcache", "animation"]
representations = ["abc"]
label = "Load Alembic"
@ -53,16 +52,12 @@ class CacheModelLoader(plugin.AssetLoader):
def _process(self, libpath, asset_group, group_name):
plugin.deselect_all()
collection = bpy.context.view_layer.active_layer_collection.collection
relative = bpy.context.preferences.filepaths.use_relative_paths
bpy.ops.wm.alembic_import(
filepath=libpath,
relative_path=relative
)
parent = bpy.context.scene.collection
imported = lib.get_selection()
# Children must be linked before parents,
@ -79,6 +74,10 @@ class CacheModelLoader(plugin.AssetLoader):
objects.reverse()
for obj in objects:
# Unlink the object from all collections
collections = obj.users_collection
for collection in collections:
collection.objects.unlink(obj)
name = obj.name
obj.name = f"{group_name}:{name}"
if obj.type != 'EMPTY':
@ -90,7 +89,7 @@ class CacheModelLoader(plugin.AssetLoader):
material_slot.material.name = f"{group_name}:{name_mat}"
if not obj.get(AVALON_PROPERTY):
obj[AVALON_PROPERTY] = dict()
obj[AVALON_PROPERTY] = {}
avalon_info = obj[AVALON_PROPERTY]
avalon_info.update({"container_name": group_name})
@ -99,6 +98,18 @@ class CacheModelLoader(plugin.AssetLoader):
return objects
def _link_objects(self, objects, collection, containers, asset_group):
# Link the imported objects to any collection where the asset group is
# linked to, except the AVALON_CONTAINERS collection
group_collections = [
collection
for collection in asset_group.users_collection
if collection != containers]
for obj in objects:
for collection in group_collections:
collection.objects.link(obj)
def process_asset(
self, context: dict, name: str, namespace: Optional[str] = None,
options: Optional[Dict] = None
@ -120,18 +131,21 @@ class CacheModelLoader(plugin.AssetLoader):
group_name = plugin.asset_name(asset, subset, unique_number)
namespace = namespace or f"{asset}_{unique_number}"
avalon_containers = bpy.data.collections.get(AVALON_CONTAINERS)
if not avalon_containers:
avalon_containers = bpy.data.collections.new(
name=AVALON_CONTAINERS)
bpy.context.scene.collection.children.link(avalon_containers)
containers = bpy.data.collections.get(AVALON_CONTAINERS)
if not containers:
containers = bpy.data.collections.new(name=AVALON_CONTAINERS)
bpy.context.scene.collection.children.link(containers)
asset_group = bpy.data.objects.new(group_name, object_data=None)
avalon_containers.objects.link(asset_group)
containers.objects.link(asset_group)
objects = self._process(libpath, asset_group, group_name)
bpy.context.scene.collection.objects.link(asset_group)
# Link the asset group to the active collection
collection = bpy.context.view_layer.active_layer_collection.collection
collection.objects.link(asset_group)
self._link_objects(objects, asset_group, containers, asset_group)
asset_group[AVALON_PROPERTY] = {
"schema": "openpype:container-2.0",
@ -207,7 +221,11 @@ class CacheModelLoader(plugin.AssetLoader):
mat = asset_group.matrix_basis.copy()
self._remove(asset_group)
self._process(str(libpath), asset_group, object_name)
objects = self._process(str(libpath), asset_group, object_name)
containers = bpy.data.collections.get(AVALON_CONTAINERS)
self._link_objects(objects, asset_group, containers, asset_group)
asset_group.matrix_basis = mat
metadata["libpath"] = str(libpath)

View file

@ -95,6 +95,8 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
self.log.info("Installing callbacks ... ")
register_event_callback("init", on_init)
_set_project()
if lib.IS_HEADLESS:
self.log.info((
"Running in headless mode, skipping Maya save/open/new"
@ -103,7 +105,6 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
return
_set_project()
self._register_callbacks()
menu.install(project_settings)

View file

@ -7,7 +7,7 @@ class PreCopyMel(PreLaunchHook):
Hook `GlobalHostDataHook` must be executed before this hook.
"""
app_groups = {"maya"}
app_groups = {"maya", "mayapy"}
launch_types = {LaunchTypes.local}
def execute(self):

View file

@ -0,0 +1,117 @@
import pyblish.api
from openpype.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from maya import cmds
from openpype.pipeline.publish import RepairAction
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.lib import reset_scene_resolution
class ValidateResolution(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate the render resolution setting aligned with DB"""
order = pyblish.api.ValidatorOrder
families = ["renderlayer"]
hosts = ["maya"]
label = "Validate Resolution"
actions = [RepairAction]
optional = True
def process(self, instance):
if not self.is_active(instance.data):
return
invalid = self.get_invalid_resolution(instance)
if invalid:
raise PublishValidationError(
"Render resolution is invalid. See log for details.",
description=(
"Wrong render resolution setting. "
"Please use repair button to fix it.\n\n"
"If current renderer is V-Ray, "
"make sure vraySettings node has been created."
)
)
@classmethod
def get_invalid_resolution(cls, instance):
width, height, pixelAspect = cls.get_db_resolution(instance)
current_renderer = instance.data["renderer"]
layer = instance.data["renderlayer"]
invalid = False
if current_renderer == "vray":
vray_node = "vraySettings"
if cmds.objExists(vray_node):
current_width = lib.get_attr_in_layer(
"{}.width".format(vray_node), layer=layer)
current_height = lib.get_attr_in_layer(
"{}.height".format(vray_node), layer=layer)
current_pixelAspect = lib.get_attr_in_layer(
"{}.pixelAspect".format(vray_node), layer=layer
)
else:
cls.log.error(
"Can't detect VRay resolution because there is no node "
"named: `{}`".format(vray_node)
)
return True
else:
current_width = lib.get_attr_in_layer(
"defaultResolution.width", layer=layer)
current_height = lib.get_attr_in_layer(
"defaultResolution.height", layer=layer)
current_pixelAspect = lib.get_attr_in_layer(
"defaultResolution.pixelAspect", layer=layer
)
if current_width != width or current_height != height:
cls.log.error(
"Render resolution {}x{} does not match "
"asset resolution {}x{}".format(
current_width, current_height,
width, height
))
invalid = True
if current_pixelAspect != pixelAspect:
cls.log.error(
"Render pixel aspect {} does not match "
"asset pixel aspect {}".format(
current_pixelAspect, pixelAspect
))
invalid = True
return invalid
@classmethod
def get_db_resolution(cls, instance):
asset_doc = instance.data["assetEntity"]
project_doc = instance.context.data["projectEntity"]
for data in [asset_doc["data"], project_doc["data"]]:
if (
"resolutionWidth" in data and
"resolutionHeight" in data and
"pixelAspect" in data
):
width = data["resolutionWidth"]
height = data["resolutionHeight"]
pixelAspect = data["pixelAspect"]
return int(width), int(height), float(pixelAspect)
# Defaults if not found in asset document or project document
return 1920, 1080, 1.0
@classmethod
def repair(cls, instance):
# Usually without renderlayer overrides the renderlayers
# all share the same resolution value - so fixing the first
# will have fixed all the others too. It's much faster to
# check whether it's invalid first instead of switching
# into all layers individually
if not cls.get_invalid_resolution(instance):
cls.log.debug(
"Nothing to repair on instance: {}".format(instance)
)
return
layer_node = instance.data['setMembers']
with lib.renderlayer(layer_node):
reset_scene_resolution()

View file

@ -17,7 +17,7 @@ class SetFrameRangeLoader(load.LoaderPlugin):
"yeticache",
"pointcache"]
representations = ["*"]
extension = {"*"}
extensions = {"*"}
label = "Set frame range"
order = 11

View file

@ -27,7 +27,7 @@ class LoadBackdropNodes(load.LoaderPlugin):
families = ["workfile", "nukenodes"]
representations = ["*"]
extension = {"nk"}
extensions = {"nk"}
label = "Import Nuke Nodes"
order = 0

View file

@ -26,7 +26,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
families = ["camera"]
representations = ["*"]
extension = {"abc"}
extensions = {"abc"}
label = "Load Alembic Camera"
icon = "camera"

View file

@ -24,7 +24,7 @@ class LoadEffects(load.LoaderPlugin):
families = ["effect"]
representations = ["*"]
extension = {"json"}
extensions = {"json"}
label = "Load Effects - nodes"
order = 0

View file

@ -25,7 +25,7 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
families = ["effect"]
representations = ["*"]
extension = {"json"}
extensions = {"json"}
label = "Load Effects - Input Process"
order = 0

View file

@ -26,7 +26,7 @@ class LoadGizmo(load.LoaderPlugin):
families = ["gizmo"]
representations = ["*"]
extension = {"gizmo"}
extensions = {"gizmo"}
label = "Load Gizmo"
order = 0

View file

@ -28,7 +28,7 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
families = ["gizmo"]
representations = ["*"]
extension = {"gizmo"}
extensions = {"gizmo"}
label = "Load Gizmo - Input Process"
order = 0

View file

@ -9,7 +9,7 @@ class MatchmoveLoader(load.LoaderPlugin):
families = ["matchmove"]
representations = ["*"]
extension = {"py"}
extensions = {"py"}
defaults = ["Camera", "Object"]

View file

@ -24,7 +24,7 @@ class AlembicModelLoader(load.LoaderPlugin):
families = ["model", "pointcache", "animation"]
representations = ["*"]
extension = {"abc"}
extensions = {"abc"}
label = "Load Alembic"
icon = "cube"

View file

@ -22,7 +22,7 @@ class LinkAsGroup(load.LoaderPlugin):
families = ["workfile", "nukenodes"]
representations = ["*"]
extension = {"nk"}
extensions = {"nk"}
label = "Load Precomp"
order = 0

View file

@ -1,5 +1,6 @@
import os
import shutil
import filecmp
from openpype.client.entities import get_representations
from openpype.lib.applications import PreLaunchHook, LaunchTypes
@ -194,3 +195,69 @@ class CopyLastPublishedWorkfile(PreLaunchHook):
self.data["last_workfile_path"] = local_workfile_path
# Keep source filepath for further path conformation
self.data["source_filepath"] = last_published_workfile_path
# Get resources directory
resources_dir = os.path.join(
os.path.dirname(local_workfile_path), 'resources'
)
# Make resource directory if it doesn't exist
if not os.path.exists(resources_dir):
os.mkdir(resources_dir)
# Copy resources to the local resources directory
for file in workfile_representation['files']:
# Get resource main path
resource_main_path = anatomy.fill_root(file["path"])
# Get resource file basename
resource_basename = os.path.basename(resource_main_path)
# Only copy if the resource file exists, and it's not the workfile
if (
not os.path.exists(resource_main_path)
or resource_basename == os.path.basename(
last_published_workfile_path
)
):
continue
# Get resource path in workfile folder
resource_work_path = os.path.join(
resources_dir, resource_basename
)
# Check if the resource file already exists in the resources folder
if os.path.exists(resource_work_path):
# Check if both files are the same
if filecmp.cmp(resource_main_path, resource_work_path):
self.log.warning(
'Resource "{}" already exists.'
.format(resource_basename)
)
continue
else:
# Add `.old` to existing resource path
resource_path_old = resource_work_path + '.old'
if os.path.exists(resource_work_path + '.old'):
for i in range(1, 100):
p = resource_path_old + '%02d' % i
if not os.path.exists(p):
# Rename existing resource file to
# `resource_name.old` + 2 digits
shutil.move(resource_work_path, p)
break
else:
self.log.warning(
'There are a hundred old files for '
'resource "{}". '
'Perhaps is it time to clean up your '
'resources folder'
.format(resource_basename)
)
continue
else:
# Rename existing resource file to `resource_name.old`
shutil.move(resource_work_path, resource_path_old)
# Copy resource file to workfile resources folder
shutil.copy(resource_main_path, resources_dir)

View file

@ -107,17 +107,18 @@ def get_time_data_from_instance_or_context(instance):
TimeData: dataclass holding time information.
"""
context = instance.context
return TimeData(
start=(instance.data.get("frameStart") or
instance.context.data.get("frameStart")),
end=(instance.data.get("frameEnd") or
instance.context.data.get("frameEnd")),
fps=(instance.data.get("fps") or
instance.context.data.get("fps")),
handle_start=(instance.data.get("handleStart") or
instance.context.data.get("handleStart")), # noqa: E501
handle_end=(instance.data.get("handleEnd") or
instance.context.data.get("handleEnd"))
start=instance.data.get("frameStart", context.data.get("frameStart")),
end=instance.data.get("frameEnd", context.data.get("frameEnd")),
fps=instance.data.get("fps", context.data.get("fps")),
step=instance.data.get("byFrameStep", instance.data.get("step", 1)),
handle_start=instance.data.get(
"handleStart", context.data.get("handleStart")
),
handle_end=instance.data.get(
"handleEnd", context.data.get("handleEnd")
)
)

View file

@ -829,6 +829,11 @@
"redshift_render_attributes": [],
"renderman_render_attributes": []
},
"ValidateResolution": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateCurrentRenderLayerIsRenderable": {
"enabled": true,
"optional": false,

View file

@ -114,6 +114,65 @@
}
}
},
"mayapy": {
"enabled": true,
"label": "MayaPy",
"icon": "{}/app_icons/maya.png",
"host_name": "maya",
"environment": {
"MAYA_DISABLE_CLIC_IPM": "Yes",
"MAYA_DISABLE_CIP": "Yes",
"MAYA_DISABLE_CER": "Yes",
"PYMEL_SKIP_MEL_INIT": "Yes",
"LC_ALL": "C"
},
"variants": {
"2024": {
"use_python_2": false,
"executables": {
"windows": [
"C:\\Program Files\\Autodesk\\Maya2024\\bin\\mayapy.exe"
],
"darwin": [],
"linux": [
"/usr/autodesk/maya2024/bin/mayapy"
]
},
"arguments": {
"windows": [
"-I"
],
"darwin": [],
"linux": [
"-I"
]
},
"environment": {}
},
"2023": {
"use_python_2": false,
"executables": {
"windows": [
"C:\\Program Files\\Autodesk\\Maya2023\\bin\\mayapy.exe"
],
"darwin": [],
"linux": [
"/usr/autodesk/maya2023/bin/mayapy"
]
},
"arguments": {
"windows": [
"-I"
],
"darwin": [],
"linux": [
"-I"
]
},
"environment": {}
}
}
},
"3dsmax": {
"enabled": true,
"label": "3ds max",

View file

@ -431,6 +431,10 @@
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateResolution",
"label": "Validate Resolution Settings"
},
{
"key": "ValidateCurrentRenderLayerIsRenderable",
"label": "Validate Current Render Layer Has Renderable Camera"

View file

@ -0,0 +1,39 @@
{
"type": "dict",
"key": "mayapy",
"label": "Autodesk MayaPy",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict-modifiable",
"key": "variants",
"collapsible_key": true,
"use_label_wrap": false,
"object_type": {
"type": "dict",
"collapsible": true,
"children": [
{
"type": "schema_template",
"name": "template_host_variant_items"
}
]
}
}
]
}

View file

@ -9,6 +9,10 @@
"type": "schema",
"name": "schema_maya"
},
{
"type": "schema",
"name": "schema_mayapy"
},
{
"type": "schema",
"name": "schema_3dsmax"

View file

@ -109,6 +109,55 @@
}
]
},
"maya": {
"enabled": true,
"label": "Maya",
"icon": "{}/app_icons/maya.png",
"host_name": "maya",
"environment": "{\n \"MAYA_DISABLE_CLIC_IPM\": \"Yes\",\n \"MAYA_DISABLE_CIP\": \"Yes\",\n \"MAYA_DISABLE_CER\": \"Yes\",\n \"PYMEL_SKIP_MEL_INIT\": \"Yes\",\n \"LC_ALL\": \"C\"\n}\n",
"variants": [
{
"name": "2024",
"label": "2024",
"executables": {
"windows": [
"C:\\Program Files\\Autodesk\\Maya2024\\bin\\mayapy.exe"
],
"darwin": [],
"linux": [
"/usr/autodesk/maya2024/bin/mayapy"
]
},
"arguments": {
"windows": [],
"darwin": [],
"linux": []
},
"environment": "{\n \"MAYA_VERSION\": \"2024\"\n}",
"use_python_2": false
},
{
"name": "2023",
"label": "2023",
"executables": {
"windows": [
"C:\\Program Files\\Autodesk\\Maya2023\\bin\\mayapy.exe"
],
"darwin": [],
"linux": [
"/usr/autodesk/maya2023/bin/mayapy"
]
},
"arguments": {
"windows": [],
"darwin": [],
"linux": []
},
"environment": "{\n \"MAYA_VERSION\": \"2023\"\n}",
"use_python_2": false
}
]
},
"adsk_3dsmax": {
"enabled": true,
"label": "3ds Max",

View file

@ -433,6 +433,10 @@ class PublishersModel(BaseSettingsModel):
default_factory=ValidateRenderSettingsModel,
title="Validate Render Settings"
)
ValidateResolution: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Resolution Setting"
)
ValidateCurrentRenderLayerIsRenderable: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Current Render Layer Has Renderable Camera"
@ -902,6 +906,11 @@ DEFAULT_PUBLISH_SETTINGS = {
"redshift_render_attributes": [],
"renderman_render_attributes": []
},
"ValidateResolution": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateCurrentRenderLayerIsRenderable": {
"enabled": True,
"optional": False,

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring addon version."""
__version__ = "0.1.4"
__version__ = "0.1.5"