import json
import logging
import maya.cmds as cmds
import maya.api.OpenMaya as OpenMaya
logger = logging.getLogger(__name__)
def export_curves(controls=None, file_path=None):
"""Serializes the given curves into the control library.
:param controls: Optional list of controls to export. If no controls are specified,
the selected curves will be exported.
:param file_path: File path to export to
:return: The exported list of ControlShapes.
"""
if controls is None:
controls = cmds.ls(sl=True)
data = get_curve_data(controls)
with open(file_path, "w") as fh:
json.dump(data, fh, indent=4, cls=CurveShapeEncoder)
logger.info("Exported controls to {}".format(file_path))
return data
def import_curves(file_path=None, tag_as_controller=False):
"""Imports control shapes from disk onto their saved named transforms.
:param file_path: Path to the control file.
:param tag_as_controller: True to tag the curve transform as a controller
:return: The new curve transforms
"""
controls = load_curves(file_path)
transforms = [
curve.create(curve.transform, tag_as_controller) for curve in controls
]
return transforms
def get_curve_data(controls=None):
"""Get the serializable data of the given controls.
:param controls: Controls to serialize
:return: List of ControlShape objects
"""
if controls is None:
controls = cmds.ls(sl=True)
data = [CurveShape(transform=control) for control in controls]
# Prune empty curves
data = [x for x in data if x.cvs]
return data
def load_curves(file_path=None):
"""Load the CurveShape objects from disk.
:param file_path:
:return:
"""
with open(file_path, "r") as fh:
data = json.load(fh)
logger.info("Loaded controls {}".format(file_path))
curves = [CurveShape(**control) for control in data]
return curves
def get_shape(node, intermediate=False):
"""Get the shape node of a tranform
This is useful if you don't want to have to check if a node is a shape node
or transform. You can pass in a shape node or transform and the function
will return the shape node.
:param node: node The name of the node.
:param intermediate: intermediate True to get the intermediate shape
:return: The name of the shape node.
"""
if cmds.objectType(node, isAType="transform"):
shapes = cmds.listRelatives(node, shapes=True, path=True)
if not shapes:
shapes = []
for shape in shapes:
is_intermediate = cmds.getAttr("{}.intermediateObject".format(shape))
if (
intermediate
and is_intermediate
and cmds.listConnections(shape, source=False)
):
return shape
elif not intermediate and not is_intermediate:
return shape
if shapes:
return shapes[0]
elif cmds.nodeType(node) in ["mesh", "nurbsCurve", "nurbsSurface"]:
is_intermediate = cmds.getAttr("{}.intermediateObject".format(node))
if is_intermediate and not intermediate:
node = cmds.listRelatives(node, parent=True, path=True)[0]
return get_shape(node)
else:
return node
return None
def get_knots(curve):
"""Gets the list of knots of a curve so it can be recreated.
:param curve: Curve to query.
:return: A list of knot values that can be passed into the curve creation command.
"""
curve = get_shape(curve)
info = cmds.createNode("curveInfo")
cmds.connectAttr("{0}.worldSpace".format(curve), "{0}.inputCurve".format(info))
knots = cmds.getAttr("{0}.knots[*]".format(info))
knots = [int(x) for x in knots]
cmds.delete(info)
return knots
class CurveShape(object):
"""Represents the data required to build a nurbs curve shape"""
def __init__(
self, transform=None, cvs=None, degree=3, form=0, knots=None, color=None
):
self.cvs = cvs
self.degree = degree
self.form = form
self.knots = knots
self.color = color
self.transform_matrix = OpenMaya.MTransformationMatrix()
self.transform = transform
if transform and cmds.objExists(transform) and not cvs:
self._set_from_curve(transform)
def _set_from_curve(self, transform):
"""Store the parameters from an existing curve in the CurveShape object.
:param transform: Transform
"""
shape = get_shape(transform)
if shape and cmds.nodeType(shape) == "nurbsCurve":
create_attr = "{}.create".format(shape)
connection = cmds.listConnections(create_attr, plugs=True, d=False)
if connection:
cmds.disconnectAttr(connection[0], create_attr)
self.transform = transform
self.cvs = cmds.getAttr("{}.cv[*]".format(shape))
self.degree = cmds.getAttr("{}.degree".format(shape))
self.form = cmds.getAttr("{}.form".format(shape))
self.knots = get_knots(shape)
if cmds.getAttr("{}.overrideEnabled".format(shape)):
if cmds.getAttr("{}.overrideRGBColors".format(shape)):
self.color = cmds.getAttr("{}.overrideColorRGB".format(shape))[0]
else:
self.color = cmds.getAttr("{}.overrideColor".format(shape))
else:
self.color = None
if connection:
cmds.connectAttr(connection[0], create_attr)
def create(self, transform=None, as_controller=True):
"""Create a curve.
:param transform: Name of the transform to create the curve shape under.
If the transform does not exist, it will be created.
:param as_controller: True to mark the curve transform as a controller.
:return: The transform of the new curve shapes.
"""
transform = transform or self.transform
if not cmds.objExists(transform):
transform = cmds.createNode("transform", name=transform)
periodic = self.form == 2
points = self._get_transformed_points()
points = points + points[: self.degree] if periodic else points
curve = cmds.curve(degree=self.degree, p=points, per=periodic, k=self.knots)
shape = get_shape(curve)
if self.color is not None:
cmds.setAttr("{}.overrideEnabled".format(shape), True)
if isinstance(self.color, int):
cmds.setAttr("{}.overrideColor".format(shape), self.color)
else:
cmds.setAttr("{}.overrideRGBColors".format(shape), True)
cmds.setAttr("{}.overrideColorRGB".format(shape), *self.color)
cmds.parent(shape, transform, r=True, s=True)
shape = cmds.rename(shape, "{}Shape".format(transform))
cmds.delete(curve)
if as_controller:
cmds.controller(transform)
logger.info("Created curve {} for transform {}".format(shape, transform))
return transform
def _get_transformed_points(self):
matrix = self.transform_matrix.asMatrix()
points = [OpenMaya.MPoint(*x) * matrix for x in self.cvs]
points = [(p.x, p.y, p.z) for p in points]
return points
def translate_by(self, x, y, z, local=True):
"""Translate the curve cvs by the given values
:param x: Translate X
:param y: Translate Y
:param z: Translate Z
:param local: True for local space, False for world
"""
space = OpenMaya.MSpace.kObject if local else OpenMaya.MSpace.kWorld
self.transform_matrix.translateBy(OpenMaya.MVector(x, y, z), space)
def set_translation(self, x, y, z, local=True):
"""Set the absolute translation of the curve shape.
:param x: Translate X
:param y: Translate Y
:param z: Translate Z
:param local: True for local space, False for world
"""
space = OpenMaya.MSpace.kObject if local else OpenMaya.MSpace.kWorld
self.transform_matrix.setTranslation(OpenMaya.MVector(x, y, z), space)
def rotate_by(self, x, y, z, local=True):
"""Rotate the curve cvs by the given euler rotation values
:param x: Rotate X
:param y: Rotate Y
:param z: Rotate Z
:param local: True for local space, False for world
"""
x, y, z = [v * 0.0174533 for v in [x, y, z]]
space = OpenMaya.MSpace.kObject if local else OpenMaya.MSpace.kWorld
self.transform_matrix.rotateBy(OpenMaya.MEulerRotation(x, y, z), space)
def set_rotation(self, x, y, z):
"""Set the absolute rotation of the curve shape in euler rotations.
:param x: Rotate X
:param y: Rotate Y
:param z: Rotate Z
"""
x, y, z = [v * 0.0174533 for v in [x, y, z]]
self.transform_matrix.setRotation(OpenMaya.MEulerRotation(x, y, z))
def scale_by(self, x, y, z, local=True):
"""Scale the curve cvs by the given amount.
:param x: Scale X
:param y: Scale Y
:param z: Scale Z
:param local: True for local space, False for world
"""
space = OpenMaya.MSpace.kObject if local else OpenMaya.MSpace.kWorld
self.transform_matrix.scaleBy([x, y, z], space)
def set_scale(self, x, y, z, local=True):
"""Set the absolute scale of the curve shape.
:param x: Scale X
:param y: Scale Y
:param z: Scale Z
:param local: True for local space, False for world
"""
space = OpenMaya.MSpace.kObject if local else OpenMaya.MSpace.kWorld
self.transform_matrix.setScale([x, y, z], space)
def ExportCurves():
curve_transforms = [cmds.listRelatives(i, p=1, type='transform')[0] for i
in cmds.ls(type='nurbsCurve', o=1, r=1, ni=1)]
selectList = []
for c in curve_transforms:
if "RootX_M" in c or "Neck_M" in c:
selectList.append(c)
file_path = "D:/curves.json"
export_curves(selectList, file_path)
def ImportCurves():
file_path = "D:/curves.json"
import_curves(file_path, True)
#ExportCurves()
ImportCurves()
More than 1 year has passed since last update.
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme