add basic support for extended static mesh workflow wip

This commit is contained in:
Ondrej Samohel 2022-01-14 19:03:46 +01:00
parent d92ee1b003
commit 969dfdc69e
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
9 changed files with 230 additions and 72 deletions

View file

@ -1,11 +1,42 @@
from openpype.hosts.maya.api import plugin
# -*- coding: utf-8 -*-
"""Creator for Unreal Static Meshes."""
from openpype.hosts.maya.api import plugin, lib
from avalon.api import CreatorError, Session
from openpype.api import get_project_settings
from maya import cmds # noqa
class CreateUnrealStaticMesh(plugin.Creator):
"""Unreal Static Meshes with collisions."""
name = "staticMeshMain"
label = "Unreal - Static Mesh"
family = "unrealStaticMesh"
icon = "cube"
dynamic_subset_keys = ["asset"]
def __init__(self, *args, **kwargs):
"""Constructor."""
super(CreateUnrealStaticMesh, self).__init__(*args, **kwargs)
self._project_settings = get_project_settings(
Session["AVALON_PROJECT"])
@classmethod
def get_dynamic_data(
cls, variant, task_name, asset_id, project_name, host_name
):
dynamic_data = super(CreateUnrealStaticMesh, cls).get_dynamic_data(
variant, task_name, asset_id, project_name, host_name
)
dynamic_data["asset"] = Session.get("AVALON_ASSET")
return dynamic_data
def process(self):
with lib.undo_chunk():
instance = super(CreateUnrealStaticMesh, self).process()
content = cmds.sets(instance, query=True)
geometry = cmds.sets(name="geometry_SET", empty=True)
collisions = cmds.sets(name="collisions_SET", empty=True)
cmds.sets([geometry, collisions], forceElement=instance)
# todo: Iterate over collision prefixes and add them to correct
# sets. Put rest to the geometry set.

View file

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
"""Cleanup leftover nodes."""
from maya import cmds # noqa
import pyblish.api
class CleanNodesUp(pyblish.api.InstancePlugin):
"""Cleans up the staging directory after a successful publish.
This will also clean published renders and delete their parent directories.
"""
order = pyblish.api.IntegratorOrder + 10
label = "Clean Nodes"
optional = True
active = True
def process(self, instance):
if not instance.data.get("cleanNodes"):
self.log.info("nothing to clean")
nodes_to_clean = instance.data.pop("cleanNodes")
self.log.info("Removing {} nodes".format(len(nodes_to_clean)))
for node in nodes_to_clean:
cmds.remove(node)

View file

@ -4,25 +4,31 @@ import pyblish.api
class CollectUnrealStaticMesh(pyblish.api.InstancePlugin):
"""Collect unreal static mesh
"""Collect Unreal Static Mesh
Ensures always only a single frame is extracted (current frame). This
also sets correct FBX options for later extraction.
Note:
This is a workaround so that the `pype.model` family can use the
same pointcache extractor implementation as animation and pointcaches.
This always enforces the "current" frame to be published.
"""
order = pyblish.api.CollectorOrder + 0.2
label = "Collect Model Data"
label = "Collect Unreal Static Meshes"
families = ["unrealStaticMesh"]
def process(self, instance):
# add fbx family to trigger fbx extractor
instance.data["families"].append("fbx")
# take the name from instance (without the `S_` prefix)
instance.data["staticMeshCombinedName"] = instance.name[1:]
geometry_set = [i for i in instance if i == "geometry_SET"]
instance.data["membersToCombine"] = cmds.sets(
geometry_set, query=True)
collision_set = [i for i in instance if i == "collisions_SET"]
instance.data["collisionMembers"] = cmds.sets(
collision_set, query=True)
# set fbx overrides on instance
instance.data["smoothingGroups"] = True
instance.data["smoothMesh"] = True

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""Create Unreal Static Mesh data to be extracted as FBX."""
import openpype.api
import pyblish.api
from maya import cmds # noqa
class ExtractUnrealStaticMesh(openpype.api.Extractor):
"""Extract FBX from Maya. """
order = pyblish.api.ExtractorOrder - 0.1
label = "Extract Unreal Static Mesh"
families = ["unrealStaticMesh"]
def process(self, instance):
to_combine = instance.data.get("membersToCombine")
static_mesh_name = instance.data.get("staticMeshCombinedName")
self.log.info(
"merging {] into {}".format(
"+ ".join(to_combine), static_mesh_name))
cmds.polyUnite(
*to_combine,
n=static_mesh_name)
if not instance.data.get("cleanNodes"):
instance.data["cleanNodes"] = []
instance.data["cleanNodes"].append(static_mesh_name)

View file

@ -1,18 +1,19 @@
# -*- coding: utf-8 -*-
from maya import cmds
from maya import cmds # noqa
import pyblish.api
import openpype.api
import openpype.hosts.maya.api.action
import re
class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin):
"""Validate name of Unreal Static Mesh
Unreals naming convention states that staticMesh sould start with `SM`
prefix - SM_[Name]_## (Eg. SM_sube_01). This plugin also validates other
types of meshes - collision meshes:
Unreals naming convention states that staticMesh should start with `SM`
prefix - SM_[Name]_## (Eg. SM_sube_01).These prefixes can be configured
in Settings UI. This plugin also validates other types of
meshes - collision meshes:
UBX_[RenderMeshName]_##:
Boxes are created with the Box objects type in
@ -52,69 +53,69 @@ class ValidateUnrealStaticmeshName(pyblish.api.InstancePlugin):
families = ["unrealStaticMesh"]
label = "Unreal StaticMesh Name"
actions = [openpype.hosts.maya.api.action.SelectInvalidAction]
regex_mesh = r"SM_(?P<renderName>.*)_(\d{2})"
regex_collision = r"((UBX)|(UCP)|(USP)|(UCX))_(?P<renderName>.*)_(\d{2})"
regex_mesh = r"(?P<renderName>.*)_(\d{2})"
regex_collision = r"_(?P<renderName>.*)_(\d{2})"
@classmethod
def get_invalid(cls, instance):
# find out if supplied transform is group or not
def is_group(groupName):
try:
children = cmds.listRelatives(groupName, children=True)
for child in children:
if not cmds.ls(child, transforms=True):
return False
invalid = []
combined_geometry_name = instance.data.get(
"staticMeshCombinedName", None)
if cls.validate_mesh:
# compile regex for testing names
regex_mesh = "{}{}".format(
("_" + cls.static_mesh_prefix) or "", cls.regex_mesh
)
sm_r = re.compile(regex_mesh)
if not sm_r.match(combined_geometry_name):
cls.log.error("Mesh doesn't comply with name validation.")
return True
except Exception:
if cls.validate_collision:
collision_set = instance.data.get("collisionMembers", None)
# soft-fail is there are no collision objects
if not collision_set:
cls.log.warning("No collision objects to validate.")
return False
invalid = []
content_instance = instance.data.get("setMembers", None)
if not content_instance:
cls.log.error("Instance has no nodes!")
return True
pass
descendants = cmds.listRelatives(content_instance,
allDescendents=True,
fullPath=True) or []
regex_collision = "{}{}".format(
"({})_".format(
"|".join("(0}".format(p) for p in cls.collision_prefixes)
) or "", cls.regex_collision
)
cl_r = re.compile(regex_collision)
descendants = cmds.ls(descendants, noIntermediate=True, long=True)
trns = cmds.ls(descendants, long=False, type=('transform'))
# filter out groups
filter = [node for node in trns if not is_group(node)]
# compile regex for testing names
sm_r = re.compile(cls.regex_mesh)
cl_r = re.compile(cls.regex_collision)
sm_names = []
col_names = []
for obj in filter:
sm_m = sm_r.match(obj)
if sm_m is None:
# test if it matches collision mesh
cl_r = sm_r.match(obj)
if cl_r is None:
cls.log.error("invalid mesh name on: {}".format(obj))
for obj in collision_set:
cl_m = cl_r.match(obj)
if not cl_m:
cls.log.error("{} is invalid".format(obj))
invalid.append(obj)
elif cl_m.group("renderName") != combined_geometry_name:
cls.log.error(
"Collision object name doesn't match"
"static mesh name: {} != {}".format(
cl_m.group("renderName"),
combined_geometry_name)
)
invalid.append(obj)
else:
col_names.append((cl_r.group("renderName"), obj))
else:
sm_names.append(sm_m.group("renderName"))
for c_mesh in col_names:
if c_mesh[0] not in sm_names:
cls.log.error(("collision name {} doesn't match any "
"static mesh names.").format(obj))
invalid.append(c_mesh[1])
return invalid
def process(self, instance):
# todo: load prefixes from creator settings.
if not self.validate_mesh and not self.validate_collision:
self.log.info("Validation of both mesh and collision names"
"is disabled.")
return
if not instance.data.get("collisionMembers", None):
self.log.info("There are no collision objects to validate")
return
invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError("Model naming is invalid. See log.")
raise RuntimeError("Model naming is invalid. See log.")

View file

@ -219,7 +219,7 @@
"hosts": [],
"task_types": [],
"tasks": [],
"template": "{family}{Variant}"
"template": "{family}{variant}"
},
{
"families": [

View file

@ -127,6 +127,13 @@
"enabled": true,
"defaults": [
"Main"
],
"static_mesh_prefix": "S_",
"collision_prefixes": [
"UBX",
"UCP",
"USP",
"UCX"
]
},
"CreateVrayProxy": {
@ -180,6 +187,11 @@
"whitelist_native_plugins": false,
"authorized_plugins": []
},
"ValidateUnrealStaticMeshName": {
"enabled": true,
"validate_mesh": false,
"validate_collision": true
},
"ValidateRenderSettings": {
"arnold_render_attributes": [],
"vray_render_attributes": [],
@ -197,6 +209,11 @@
"regex": "(.*)_(\\d)*_(?P<shader>.*)_(GEO)",
"top_level_regex": ".*_GRP"
},
"ValidateModelContent": {
"enabled": true,
"optional": false,
"validate_top_group": true
},
"ValidateTransformNamingSuffix": {
"enabled": true,
"SUFFIX_NAMING_TABLE": {
@ -281,11 +298,6 @@
"optional": true,
"active": true
},
"ValidateModelContent": {
"enabled": true,
"optional": false,
"validate_top_group": true
},
"ValidateNoAnimation": {
"enabled": false,
"optional": true,

View file

@ -66,6 +66,38 @@
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "CreateUnrealStaticMesh",
"label": "Create Unreal - Static Mesh",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "defaults",
"label": "Default Subsets",
"object_type": "text"
},
{
"type": "text",
"key": "static_mesh_prefix",
"label": "Static Mesh Prefix"
},
{
"type": "list",
"key": "collision_prefixes",
"label": "Collision Mesh Prefixes",
"object_type": "text"
}
]
},
{
"type": "schema_template",
"name": "template_create_plugin",
@ -118,10 +150,6 @@
"key": "CreateSetDress",
"label": "Create Set Dress"
},
{
"key": "CreateUnrealStaticMesh",
"label": "Create Unreal - Static Mesh"
},
{
"key": "CreateVrayProxy",
"label": "Create VRay Proxy"

View file

@ -129,6 +129,31 @@
]
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateUnrealStaticMeshName",
"label": "Validate Unreal Static Mesh Name",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "validate_mesh",
"label": "Validate mesh Names "
},
{
"type": "boolean",
"key": "validate_collision",
"label": "Validate collision names"
}
]
},
{
"type": "dict",
"collapsible": true,