Files
Nexus/2023/scripts/animation_tools/studiolibrary/mutils/transferobject.py
2025-11-24 00:15:32 +08:00

404 lines
9.8 KiB
Python

# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
#
# This library is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. This library is distributed in the
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
"""
Base object for saving poses, animation, selection sets and mirror tables.
Example:
import mutils
t = mutils.TransferObject.fromPath("/tmp/pose.json")
t = mutils.TransferObject.fromObjects(["object1", "object2"])
t.load(selection=True)
t.load(objects=["obj1", "obj2"])
t.load(namespaces=["namespace1", "namespace2"])
t.save("/tmp/pose.json")
t.read("/tmp/pose.json")
"""
import os
import abc
import json
import time
import locale
import getpass
import logging
from studiovendor import six
import mutils
try:
import maya.cmds
except Exception:
import traceback
traceback.print_exc()
logger = logging.getLogger(__name__)
class TransferObject(object):
@classmethod
def fromPath(cls, path):
"""
Return a new transfer instance for the given path.
:type path: str
:rtype: TransferObject
"""
t = cls()
t.setPath(path)
t.read()
return t
@classmethod
def fromObjects(cls, objects, **kwargs):
"""
Return a new transfer instance for the given objects.
:type objects: list[str]
:rtype: TransferObject
"""
t = cls(**kwargs)
for obj in objects:
t.add(obj)
return t
@staticmethod
def readJson(path):
"""
Read the given json path
:type path: str
:rtype: dict
"""
with open(path, "r") as f:
data = f.read() or "{}"
data = json.loads(data)
return data
@staticmethod
def readList(path):
"""
Legacy method for reading older .list file type.
:rtype: dict
"""
with open(path, "r") as f:
data = f.read()
data = eval(data, {})
result = {}
for obj in data:
result.setdefault(obj, {})
return {"objects": result}
@staticmethod
def readDict(path):
"""
Legacy method for reading older .dict file type.
:rtype: dict
"""
with open(path, "r") as f:
data = f.read()
data = eval(data, {})
result = {}
for obj in data:
result.setdefault(obj, {"attrs": {}})
for attr in data[obj]:
typ, val = data[obj][attr]
result[obj]["attrs"][attr] = {"type": typ, "value": val}
return {"objects": result}
def __init__(self):
self._path = None
self._namespaces = None
self._data = {"metadata": {}, "objects": {}}
def path(self):
"""
Return the disc location for the transfer object.
:rtype: str
"""
return self._path
def setPath(self, path):
"""
Set the disc location for loading and saving the transfer object.
:type path: str
"""
dictPath = path.replace(".json", ".dict")
listPath = path.replace(".json", ".list")
if not os.path.exists(path):
if os.path.exists(dictPath):
path = dictPath
elif os.path.exists(listPath):
path = listPath
self._path = path
def validate(self, **kwargs):
"""
Validate the given kwargs for the current IO object.
:type kwargs: dict
"""
namespaces = kwargs.get("namespaces")
if namespaces is not None:
sceneNamespaces = mutils.namespace.getAll() + [":"]
for namespace in namespaces:
if namespace and namespace not in sceneNamespaces:
msg = 'The namespace "{0}" does not exist in the scene! ' \
"Please choose a namespace which exists."
msg = msg.format(namespace)
raise ValueError(msg)
def mtime(self):
"""
Return the modification datetime of self.path().
:rtype: float
"""
return os.path.getmtime(self.path())
def ctime(self):
"""
Return the creation datetime of self.path().
:rtype: float
"""
return os.path.getctime(self.path())
def data(self):
"""
Return all the data for the transfer object.
:rtype: dict
"""
return self._data
def setData(self, data):
"""
Set the data for the transfer object.
:type data:
"""
self._data = data
def owner(self):
"""
Return the user who created this item.
:rtype: str
"""
return self.metadata().get("user", "")
def description(self):
"""
Return the user description for this item.
:rtype: str
"""
return self.metadata().get("description", "")
def objects(self):
"""
Return all the object data.
:rtype: dict
"""
return self.data().get("objects", {})
def object(self, name):
"""
Return the data for the given object name.
:type name: str
:rtype: dict
"""
return self.objects().get(name, {})
def createObjectData(self, name):
"""
Create the object data for the given object name.
:type name: str
:rtype: dict
"""
return {}
def namespaces(self):
"""
Return the namespaces contained in the transfer object
:rtype: list[str]
"""
if self._namespaces is None:
group = mutils.groupObjects(self.objects())
self._namespaces = group.keys()
return self._namespaces
def objectCount(self):
"""
Return the number of objects in the transfer object.
:rtype: int
"""
return len(self.objects() or [])
def add(self, objects):
"""
Add the given objects to the transfer object.
:type objects: str | list[str]
"""
if isinstance(objects, six.string_types):
objects = [objects]
for name in objects:
self.objects()[name] = self.createObjectData(name)
def remove(self, objects):
"""
Remove the given objects to the transfer object.
:type objects: str | list[str]
"""
if isinstance(objects, six.string_types):
objects = [objects]
for obj in objects:
del self.objects()[obj]
def setMetadata(self, key, value):
"""
Set the given key and value in the metadata.
:type key: str
:type value: int | str | float | dict
"""
self.data()["metadata"][key] = value
def updateMetadata(self, metadata):
"""
Update the given key and value in the metadata.
:type metadata: dict
"""
self.data()["metadata"].update(metadata)
def metadata(self):
"""
Return the current metadata for the transfer object.
Example: print(self.metadata())
Result # {
"User": "",
"Scene": "",
"Reference": {"filename": "", "namespace": ""},
"Description": "",
}
:rtype: dict
"""
return self.data().get("metadata", {})
def read(self, path=""):
"""
Return the data from the path set on the Transfer object.
:type path: str
:rtype: dict
"""
path = path or self.path()
if path.endswith(".dict"):
data = self.readDict(path)
elif path.endswith(".list"):
data = self.readList(path)
else:
data = self.readJson(path)
self.setData(data)
@abc.abstractmethod
def load(self, *args, **kwargs):
pass
@mutils.showWaitCursor
def save(self, path):
"""
Save the current metadata and object data to the given path.
:type path: str
:rtype: None
"""
logger.info("Saving pose: %s" % path)
user = getpass.getuser()
if user:
user = six.text_type(user)
ctime = str(time.time()).split(".")[0]
references = mutils.getReferenceData(self.objects())
self.setMetadata("user", user)
self.setMetadata("ctime", ctime)
self.setMetadata("version", "1.0.0")
self.setMetadata("references", references)
self.setMetadata("mayaVersion", maya.cmds.about(v=True))
self.setMetadata("mayaSceneFile", maya.cmds.file(q=True, sn=True))
# Move the metadata information to the top of the file
metadata = {"metadata": self.metadata()}
data = self.dump(metadata)[:-1] + ","
# Move the objects information to after the metadata
objects = {"objects": self.objects()}
data += self.dump(objects)[1:]
# Create the given directory if it doesn't exist
dirname = os.path.dirname(path)
if not os.path.exists(dirname):
os.makedirs(dirname)
with open(path, "w") as f:
f.write(str(data))
logger.info("Saved pose: %s" % path)
def dump(self, data=None):
"""
:type data: str | dict
:rtype: str
"""
if data is None:
data = self.data()
return json.dumps(data, indent=2)