code tweaks with big roy's comment

This commit is contained in:
Kayla Man 2024-06-19 00:09:41 +08:00
parent 3508441de4
commit 041991e3c7
16 changed files with 239 additions and 212 deletions

View file

@ -4232,3 +4232,77 @@ def get_node_index_under_parent(node: str) -> int:
return cmds.listRelatives(parent,
children=True,
fullPath=True).index(node)
def search_textures(filepath):
"""Search all texture files on disk.
This also parses to full sequences for those with dynamic patterns
like <UDIM> and %04d in the filename.
Args:
filepath (str): The full path to the file, including any
dynamic patterns like <UDIM> or %04d
Returns:
list: The files found on disk
"""
filename = os.path.basename(filepath)
# Collect full sequence if it matches a sequence pattern
if len(filename.split(".")) > 2:
# For UDIM based textures (tiles)
if "<UDIM>" in filename:
sequences = get_sequence(filepath,
pattern="<UDIM>")
if sequences:
return sequences
# Frame/time - Based textures (animated masks f.e)
elif "%04d" in filename:
sequences = get_sequence(filepath,
pattern="%04d")
if sequences:
return sequences
# Assuming it is a fixed name (single file)
if os.path.exists(filepath):
return [filepath]
return []
def get_sequence(filepath, pattern="%04d"):
"""Get sequence from filename.
This will only return files if they exist on disk as it tries
to collect the sequence using the filename pattern and searching
for them on disk.
Supports negative frame ranges like -001, 0000, 0001 and -0001,
0000, 0001.
Arguments:
filepath (str): The full path to filename containing the given
pattern.
pattern (str): The pattern to swap with the variable frame number.
Returns:
list: file sequence.
"""
import clique
escaped = re.escape(filepath)
re_pattern = escaped.replace(pattern, "-?[0-9]+")
source_dir = os.path.dirname(filepath)
files = [f for f in os.listdir(source_dir)
if re.match(re_pattern, f)]
pattern = [clique.PATTERNS["frames"]]
collection, remainder = clique.assemble(files, patterns=pattern)
return collection

View file

@ -8,9 +8,9 @@ from ayon_core.lib import BoolDef, NumberDef
class CreateOxCache(plugin.MayaCreator):
"""Output for procedural plugin nodes of Ornatrix """
identifier = "io.openpype.creators.maya.OxCache"
identifier = "io.openpype.creators.maya.oxcache"
label = "Ornatrix Cache"
product_type = "OxCache"
product_type = "oxcache"
icon = "pagelines"
def get_instance_attr_defs(self):

View file

@ -9,9 +9,9 @@ from ayon_maya.api import (
class CreateOxRig(plugin.MayaCreator):
"""Output for Ornatrix nodes"""
identifier = "io.openpype.creators.maya.OxRig"
identifier = "io.openpype.creators.maya.oxrig"
label = "Ornatrix Rig"
product_type = "OxRig"
product_type = "oxrig"
icon = "usb"
def create(self, product_name, instance_data, pre_create_data):

View file

@ -34,7 +34,7 @@ class ArnoldStandinLoader(plugin.Loader):
"proxyAbc",
"pointcache",
"usd",
"OxCache"
"oxcache"
}
representations = {"ass", "abc", "usda", "usdc", "usd"}

View file

@ -11,7 +11,7 @@ from ayon_maya.api import lib, plugin
class OxRigLoader(plugin.ReferenceLoader):
"""This loader will load Ornatix rig."""
product_types = {"OxRig"}
product_types = {"oxrig"}
representations = {"ma"}
label = "Load Ornatrix Rig"
@ -27,6 +27,13 @@ class OxRigLoader(plugin.ReferenceLoader):
):
path = self.filepath_from_context(context)
# Check if the plugin for Ornatrix is available on the pc
try:
cmds.loadPlugin("Ornatrix.mll", quiet=True)
except Exception as exc:
self.log.error("Encountered exception:\n%s" % exc)
return
attach_to_root = options.get("attach_to_root", True)
group_name = options["group_name"]
@ -86,7 +93,7 @@ class OxRigLoader(plugin.ReferenceLoader):
node.endswith("input_SET")), None)
self.log.info("Creating variant: {}".format(variant))
creator_identifier = "io.openpype.creators.maya.OxCache"
creator_identifier = "io.openpype.creators.maya.oxcache"
host = registered_host()
create_context = CreateContext(host)
@ -99,7 +106,6 @@ class OxRigLoader(plugin.ReferenceLoader):
pre_create_data={"use_selection": True}
)
def use_resources_textures(self, namespace, path):
"""Use texture maps from resources directories
@ -107,15 +113,16 @@ class OxRigLoader(plugin.ReferenceLoader):
namespace (str): namespace
path (str): published filepath
"""
_, maya_extension = os.path.splitext(path)
settings_path = path.replace(maya_extension, ".rigsettings")
path_no_ext, _ = os.path.splitext(path)
settings_path = f"{path_no_ext}.rigsettings"
with open(settings_path, "r") as fp:
image_attributes = json.load(fp)
fp.close()
if image_attributes:
for image_attribute in image_attributes:
texture_attribute = "{}:{}".format(
namespace, image_attribute["texture_attribute"])
cmds.setAttr(texture_attribute,
image_attribute["destination_file"],
type="string")
if not image_attributes:
return
for image_attribute in image_attributes:
texture_attribute = "{}:{}".format(
namespace, image_attribute["texture_attribute"])
cmds.setAttr(texture_attribute,
image_attribute["destination_file"],
type="string")

View file

@ -20,7 +20,7 @@ from ayon_maya.api.plugin import get_load_color_for_product_type
class VRayProxyLoader(plugin.Loader):
"""Load VRay Proxy with Alembic or VrayMesh."""
product_types = {"vrayproxy", "model", "pointcache", "animation", "OxCache"}
product_types = {"vrayproxy", "model", "pointcache", "animation", "oxcache"}
representations = {"vrmesh", "abc"}
label = "Import VRay Proxy"

View file

@ -5,13 +5,11 @@ from maya import cmds
class CollectOxCache(plugin.MayaInstancePlugin):
"""Collect all information of the Ornatrix caches
"""
"""Collect all information of the Ornatrix caches"""
order = pyblish.api.CollectorOrder + 0.45
label = "Collect Ornatrix Cache"
families = ["OxRig", "OxCache"]
families = ["oxrig", "oxcache"]
def process(self, instance):
@ -22,8 +20,8 @@ class CollectOxCache(plugin.MayaInstancePlugin):
parent = cmds.listRelatives(ox_shape, parent=True)[0]
transform_data = {"name": parent, "cbId": lib.get_id(parent)}
ox_cache_nodes = [
ox_node for ox_node in cmds.listConnections(ox_shape, destination=True)
if cmds.nodeType(ox_node) == "HairFromGuidesNode"
ox_node for ox_node in cmds.listConnections(
ox_shape, destination=True, type="HairFromGuidesNode")
]
# transfer cache file
shape_data = {

View file

@ -0,0 +1,94 @@
import os
import re
import pyblish.api
from ayon_core.pipeline.publish import KnownPublishError
from ayon_maya.api import lib
from ayon_maya.api import plugin
from maya import cmds
ORNATRIX_NODES = {
"HairFromGuidesNode", "GuidesFromMeshNode",
"MeshFromStrandsNode", "SurfaceCombNode"
}
class CollectOxRig(plugin.MayaInstancePlugin):
"""Collect all information of the Ornatrix Rig"""
order = pyblish.api.CollectorOrder + 0.4
label = "Collect Ornatrix Rig"
families = ["oxrig"]
def process(self, instance):
assert "input_SET" in instance.data["setMembers"], (
"Ornatrix Rig must have an input_SET")
ornatrix_nodes = cmds.ls(instance[:], long=True)
self.log.debug(f"Getting ornatrix nodes: {ornatrix_nodes}")
# Force frame range for yeti cache export for the rig
# Collect any textures if used
ornatrix_resources = []
for node in ornatrix_nodes:
# Get Yeti resources (textures)
resources = self.get_texture_resources(node)
ornatrix_resources.extend(resources)
# avoid duplicate dictionary data
instance.data["resources"] = [
i for n, i in enumerate(ornatrix_resources)
if i not in ornatrix_resources[n + 1:]
]
self.log.debug("{}".format(instance.data["resources"]))
start = cmds.playbackOptions(query=True, animationStartTime=True)
for key in ["frameStart", "frameEnd",
"frameStartHandle", "frameEndHandle"]:
instance.data[key] = start
def get_texture_resources(self, node):
resources = []
node_shape = cmds.listRelatives(node, shapes=True)
if not node_shape:
return []
ox_nodes = [
ox_node for ox_node in cmds.listConnections(node_shape, destination=True)
if cmds.nodeType(ox_node) in ORNATRIX_NODES
]
ox_image_file = [
ox_img for ox_img in cmds.listConnections(ox_nodes, destination=False)
if cmds.nodeType(ox_img) == "file"
]
if not ox_image_file:
return []
for img in ox_image_file:
texture_attr = "{}.fileTextureName".format(img)
texture = cmds.getAttr("{}.fileTextureName".format(img))
files = []
if os.path.isabs(texture):
self.log.debug("Texture is absolute path, ignoring "
"image search paths for: %s" % texture)
files = lib.search_textures(texture)
else:
root = os.environ["AYON_WORKDIR"]
filepath = os.path.join(root, texture)
files = lib.search_textures(filepath)
if files:
# Break out on first match in search paths..
break
if not files:
raise KnownPublishError(
"No texture found for: %s "
"(searched: %s)" % (texture))
item = {
"files": files,
"source": texture,
"texture_attribute": texture_attr
}
resources.append(item)
return resources

View file

@ -15,12 +15,6 @@ SETTINGS = {"renderDensity",
"cbId"}
ORNATRIX_NODES = {
"HairFromGuidesNode", "GuidesFromMeshNode",
"MeshFromStrandsNode", "SurfaceCombNode"
}
class CollectYetiRig(plugin.MayaInstancePlugin):
"""Collect all information of the Yeti Rig"""
@ -156,11 +150,11 @@ class CollectYetiRig(plugin.MayaInstancePlugin):
if os.path.isabs(texture):
self.log.debug("Texture is absolute path, ignoring "
"image search paths for: %s" % texture)
files = self.search_textures(texture)
files = lib.search_textures(texture)
else:
for root in image_search_paths:
filepath = os.path.join(root, texture)
files = self.search_textures(filepath)
files = lib.search_textures(filepath)
if files:
# Break out on first match in search paths..
break
@ -207,7 +201,7 @@ class CollectYetiRig(plugin.MayaInstancePlugin):
ref_file_name = os.path.basename(ref_file)
if "%04d" in ref_file_name:
item["files"] = self.get_sequence(ref_file)
item["files"] = lib.get_sequence(ref_file)
else:
if os.path.exists(ref_file) and os.path.isfile(ref_file):
item["files"] = [ref_file]
@ -221,173 +215,3 @@ class CollectYetiRig(plugin.MayaInstancePlugin):
resources.append(item)
return resources
def search_textures(self, filepath):
"""Search all texture files on disk.
This also parses to full sequences for those with dynamic patterns
like <UDIM> and %04d in the filename.
Args:
filepath (str): The full path to the file, including any
dynamic patterns like <UDIM> or %04d
Returns:
list: The files found on disk
"""
filename = os.path.basename(filepath)
# Collect full sequence if it matches a sequence pattern
if len(filename.split(".")) > 2:
# For UDIM based textures (tiles)
if "<UDIM>" in filename:
sequences = self.get_sequence(filepath,
pattern="<UDIM>")
if sequences:
return sequences
# Frame/time - Based textures (animated masks f.e)
elif "%04d" in filename:
sequences = self.get_sequence(filepath,
pattern="%04d")
if sequences:
return sequences
# Assuming it is a fixed name (single file)
if os.path.exists(filepath):
return [filepath]
return []
def get_sequence(self, filepath, pattern="%04d"):
"""Get sequence from filename.
This will only return files if they exist on disk as it tries
to collect the sequence using the filename pattern and searching
for them on disk.
Supports negative frame ranges like -001, 0000, 0001 and -0001,
0000, 0001.
Arguments:
filepath (str): The full path to filename containing the given
pattern.
pattern (str): The pattern to swap with the variable frame number.
Returns:
list: file sequence.
"""
import clique
escaped = re.escape(filepath)
re_pattern = escaped.replace(pattern, "-?[0-9]+")
source_dir = os.path.dirname(filepath)
files = [f for f in os.listdir(source_dir)
if re.match(re_pattern, f)]
pattern = [clique.PATTERNS["frames"]]
collection, remainder = clique.assemble(files, patterns=pattern)
return collection
def _replace_tokens(self, strings):
env_re = re.compile(r"\$\{(\w+)\}")
replaced = []
for s in strings:
matches = re.finditer(env_re, s)
for m in matches:
try:
s = s.replace(m.group(), os.environ[m.group(1)])
except KeyError:
msg = "Cannot find requested {} in environment".format(
m.group(1))
self.log.error(msg)
raise RuntimeError(msg)
replaced.append(s)
return replaced
class CollectOxRig(CollectYetiRig):
"""Collect all information of the Ornatrix Rig"""
order = pyblish.api.CollectorOrder + 0.4
label = "Collect Ornatrix Rig"
families = ["OxRig"]
def process(self, instance):
assert "input_SET" in instance.data["setMembers"], (
"Ornatrix Rig must have an input_SET")
ornatrix_nodes = cmds.ls(instance[:], long=True)
self.log.debug(f"Getting ornatrix nodes: {ornatrix_nodes}")
# Force frame range for yeti cache export for the rig
# Collect any textures if used
ornatrix_resources = []
for node in ornatrix_nodes:
# Get Yeti resources (textures)
resources = self.get_texture_resources(node)
ornatrix_resources.extend(resources)
# avoid duplicate dictionary data
instance.data["resources"] = [
i for n, i in enumerate(ornatrix_resources)
if i not in ornatrix_resources[n + 1:]
]
self.log.debug("{}".format(instance.data["resources"]))
start = cmds.playbackOptions(query=True, animationStartTime=True)
for key in ["frameStart", "frameEnd",
"frameStartHandle", "frameEndHandle"]:
instance.data[key] = start
def get_texture_resources(self, node):
# add docstrings
resources = []
node_shape = cmds.listRelatives(node, shapes=True)
if not node_shape:
return []
ox_nodes = [
ox_node for ox_node in cmds.listConnections(node_shape, destination=True)
if cmds.nodeType(ox_node) in ORNATRIX_NODES
]
ox_imageFile = [
ox_img for ox_img in cmds.listConnections(ox_nodes, destination=False)
if cmds.nodeType(ox_img) == "file"
]
if not ox_imageFile:
return []
for img in ox_imageFile:
texture_attr = "{}.fileTextureName".format(img)
texture = cmds.getAttr("{}.fileTextureName".format(img))
files = []
if os.path.isabs(texture):
self.log.debug("Texture is absolute path, ignoring "
"image search paths for: %s" % texture)
files = self.search_textures(texture)
else:
root = os.environ["AYON_WORKDIR"]
filepath = os.path.join(root, texture)
files = self.search_textures(filepath)
if files:
# Break out on first match in search paths..
break
if not files:
raise KnownPublishError(
"No texture found for: %s "
"(searched: %s)" % (texture))
item = {
"files": files,
"source": texture,
"texture_attribute": texture_attr
}
resources.append(item)
return resources

View file

@ -12,9 +12,10 @@ class ExtractOxCache(plugin.MayaExtractorPlugin):
"""
label = "Extract Ornatrix Cache"
families = ["OxRig", "OxCache"]
families = ["oxrig", "oxcache"]
def process(self, instance):
cmds.loadPlugin("Ornatrix.mll", quiet=True)
# Define extract output file path
ox_nodes = cmds.ls(instance[:], long=True)
dirname = self.staging_dir(instance)

View file

@ -9,16 +9,16 @@ from ayon_maya.api import plugin
from maya import cmds
class ExtractxRig(plugin.MayaExtractorPlugin):
"""Extract the Ornatrix rig to a Maya Scene and write the Ornatrix rig data."""
label = "Extract Ornatrix Rig"
families = ["OxRig"]
families = ["oxrig"]
scene_type = "ma"
def process(self, instance):
"""Plugin entry point."""
cmds.loadPlugin("Ornatrix.mll", quiet=True)
maya_settings = instance.context.data["project_settings"]["maya"]
ext_mapping = {
item["name"]: item["value"]

View file

@ -33,7 +33,7 @@ class ValidateFrameRange(plugin.MayaInstancePlugin,
"renderlayer",
"review",
"yeticache",
"OxCache"]
"oxcache"]
optional = True
actions = [RepairAction]
exclude_product_types = []

View file

@ -1,6 +1,6 @@
name = "maya"
title = "Maya"
version = "0.2.3"
version = "0.2.4"
client_dir = "ayon_maya"
ayon_required_addons = {

View file

@ -205,6 +205,14 @@ class CreatorsModel(BaseSettingsModel):
default_factory=BasicCreatorModel,
title="Create Maya Scene"
)
CreateOxRig: BasicCreatorModel = SettingsField(
default_factory=BasicCreatorModel,
title="Create Ornatrix Rig"
)
CreateOxCache: BasicCreatorModel = SettingsField(
default_factory=BasicCreatorModel,
title="Create Ornatrix Cache"
)
CreateRenderSetup: BasicCreatorModel = SettingsField(
default_factory=BasicCreatorModel,
title="Create Render Setup"
@ -373,6 +381,18 @@ DEFAULT_CREATORS_SETTINGS = {
"Main"
]
},
"CreateOxRig": {
"enabled": False,
"default_variants": [
"Main"
]
},
"CreateOxCache": {
"enabled": False,
"default_variants": [
"Main"
]
},
"CreateRenderSetup": {
"enabled": True,
"default_variants": [

View file

@ -39,7 +39,9 @@ class ColorsSetting(BaseSettingsModel):
(99, 206, 220, 1.0), title="Yeti Cache:")
yetiRig: ColorRGBA_uint8 = SettingsField(
(0, 205, 125, 1.0), title="Yeti Rig:")
OxRig: ColorRGBA_uint8 = SettingsField(
oxCache: ColorRGBA_uint8 = SettingsField(
(156, 234, 195, 1.0), title="Ornatrix Cache:")
oxRig: ColorRGBA_uint8 = SettingsField(
(206, 234, 195, 1.0), title="Ornatrix Rig:")
# model: ColorRGB_float = SettingsField(
# (0.82, 0.52, 0.12), title="Model:"
@ -223,6 +225,10 @@ class LoadersModel(BaseSettingsModel):
default_factory=YetiRigLoaderModel,
title="Yeti Rig Loader"
)
OxCacheLoader: LoaderEnabledModel = SettingsField(
default_factory=LoaderEnabledModel,
title="Ornatrix Cache Loader"
)
OxRigLoader: OxRigLoaderModel = SettingsField(
default_factory=OxRigLoaderModel,
title="Ornatrix Rig Loader"
@ -246,7 +252,9 @@ DEFAULT_LOADERS_SETTING = {
"vrayproxy": [255, 150, 12, 1.0],
"vrayscene_layer": [255, 150, 12, 1.0],
"yeticache": [99, 206, 220, 1.0],
"yetiRig": [0, 205, 125, 1.0]
"yetiRig": [0, 205, 125, 1.0],
"oxCache": [156, 234, 195, 1.0],
"oxRig": [206, 234, 195, 1.0]
# "model": [0.82, 0.52, 0.12],
# "rig": [0.23, 0.89, 0.92],
# "pointcache": [0.37, 0.82, 0.12],
@ -294,6 +302,7 @@ DEFAULT_LOADERS_SETTING = {
"VRaySceneLoader": {"enabled": True},
"XgenLoader": {"enabled": True},
"YetiCacheLoader": {"enabled": True},
"OxCacheLoader": {"enabled": True},
"YetiRigLoader": {
"enabled": True,
"create_cache_instance_on_load": True