Source code for bgdev.utils.scripts

"""Random scripts and tools (needs to be sorted...).

:author: Benoit GIELLY <benoit.gielly@gmail.com>

"""
from functools import partial
import logging
from math import sin, sqrt

from PySide2 import QtWidgets
from maya import cmds
from maya.api import OpenMaya

LOG = logging.getLogger(__name__)


[docs]def snap(**kwargs): """Snap an obj on a target.""" srt = kwargs.get("snap", kwargs.get("sn", "rt")).lower() obj = kwargs.get("object", kwargs.get("obj", None)) tgt = kwargs.get("target", kwargs.get("tgt", None)) pivot = kwargs.get("pivot", kwargs.get("pv", None)) try: if not obj: obj = cmds.ls(selection=True)[0] if not tgt: tgt = cmds.ls(selection=True)[1] except IndexError: cmds.warning( "Select 'source' then 'target' or use the command's flags" ) if "t" in srt: if pivot: pos = cmds.xform( tgt + ".rp", query=True, translation=True, worldSpace=True ) cmds.move(pos[0], pos[1], pos[2], rotatePivotRelative=True) else: pos = cmds.xform( tgt, query=True, translation=True, worldSpace=True ) cmds.xform(obj, translation=pos, worldSpace=True) if "r" in srt: rot = cmds.xform(tgt, query=True, rotation=True, worldSpace=True) cmds.xform(obj, rotation=rot, worldSpace=True) if "s" in srt: scl = cmds.getAttr(tgt + ".scale")[0] cmds.setAttr(obj + ".scale", scl[0], scl[1], scl[2], type="double3")
[docs]def toggle_side_select(toggle=True): # pylint: disable=too-many-statements """Toggle selection.""" node_list = cmds.ls(selection=True, flatten=True) new_list = [] # if vertex, use symmetry table to find the opposite if ".vtx" in node_list[0]: for each in node_list: index = int(each.rpartition("[")[-1].rpartition("]")[0]) if cmds.objExists(each + ".sym"): mirror_index = cmds.getAttr( each.rpartition(".vtx")[0] + ".sym" )[index][1:] mirror = each.replace("[%s]" % index, "[%s]" % mirror_index) new_list.append(mirror) # else, toggle the prefixes else: for each in node_list: namespace = each.rpartition(":")[0] namespace = "%s:" % namespace if namespace else namespace clean_name = each.replace(namespace, "") # split to find the short name short_name = clean_name.rpartition("|")[-1] if short_name.lower().startswith("c"): new_list.append(each) continue if short_name.startswith("L_"): pfx = ["L_", "R_"] elif short_name.startswith("R_"): pfx = ["R_", "L_"] elif short_name.startswith("L"): pfx = ["L", "R"] elif short_name.startswith("R"): pfx = ["R", "L"] elif short_name.startswith("LB_"): pfx = ["LB_", "RB_"] elif short_name.startswith("LF_"): pfx = ["LF_", "RF_"] elif short_name.startswith("RB_"): pfx = ["RB_", "LB_"] elif short_name.startswith("RF_"): pfx = ["RF_", "LF_"] elif "Left" in short_name: pfx = ["Left", "Right"] elif "Right" in short_name: pfx = ["Right", "Left"] elif "left" in short_name: pfx = ["left", "right"] elif "right" in short_name: pfx = ["right", "left"] else: new_list.append(each) continue flipped_name = [] size = len(pfx[0]) for short_name in clean_name.split("|"): if short_name.startswith(pfx[0]): name = ( short_name[:size].replace(pfx[0], pfx[1], 1) + short_name[size:] ) flipped_name.append(name) else: flipped_name.append(short_name) flipped_node = "{namespace}{name}".format( namespace=namespace, name="|".join(flipped_name) ) if cmds.objExists(flipped_node): new_list.append(flipped_node) else: cmds.warning("{} doesn't exists".format(flipped_node)) new_list.append(each) if toggle: cmds.select(new_list, replace=True) else: cmds.select(new_list, add=True)
[docs]def simple_snap(srt=None): """Snap quickly.""" selection = cmds.ls(selection=True) pos = cmds.xform( selection[-1], query=True, worldSpace=True, rotatePivot=True ) rot = cmds.xform(selection[-1], query=True, worldSpace=True, rotation=True) scl = cmds.xform(selection[-1], query=True, worldSpace=True, scale=True) for each in selection[:-1]: if "s" in srt: cmds.xform(each, scale=scl, worldSpace=True) if "r" in srt: cmds.xform(each, rotation=rot, worldSpace=True) if "t" in srt: cmds.xform(each, translation=pos, worldSpace=True)
[docs]def mirror_position(selected=False): """Mirror selected along world Z+.""" selection = cmds.ls(selection=True) for each in selection: matrix = cmds.xform(each, query=True, matrix=True, worldSpace=True) inverse = OpenMaya.MMatrix( [-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] ) result = OpenMaya.MMatrix(matrix) * inverse node = each if selected else cmds.spaceLocator(name=each + "_loc")[0] cmds.xform(node, matrix=result, worldSpace=True)
[docs]def quick_distance(): """Get distance between two nodes.""" attributes = ["startPoint", "endPoint"] selection = cmds.ls(selection=True) if len(selection) != 2: return cmds.warning("Select 2 nodes") points = {} for each, key in zip(selection, attributes): points[key] = cmds.xform( each, query=True, rotatePivot=True, worldSpace=True ) distance = cmds.distanceDimension(**points) for each, attr in zip(selection, attributes): target = cmds.listConnections("{}.{}".format(distance, attr))[0] if each != target: cmds.parentConstraint(each, target, maintainOffset=True)
[docs]def show_joint_orient(value=True): """Display jointOrient attributes in channel box.""" for each in cmds.ls(type="joint"): for plug in [each + ".jo" + x for x in "xyz"]: cmds.setAttr(plug, channelBox=value)
[docs]def find_bad_meshes(): """Find meshes with incorrect amount of vertices.""" bad_meshes = [] sel_list = OpenMaya.MGlobal.getSelectionListByName("*_geo") sel_iter = OpenMaya.MItSelectionList(sel_list) while not sel_iter.isDone(): mesh = OpenMaya.MFnMesh(sel_iter.getComponent()[0]) num_vertices = mesh.numVertices num_faces = mesh.numPolygons if num_vertices > num_faces * 4: bad_meshes.append(mesh.fullPathName()) sel_iter.next() if bad_meshes: cmds.sets(bad_meshes, name="BAD_MESHES") cmds.error("BAD MESHES FOUND!") else: cmds.warning("Scene seems clean, well done!")
[docs]def get_vectors_dialog(): """Query aim and up vectors using a LayoutPromptDialog.""" import pymel.core as pm # get the dialog's formLayout. form = pm.setParent(query=True) # build widgets aim_field = pm.intFieldGrp( numberOfFields=3, label="Aim Vector", value1=1, value2=0, value3=0 ) up_field = pm.intFieldGrp( numberOfFields=3, label="Up Vector", value1=0, value2=1, value3=0 ) def get_fields(aim_field, up_field, _): """Query fields values when button is clicked.""" aim_value = [int(x) for x in aim_field.getValue()] up_value = [int(x) for x in up_field.getValue()] value = aim_value, up_value pm.layoutDialog(dismiss=str(value)) with pm.horizontalLayout(): pm.button(label="OK", command=partial(get_fields, aim_field, up_field)) pm.button(label="Cancel", command='pm.layoutDialog(dismiss="Cancel")') form.redistribute()
[docs]def align_fingers( nodes=None, aim_vector=(1, 0, 0), up_vector=(0, 1, 0), gui=False ): """Align selection based on first and last plane (use for fingers).""" import pymel.core as pm import pymel.core.datatypes as dt # pylint: disable=too-many-locals, eval-used if gui is True: value = pm.layoutDialog(uiScript=get_vectors_dialog) if value == "Cancel": return aim_vector, up_vector = eval(value) # create locator and snap to selection[0] selection = nodes or pm.selected() selection = [pm.PyNode(x) for x in selection] locator = pm.spaceLocator() locator.setMatrix(selection[0].getMatrix(worldSpace=True)) # reverse vectors if we're on the right side (YZ plane) x_axis = locator.getTranslation(space="world")[0] if x_axis < 0: aim_vector = [-1 * x for x in aim_vector] up_vector = [-1 * x for x in up_vector] # aim to selection[2] pm.delete( pm.aimConstraint( selection[-1], locator, maintainOffset=False, aimVector=aim_vector, upVector=up_vector, worldUpObject=selection[1], worldUpType="object", ) ) # find AH distance index = len(selection) // 2 pt_a = dt.Point(selection[0].getTranslation(space="world")) pt_b = dt.Point(selection[index].getTranslation(space="world")) pt_c = dt.Point(selection[-1].getTranslation(space="world")) c_side = pt_b - pt_a b_side = pt_c - pt_a height = sin(c_side.angle(b_side)) * c_side.length() ah_dist = sqrt(pow(c_side.length(), 2) - pow(height, 2)) # offset by ah_dist along aim axis ah_values = [ah_dist * x for x in aim_vector] pm.move( locator, *ah_values, relative=True, objectSpace=True, worldSpaceDistance=True ) # re-orient properly pm.delete( pm.aimConstraint( selection[index], locator, maintainOffset=False, aimVector=aim_vector, upVector=up_vector, worldUpObject=selection[0], worldUpType="object", ) ) # move forward by half of AC ac_values = [b_side.length() * x for x in aim_vector] pm.move( locator, *ac_values, relative=True, objectSpace=True, worldSpaceDistance=True ) # orient the base locator for i, each in enumerate(selection, 1): if i < len(selection): tmp = pm.spaceLocator() tmp.setMatrix(each.getMatrix(worldSpace=True)) aim = pm.aimConstraint( selection[i], tmp, maintainOffset=False, aimVector=aim_vector, upVector=up_vector, worldUpObject=locator, worldUpType="object", ) orientation = pm.xform( tmp, query=True, worldSpace=True, rotation=True ) pm.delete(aim, tmp) pm.xform(each, rotation=orientation, worldSpace=True) else: tmp = pm.spaceLocator() pm.parent(tmp, selection[-2]) tmp.resetFromRestPosition() orientation = pm.xform( tmp, query=True, worldSpace=True, rotation=True ) pm.xform(each, rotation=orientation, worldSpace=True) pm.delete(tmp) # cleanup pm.delete(locator)
[docs]def show_cam_clip_planes(): """Turn nearClip and farClip planes visible in the CB for all cameras.""" for camera in cmds.ls(type="camera"): cmds.setAttr(camera + ".nearClipPlane", channelBox=True) cmds.setAttr(camera + ".farClipPlane", channelBox=True) # select the perspCamera if cmds.objExists("persp"): cmds.select("persp")
[docs]def set_default_clip_plane(): """Set camera clipPlanes to default values.""" values = [ cmds.optionVar(query="defaultCameraNearClipValue"), cmds.optionVar(query="defaultCameraFarClipValue"), ] for camera in cmds.ls(type="camera"): for attr in ["nearClipPlane", "farClipPlane"]: plug = "{}.{}".format(camera, attr) value = values[0] if "near" in attr else values[-1] if cmds.getAttr(plug, settable=True): cmds.setAttr(plug, value) show_cam_clip_planes()
[docs]def change_default_clip_plane(): """Create a quick GUI to change the camera clipPlane values option vars.""" import pymel.core as pm # methods def set_values(widget, near, far): """Set the near and far clip values.""" cmds.optionVar(floatValue=["defaultCameraNearClipValue", near.value()]) cmds.optionVar(floatValue=["defaultCameraFarClipValue", far.value()]) widget.close() def create_intfield(layout, name): """Create the near/far label and intfield combo.""" hbox = QtWidgets.QHBoxLayout() label = QtWidgets.QLabel(name + " Clip:") label.setMinimumWidth(60) field = QtWidgets.QDoubleSpinBox() if name == "Near": field.setMinimum(0.0000000001) field.setDecimals(4) else: field.setMaximum(10000000000) field.setDecimals(0) field.setValue( cmds.optionVar(query="defaultCamera{}ClipValue".format(name)) ) hbox.addWidget(label) hbox.addWidget(field) hbox.setStretch(1, 1) layout.addLayout(hbox) return field # widgets maya = pm.toQtWindow("MayaWindow") widget = QtWidgets.QDialog(maya) widget.setWindowTitle("Set default camera clipPlane") widget.setMinimumWidth(300) layout = QtWidgets.QVBoxLayout(widget) layout.setContentsMargins(2, 2, 2, 2) layout.setSpacing(2) near_field = create_intfield(layout, "Near") far_field = create_intfield(layout, "Far") btn_hbox = QtWidgets.QHBoxLayout() ok_btn = QtWidgets.QPushButton("OK") cl_btn = QtWidgets.QPushButton("Cancel") btn_hbox.addWidget(ok_btn) btn_hbox.addWidget(cl_btn) layout.addLayout(btn_hbox) # signals ok_btn.clicked.connect(partial(set_values, widget, near_field, far_field)) cl_btn.clicked.connect(widget.close) widget.show()