# 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 . """ 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)