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
import os
import time

from PySide2 import QtWidgets
from maya import cmds
from maya.api import OpenMaya
import pymel.core as pm
import pymel.core.datatypes as dt

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+.""" # get selection selection = pm.selected() # get and set matrix for each in selection: matrix = each.wm[0].get() inverse = dt.Matrix( [-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ) result = matrix * inverse node = pm.spaceLocator(name=each + "_loc") if not selected else each node.setMatrix(result, worldSpace=True)
[docs]def quick_distance(): """Get distance between two nodes.""" sel = pm.selected() pos = [ pm.xform(x, query=True, rotatePivot=True, worldSpace=True) for x in sel ] dist = pm.distanceDimension(startPoint=pos[0], endPoint=pos[-1]) pm.parentConstraint(sel[0], dist.inputs()[0], maintainOffset=True) pm.parentConstraint(sel[-1], dist.inputs()[-1], maintainOffset=True)
[docs]def time_me(func): """Use this decorator to time functions execution.""" def timer(*args, **kwargs): # pylint: disable=missing-docstring start = time.time() result = func(*args, **kwargs) delta = time.time() - start nice_args = ", ".join(["%r" % x for x in args]) nice_kwargs = ", ".join( ["%s=%s" % (k, v) for k, v in kwargs.iteritems()] ) if nice_args and nice_kwargs: args = "({}, {})".format(nice_args, nice_kwargs) elif nice_args: args = "({})".format(nice_args) elif nice_kwargs: args = "({})".format(nice_kwargs) else: args = "()" LOG.info( ' > Timer: "%s%s" executed in %.3f sec', func.__name__, args, delta, ) return result return timer
[docs]def get_vectors_dialog(): """Query aim and up vectors using a LayoutPromptDialog.""" # 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 selected locators based on first and last plane (use for fingers).""" # 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 pm.ls(type="camera"): camera.nearClipPlane.showInChannelBox(True) camera.farClipPlane.showInChannelBox(True) # select the perspCamera if pm.objExists("persp"): pm.select("persp")
[docs]def set_near_clip_plane(): """Set nearClipPlane to 1.""" for camera in pm.ls(type="camera"): if ( not camera.nearClipPlane.isLocked() and not camera.nearClipPlane.isConnected() ): camera.nearClipPlane.set(0.01) camera.farClipPlane.set(100000)
[docs]def logging_time(): """Print the logging time.""" _file, local_dict = "~/bin/Linux64/logtime", {} if os.path.isfile(_file): execfile(_file, globals(), local_dict) msg = local_dict["logged_in_time"]() else: import datetime import subprocess hours1, minutes1 = map( int, (subprocess.check_output("who").split()[3].split(":")) ) hours2, minutes2 = map( int, datetime.datetime.now().strftime("%H:%M").split(":") ) delta = (hours2 * 60 + minutes2) - (hours1 * 60 + minutes1) hours, minutes = delta / 60, delta % 60 msg = "%s - " % datetime.datetime.today().strftime("%a %d %b") msg += "Arrived at : %sh%02d" % (hours1, minutes1) msg += "\tCurrent time : %sh%02d" % (hours2, minutes2) msg += "\tTime logged in : %sh%02d" % (hours, minutes) return cmds.warning(msg)
[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 set_default_clip_plane(): """Create a quick GUI to change de camera clipPlane values option vars.""" # 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()
[docs]def show_joint_orient(value=True): """Display jointOrient attributes in channel box.""" for each in pm.ls(type="joint"): for attr in ["jo" + x for x in "xyz"]: each.attr(attr).showInChannelBox(value)