├── README.md ├── LICENSE └── vfk_UI.py /README.md: -------------------------------------------------------------------------------- 1 | # variableFK 2 | UI for building a variable foward kinematic rig, after Jeff Brodsky. FK controls slide along length of the geo, allowing curves to push forward and back smoothly. Ideal for tentacles, elephant trunks, etc. 3 | 4 | To open UI, open vfk_UI.py file in Maya script editor and run the entire block of code. 5 | 6 | Demonstration of original rig by Jeff Brodsky can be found here: 7 | https://vimeo.com/49353110 8 | 9 | Tutorial of the node setup by Jeff Brodsky can be found here: 10 | https://vimeo.com/72424469 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 James Sumner III 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vfk_UI.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import maya.cmds as mc 3 | import pymel.core as pmc 4 | 5 | from PySide import QtCore as qc 6 | from PySide import QtGui as qg 7 | from shiboken import wrapInstance 8 | 9 | import maya.OpenMayaUI as omui 10 | 11 | def maya_main_window(): 12 | ''' 13 | Return the Maya main window as a Python object 14 | ''' 15 | main_window_ptr = omui.MQtUtil.mainWindow() 16 | return wrapInstance(long(main_window_ptr), qg.QWidget) 17 | 18 | 19 | class VFK_UI(qg.QDialog): 20 | 21 | def __init__(self, parent=maya_main_window()): 22 | super(VFK_UI, self).__init__(parent) 23 | 24 | def format_widget(self, lbl_text = 'Label text', sub_text = 'Default value'): 25 | lbl = qg.QLabel(lbl_text) 26 | le = qg.QLineEdit() 27 | le.setMinimumSize(75,20) 28 | le.setMaximumSize(75,20) 29 | sub = qg.QLabel(sub_text) 30 | sub.setStyleSheet('color: rgb(140,140,140)') 31 | 32 | layoutA = qg.QVBoxLayout() 33 | layoutA.setContentsMargins(0,0,0,0) 34 | layoutA.setSpacing(0) 35 | layoutB = qg.QHBoxLayout() 36 | layoutB.setContentsMargins(0,0,0,0) 37 | layoutB.setSpacing(0) 38 | layoutB.addWidget(lbl) 39 | layoutB.addWidget(le) 40 | layoutA.addLayout(layoutB) 41 | layoutA.addWidget(sub) 42 | 43 | widget = qg.QWidget() 44 | widget.setLayout(layoutA) 45 | 46 | return_tuple = (widget, le) 47 | 48 | return return_tuple 49 | 50 | def create_ui(self): 51 | pmc.undoInfo(openChunk=True) 52 | 53 | self.setWindowTitle('VFK Rig Creator') 54 | self.setWindowFlags(qc.Qt.Tool) 55 | self.setMinimumSize(300, 400) 56 | self.setMaximumWidth(300) 57 | 58 | self.create_controls() 59 | self.create_layout() 60 | self.create_connections() 61 | 62 | pmc.undoInfo(closeChunk=True) 63 | 64 | def create_controls(self): 65 | self.header_lbl = qg.QLabel('Select start joint, then end joint') 66 | self.credit_lbl = qg.QLabel('Automates creation of VFK rig after Jeff Brodsky') 67 | self.credit_lbl.setStyleSheet('color: rgb(140,140,140)') 68 | self.link_lbl = qg.QLabel('https://vimeo.com/49353110') 69 | self.link_lbl.setStyleSheet('color: rgb(140,140,140)') 70 | 71 | self.name_widget, self.name_le = self.format_widget(lbl_text = 'Name prefix', 72 | sub_text = 'Default = nameOfTopJoint_ \nNames should be unique.') 73 | 74 | self.joints_widget, self.joints_le = self.format_widget(lbl_text = 'Total number of joints', 75 | sub_text = 'Default = 20') 76 | self.controls_widget, self.controls_le = self.format_widget(lbl_text = 'Number of controls', 77 | sub_text = 'Default = 3') 78 | self.control_radius_widget, self.control_radius_le = self.format_widget(lbl_text = 'Control radius', 79 | sub_text = 'Default = 4.0') 80 | self.joint_radius_widget, self.joint_radius_le= self.format_widget(lbl_text = 'Joint radius', 81 | sub_text = 'Default = 0.25') 82 | self.joint_prefix_widget, self.joint_prefix_le = self.format_widget(lbl_text = 'Joint prefix', 83 | sub_text = 'Default = "joint_"') 84 | self.joint_grp_prefix_widget, self.joint_grp_prefix_le = self.format_widget(lbl_text = 'Joint group prefix', 85 | sub_text = 'Default = "vfk_grp_"') 86 | self.control_prefix_widget, self.control_prefix_le = self.format_widget(lbl_text = 'Control prefix', 87 | sub_text = 'Default = "CTRL_vfk_"') 88 | self.control_grp_prefix_widget, self.control_grp_prefix_le = self.format_widget(lbl_text = 'Control group prefix', 89 | sub_text = 'Default = "OFF_CTRL_vfk_"') 90 | # Bone translate axis widget 91 | self.bone_trans_axis_widget = qg.QWidget() 92 | bone_trans_axis_lbl = qg.QLabel('Set bone main axis') 93 | self.bone_trans_axis_radX = qg.QRadioButton('x') 94 | self.bone_trans_axis_radY = qg.QRadioButton('y') 95 | self.bone_trans_axis_radZ = qg.QRadioButton('z') 96 | self.bone_trans_axis_radX.setChecked(True) 97 | bone_trans_axis_lbl.setMaximumHeight(20) 98 | self.bone_trans_axis_radX.setMaximumHeight(20) 99 | self.bone_trans_axis_radY.setMaximumHeight(20) 100 | self.bone_trans_axis_radZ.setMaximumHeight(20) 101 | 102 | bone_trans_axis_sub = qg.QLabel('Default = x') 103 | bone_trans_axis_sub.setStyleSheet('color: rgb(140,140,140)') 104 | 105 | bone_trans_axis_layoutA = qg.QVBoxLayout() 106 | bone_trans_axis_layoutA.setContentsMargins(0,0,0,0) 107 | bone_trans_axis_layoutA.setSpacing(0) 108 | 109 | bone_trans_axis_layoutB = qg.QHBoxLayout() 110 | bone_trans_axis_layoutB.setContentsMargins(0,0,0,0) 111 | bone_trans_axis_layoutB.setSpacing(0) 112 | 113 | bone_trans_axis_layoutC = qg.QHBoxLayout() 114 | bone_trans_axis_layoutC.setContentsMargins(0,0,0,0) 115 | bone_trans_axis_layoutC.setSpacing(10) 116 | bone_trans_axis_layoutC.setAlignment(qc.Qt.AlignRight) 117 | 118 | 119 | bone_trans_axis_layoutB.addWidget(bone_trans_axis_lbl) 120 | bone_trans_axis_layoutB.addLayout(bone_trans_axis_layoutC) 121 | 122 | 123 | bone_trans_axis_layoutC.addWidget(self.bone_trans_axis_radX) 124 | bone_trans_axis_layoutC.addWidget(self.bone_trans_axis_radY) 125 | bone_trans_axis_layoutC.addWidget(self.bone_trans_axis_radZ) 126 | 127 | bone_trans_axis_layoutA.addLayout(bone_trans_axis_layoutB) 128 | bone_trans_axis_layoutA.addWidget(bone_trans_axis_sub) 129 | bone_trans_axis_sub.setAlignment(qc.Qt.AlignTop) 130 | 131 | self.bone_trans_axis_widget.setLayout(bone_trans_axis_layoutA) 132 | 133 | # Bone up axis widget 134 | self.bone_up_axis_widget = qg.QWidget() 135 | bone_up_axis_lbl = qg.QLabel('Set bone up axis') 136 | self.bone_up_axis_radX = qg.QRadioButton('x') 137 | self.bone_up_axis_radY = qg.QRadioButton('y') 138 | self.bone_up_axis_radZ = qg.QRadioButton('z') 139 | self.bone_up_axis_radZ.setChecked(True) 140 | bone_up_axis_lbl.setMaximumHeight(20) 141 | self.bone_up_axis_radX.setMaximumHeight(20) 142 | self.bone_up_axis_radY.setMaximumHeight(20) 143 | self.bone_up_axis_radZ.setMaximumHeight(20) 144 | 145 | bone_up_axis_sub = qg.QLabel('Default = z') 146 | bone_up_axis_sub.setStyleSheet('color: rgb(140,140,140)') 147 | 148 | bone_up_axis_layoutA = qg.QVBoxLayout() 149 | bone_up_axis_layoutA.setContentsMargins(0,0,0,0) 150 | bone_up_axis_layoutA.setSpacing(0) 151 | 152 | bone_up_axis_layoutB = qg.QHBoxLayout() 153 | bone_up_axis_layoutB.setContentsMargins(0,0,0,0) 154 | bone_up_axis_layoutB.setSpacing(0) 155 | 156 | bone_up_axis_layoutC = qg.QHBoxLayout() 157 | bone_up_axis_layoutC.setContentsMargins(0,0,0,0) 158 | bone_up_axis_layoutC.setSpacing(10) 159 | bone_up_axis_layoutC.setAlignment(qc.Qt.AlignRight) 160 | 161 | 162 | bone_up_axis_layoutB.addWidget(bone_up_axis_lbl) 163 | bone_up_axis_layoutB.addLayout(bone_up_axis_layoutC) 164 | 165 | 166 | bone_up_axis_layoutC.addWidget(self.bone_up_axis_radX) 167 | bone_up_axis_layoutC.addWidget(self.bone_up_axis_radY) 168 | bone_up_axis_layoutC.addWidget(self.bone_up_axis_radZ) 169 | 170 | bone_up_axis_layoutA.addLayout(bone_up_axis_layoutB) 171 | bone_up_axis_layoutA.addWidget(bone_up_axis_sub) 172 | bone_up_axis_sub.setAlignment(qc.Qt.AlignTop) 173 | 174 | self.bone_up_axis_widget.setLayout(bone_up_axis_layoutA) 175 | 176 | # Create VFK button 177 | self.create_vfk_btn = qg.QPushButton('Create VFK Rig') 178 | self.create_vfk_btn.setMaximumSize(200,100) 179 | #btn_grad = qg.QLinearGradient(x1:0, y1:0, x2:1, y2:0, stop: 0 red, stop: 1 blue) 180 | #image_path = pmc.internalVar(upd=True) + 'icons/jiii_buttonBG.png' 181 | #self.create_vfk_btn.setStyleSheet('background-image: url(' + image_path + ');' 182 | # 'border: solid black 1px;') 183 | self.create_vfk_btn.setStyleSheet('border: solid black 1px;' 184 | 'background-color: QLinearGradient(x1:0, y1:0, x2:1, y2:0, stop: 0 rgb(255,0,255), stop: 1 rgb(0,255,255)') 185 | 186 | self.close_on_create_chk = qg.QCheckBox('Close window on rig creation') 187 | self.close_on_create_chk.setCheckState(qc.Qt.Checked) 188 | 189 | def create_layout(self): 190 | tab_widget = qg.QTabWidget() 191 | basic_tab_page = qg.QWidget() 192 | advanced_tab_page = qg.QWidget() 193 | basic_layout = qg.QVBoxLayout(basic_tab_page) 194 | advanced_layout = qg.QVBoxLayout(advanced_tab_page) 195 | btn_layout = qg.QHBoxLayout() 196 | 197 | basic_layout.addWidget(self.name_widget) 198 | basic_layout.addWidget(self.joints_widget) 199 | basic_layout.addWidget(self.controls_widget) 200 | basic_layout.setAlignment(qc.Qt.AlignTop) 201 | 202 | advanced_layout.addWidget(self.control_radius_widget) 203 | advanced_layout.addWidget(self.joint_radius_widget) 204 | advanced_layout.addWidget(self.joint_prefix_widget) 205 | advanced_layout.addWidget(self.joint_grp_prefix_widget) 206 | advanced_layout.addWidget(self.control_prefix_widget) 207 | advanced_layout.addWidget(self.control_grp_prefix_widget) 208 | advanced_layout.addWidget(self.bone_trans_axis_widget) 209 | advanced_layout.addWidget(self.bone_up_axis_widget) 210 | advanced_layout.setAlignment(qc.Qt.AlignTop) 211 | 212 | tab_widget.addTab(basic_tab_page, 'Basic') 213 | tab_widget.addTab(advanced_tab_page, 'Advanced') 214 | 215 | btn_layout.addWidget(self.create_vfk_btn) 216 | #btn_layout.addWidget(self.close_on_create_chk) 217 | 218 | main_layout = qg.QVBoxLayout() 219 | main_layout.setContentsMargins(10,10,10,10) 220 | 221 | main_layout.addWidget(self.header_lbl) 222 | main_layout.addWidget(self.credit_lbl) 223 | main_layout.addWidget(self.link_lbl) 224 | main_layout.addWidget(tab_widget) 225 | main_layout.addLayout(btn_layout) 226 | 227 | main_layout.setAlignment(qc.Qt.AlignTop) 228 | self.setLayout(main_layout) 229 | 230 | def create_connections(self): 231 | self.create_vfk_btn.clicked.connect(self.create_vfk) 232 | 233 | #self.create_vfk_btn.clicked.connect(self._testBind) 234 | 235 | 236 | ############### 237 | # MAIN FUNCTION 238 | ############### 239 | def create_vfk(self, name = "", numJoints=20.0, numControls=3.0, controlRadius = 4.0, 240 | jointRadius=0.25, jointPrefix = 'joint_', jointGroupPrefix ='vfk_grp_', 241 | controlPrefix = 'CTRL_vfk_', controlGroupPrefix = 'OFF_CTRL_vfk_', 242 | boneTranslateAxis = '.tx', boneUpAxis = [0,0,1]): 243 | 244 | ''' 245 | if self.close_on_create_chk.checkState() == qc.Qt.Checked: 246 | self.close 247 | ''' 248 | 249 | 250 | pmc.undoInfo(openChunk=True) 251 | 252 | ### Get top and end joints, check for parents/children 253 | sels = pmc.ls(sl=1) 254 | topJoint = sels[0] 255 | endJoint = sels[1] 256 | 257 | try: 258 | print topJoint, ' and ', endJoint, ' selected.' 259 | ### pmc.listRelatives(topJoint, children=True, type='joint') 260 | except IndexError: 261 | print 'Error: Select a joint and an immediate child joint.' 262 | 263 | 264 | 265 | endChild = pmc.listRelatives(endJoint, c=True) 266 | if endChild: 267 | linkJointEnd = pmc.duplicate(endJoint, parentOnly=True, n=endJoint + '_LINK') 268 | pmc.setAttr(linkJointEnd[0] + '.radius', jointRadius * 2) 269 | pmc.parent(endChild, linkJointEnd) 270 | pmc.parent(linkJointEnd, w=True) 271 | 272 | topParent = pmc.listRelatives(topJoint, p=True) 273 | if topParent: 274 | linkJointTop = pmc.duplicate(topJoint, parentOnly=True, n=topJoint + '_LINK') 275 | pmc.setAttr(linkJointTop[0] + '.radius', jointRadius * 2) 276 | pmc.parent(linkJointTop, topParent) 277 | pmc.parent(topJoint, linkJointTop) 278 | 279 | ### Check basic user-defined values 280 | if self.name_le.text() != "": 281 | name = self.name_le.text() 282 | else: 283 | name = str(topJoint) + '_' 284 | if self.joints_le.text() != "": 285 | numJoints = float(self.joints_le.text()) 286 | if self.controls_le.text() != "": 287 | numControls = float(self.controls_le.text()) 288 | 289 | ### Check advanced user-defined values 290 | if self.control_radius_le.text() != "": 291 | controlRadius = float(self.control_radius_le.text()) 292 | if self.joint_radius_le.text() != "": 293 | jointRadius = float(self.joint_radius_le.text()) 294 | if self.joint_prefix_le.text() != "": 295 | jointPrefix = self.joint_prefix_le.text() 296 | if self.joint_grp_prefix_le.text() != "": 297 | jointGroupPrefix = self.joint_grp_prefix_le.text() 298 | if self.control_prefix_le.text() != "": 299 | controlPrefix = self.control_prefix_le.text() 300 | if self.control_grp_prefix_le.text() != "": 301 | controlGroupPrefix = self.control_grp_prefix_le.text() 302 | 303 | if self.bone_trans_axis_radX.isChecked() == True and self.bone_up_axis_radX.isChecked() == True: 304 | print 'Warning: bone main axis and bone up axis cannot be same.' 305 | return 306 | if self.bone_trans_axis_radY.isChecked() == True and self.bone_up_axis_radY.isChecked() == True: 307 | print 'Warning: bone main axis and bone up axis cannot be same.' 308 | return 309 | if self.bone_trans_axis_radZ.isChecked() == True and self.bone_up_axis_radZ.isChecked() == True: 310 | print 'Warning: bone main axis and bone up axis cannot be same.' 311 | return 312 | 313 | if self.bone_trans_axis_radX.isChecked() == True: 314 | boneTranslateAxis = '.tx' 315 | if self.bone_trans_axis_radY.isChecked() == True: 316 | boneTranslateAxis = '.ty' 317 | if self.bone_trans_axis_radZ.isChecked() == True: 318 | boneTranslateAxis = '.tz' 319 | if self.bone_up_axis_radX.isChecked() == True: 320 | boneUpAxis = [1,0,0] 321 | if self.bone_up_axis_radY.isChecked() == True: 322 | boneUpAxis = [0,1,0] 323 | if self.bone_up_axis_radZ.isChecked() == True: 324 | boneUpAxis = [0,0,1] 325 | 326 | #### ACTUAL FUNCTION PART 327 | nurbsWidth = pmc.getAttr(endJoint + boneTranslateAxis) 328 | 329 | self._jointRes(topJoint, endJoint, boneTranslateAxis= boneTranslateAxis, add= numJoints-2) 330 | 331 | surface = pmc.nurbsPlane(pivot=[0,0,0], axis= boneUpAxis, width=nurbsWidth, lengthRatio=0.1, 332 | u=(numJoints-1), ch=0, n= name + 'vfk_surface') 333 | 334 | ## Uncomment to use as polyPlane instead of nurbsSurface 335 | ## surface = pmc.polyPlane(w=20, h=1, sx=20, sy=1, ax=[0,1,0], cuv=2, ch=0, n= name + 'vfk_surface') 336 | 337 | if boneTranslateAxis == '.ty': 338 | if boneUpAxis == [1,0,0]: 339 | pmc.setAttr(surface[0] + '.rx', -90) 340 | if boneUpAxis == [0,0,1]: 341 | pmc.setAttr(surface[0] + '.rz', -90) 342 | pmc.makeIdentity(surface[0], apply=True, t=0, r=1, s=0) 343 | 344 | if boneTranslateAxis == '.tz': 345 | if boneUpAxis == [0,1,0]: 346 | pmc.setAttr(surface[0] + '.ry', -90) 347 | pmc.makeIdentity(surface[0], apply=True, t=0, r=1, s=0) 348 | 349 | surface_off = pmc.group(surface, n= name + 'OFF_surface') 350 | 351 | pmc.parent(surface_off, topJoint) 352 | if boneTranslateAxis == '.tx': 353 | pmc.xform(surface_off, translation = [nurbsWidth/2, 0, 0], rotation=[0,0,0]) 354 | if boneTranslateAxis == '.ty': 355 | pmc.xform(surface_off, translation = [0, nurbsWidth/2, 0], rotation=[0,0,0]) 356 | if boneTranslateAxis == '.tz': 357 | pmc.xform(surface_off, translation = [0, 0, nurbsWidth/2], rotation=[0,0,0]) 358 | pmc.parent(surface_off, w=True) 359 | 360 | surface_mtx= pmc.xform(surface, q=True, ws=True, m=True) 361 | 362 | joints = pmc.listRelatives(topJoint, children=1, allDescendents=1) 363 | joints.append(topJoint) 364 | joints.reverse() 365 | 366 | for j in xrange(len(joints)): 367 | pmc.select(joints[j]) 368 | pmc.rename(joints[j], jointPrefix + str(j+1)) 369 | 370 | pmc.setAttr(joints[j] + '.radius', jointRadius) 371 | pmc.addAttr(joints[j], ln='position', min=0, max=1, dv=0, keyable=True) 372 | pmc.setAttr(joints[j] + '.position', j/(numJoints-1)) 373 | pmc.select(cl=1) 374 | 375 | jmtx = pmc.xform(joints[j], q=True, m=True, ws=True) 376 | 377 | if j == 0: 378 | off_vfk = pmc.group(em=True, n= name + 'OFF_vfk') 379 | pmc.xform(off_vfk, ws=True, m=jmtx) 380 | root = pmc.listRelatives(joints[0], parent=True) 381 | for c in xrange(int(numControls)): 382 | jparent = pmc.listRelatives(joints[j], parent=True) 383 | vfk_grp = pmc.group(em=True, n= name + jointGroupPrefix + 'j' + str(j+1) + '_c' + str(c+1)) 384 | pmc.xform(vfk_grp, ws=True, m=jmtx) 385 | pmc.parent(joints[j], vfk_grp) 386 | pmc.parent(vfk_grp, jparent) 387 | if c == 0: 388 | pmc.parent(vfk_grp, off_vfk) 389 | if root != None: 390 | pmc.parent(off_vfk, root) 391 | else: 392 | for c in xrange(int(numControls)): 393 | jparent = pmc.listRelatives(joints[j], parent=True) 394 | vfk_grp = pmc.group(em=True, n= name + jointGroupPrefix + 'j' + str(j+1) + '_c' + str(c+1)) 395 | pmc.xform(vfk_grp, ws=True, m=jmtx) 396 | pmc.parent(joints[j], vfk_grp) 397 | pmc.parent(vfk_grp, jparent) 398 | 399 | ctrlSpacing = (nurbsWidth/(numControls+1)) 400 | 401 | for i in xrange(int(numControls)): 402 | if boneTranslateAxis == '.tx': 403 | ctrl_normal = [1,0,0] 404 | if boneTranslateAxis == '.ty': 405 | ctrl_normal = [0,1,0] 406 | if boneTranslateAxis == '.tz': 407 | ctrl_normal = [0,0,1] 408 | ctrl = pmc.circle(normal=ctrl_normal, sw=360, r=controlRadius, ch=0, n= name + controlPrefix + str(i+1)) 409 | ctrl_off = pmc.group(ctrl, n= name + controlGroupPrefix + str(i+1)) 410 | pmc.xform(ctrl_off, ws=True, m=surface_mtx) 411 | 412 | pmc.parent(ctrl_off, surface) 413 | pmc.setAttr(ctrl[0] + boneTranslateAxis, ((nurbsWidth/-2) + (ctrlSpacing*(i+1)))) 414 | pmc.parent(ctrl_off, w=True) 415 | 416 | flcl = self._parentSurfaceFLCL(ctrl, surface[0]) 417 | 418 | ctrl_mtx = pmc.xform(ctrl, q=True, m=True, ws=True) 419 | pmc.xform(ctrl_off, ws=True, m=ctrl_mtx) 420 | pmc.parent(ctrl_off, flcl[0]) 421 | pmc.parent(ctrl, ctrl_off) 422 | 423 | min_falloff = 1/numJoints 424 | 425 | pmc.addAttr(ctrl[0], ln='position', min=0, max=10, dv=0, keyable=True) 426 | pmc.addAttr(ctrl[0], ln='falloff', min=min_falloff, max=1, dv=0.5, keyable=True) 427 | pmc.addAttr(ctrl[0], ln='numberOfJointsAffected', min=0, max=numJoints, dv=0, keyable=True) 428 | 429 | multD = pmc.createNode('multiplyDivide', n= name + 'multD_jAff_vfk_' + str(i+1)) 430 | setR = mc.createNode('setRange', n= name + 'setR_jAff_vfk_' + str(i+1)) 431 | 432 | pmc.connectAttr(ctrl[0] + '.falloff', multD + '.input1X') 433 | pmc.setAttr(multD + '.input2X', 2) 434 | pmc.setAttr(multD + '.operation', 1) 435 | 436 | pmc.connectAttr(multD + '.outputX', setR + '.valueX') 437 | pmc.setAttr(setR + '.oldMinX', 0) 438 | pmc.setAttr(setR + '.oldMaxX', 1) 439 | pmc.setAttr(setR + '.minX', 0) 440 | pmc.setAttr(setR + '.maxX', numJoints) 441 | pmc.connectAttr(setR + '.outValueX', ctrl[0] + '.numberOfJointsAffected') 442 | 443 | 444 | paramU = pmc.getAttr(flcl[0] + '.parameterU') 445 | 446 | div_ten = pmc.createNode('multiplyDivide', n="DIV_" + name + controlPrefix + str(i+1)) 447 | pmc.setAttr(div_ten.input2X, 10) 448 | pmc.setAttr(div_ten.operation, 2) 449 | 450 | pmc.connectAttr(ctrl[0] + '.position', div_ten.input1X) 451 | 452 | pmc.connectAttr(div_ten.outputX, flcl[0] + '.parameterU') 453 | 454 | pmc.setAttr(ctrl[0] + '.position', paramU * 10.0) 455 | 456 | fPos_plus = pmc.createNode('plusMinusAverage', n= name + 'fPosPlus_vfk_' + str(i+1)) 457 | pmc.connectAttr(div_ten.outputX, fPos_plus + '.input1D[0]', f=True) 458 | pmc.connectAttr(ctrl[0] + '.falloff', fPos_plus + '.input1D[1]', f=True) 459 | pmc.setAttr(fPos_plus + '.operation', 1) 460 | 461 | fPos_minus = pmc.createNode('plusMinusAverage', n= name + 'fPosMinus_vfk_' + str(i+1)) 462 | pmc.connectAttr(div_ten.outputX, fPos_minus + '.input1D[0]', f=True) 463 | pmc.connectAttr(ctrl[0] + '.falloff', fPos_minus + '.input1D[1]', f=True) 464 | pmc.setAttr(fPos_minus + '.operation', 2) 465 | 466 | for f in (fPos_plus, fPos_minus): 467 | for j in xrange(len(joints)): 468 | upperM = pmc.createNode('plusMinusAverage', n= (name + f + '_upperM_j' + str(j+1) + '_c' + str(i+1))) 469 | lowerM = pmc.createNode('plusMinusAverage', n= (name + f + '_lowerM_j' + str(j+1) + '_c' + str(i+1))) 470 | 471 | pmc.setAttr(upperM + '.operation', 2) 472 | pmc.setAttr(lowerM + '.operation', 2) 473 | 474 | pmc.connectAttr(joints[j] + '.position', upperM + '.input1D[0]') 475 | pmc.connectAttr(f + '.output1D', upperM + '.input1D[1]') 476 | 477 | pmc.connectAttr(div_ten.outputX, lowerM + '.input1D[0]') 478 | pmc.connectAttr(f + '.output1D', lowerM + '.input1D[1]') 479 | 480 | divA = pmc.createNode('multiplyDivide', n= f + '_divA_j' + str(j+1) + '_c' + str(i+1)) 481 | pmc.setAttr(divA + '.operation', 2) 482 | pmc.connectAttr(upperM + '.output1D', divA + '.input1X') 483 | pmc.connectAttr(lowerM + '.output1D', divA + '.input2X') 484 | 485 | multA = pmc.createNode('multiplyDivide', n= f + '_multA_j' + str(j+1) + '_c' + str(i+1)) 486 | pmc.setAttr(multA + '.operation', 1) 487 | pmc.connectAttr(divA + '.outputX', multA + '.input1X') 488 | pmc.setAttr(multA + '.input2X', 2) 489 | 490 | divB = pmc.createNode('multiplyDivide', n= f + '_divB_j' + str(j+1) + '_c' + str(i+1)) 491 | pmc.setAttr(divB + '.operation', 2) 492 | pmc.connectAttr(multA + '.outputX', divB + '.input1X') 493 | pmc.connectAttr(ctrl[0] + '.numberOfJointsAffected', divB + '.input2X') 494 | 495 | 496 | for j in xrange(len(joints)): 497 | cond = pmc.createNode('condition', n= name + 'cond_j' + str(j+1) + '_c' + str(i+1)) 498 | pmc.setAttr(cond + '.operation', 3) 499 | pmc.connectAttr(div_ten.outputX, cond + '.firstTerm') # then use minus 500 | pmc.connectAttr(joints[j] + '.position', cond + '.secondTerm') # then use plus 501 | 502 | pmc.connectAttr(fPos_minus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfTrueR') 503 | pmc.connectAttr(fPos_minus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfTrueG') 504 | pmc.connectAttr(fPos_minus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfTrueB') 505 | 506 | pmc.connectAttr(fPos_plus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfFalseR') 507 | pmc.connectAttr(fPos_plus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfFalseG') 508 | pmc.connectAttr(fPos_plus + '_divB_j' + str(j+1) + '_c' + str(i+1) + '.outputX', cond + '.colorIfFalseB') 509 | 510 | cond_neg = pmc.createNode('condition', n= name + 'cond_neg_j' + str(j+1) + '_c' + str(i+1)) 511 | pmc.connectAttr(cond + '.outColorR', cond_neg + '.firstTerm') 512 | pmc.setAttr(cond_neg + '.secondTerm', 0) 513 | pmc.setAttr(cond_neg + '.operation', 2) 514 | pmc.connectAttr(cond + '.outColor', cond_neg + '.colorIfTrue') 515 | pmc.setAttr(cond_neg + '.colorIfFalse', [0,0,0]) 516 | 517 | multiFinalRot = pmc.createNode('multiplyDivide', n= name + 'multiFinalRot_j' + str(j+1) + '_c' + str(i+1)) 518 | pmc.setAttr(multiFinalRot + '.operation', 1) 519 | pmc.connectAttr(cond_neg + '.outColor', multiFinalRot + '.input1') 520 | pmc.connectAttr(ctrl[0] + '.rotate', multiFinalRot + '.input2') 521 | 522 | pmc.connectAttr(multiFinalRot + '.output', name + jointGroupPrefix + 'j' + str(j+1) + '_c' + str(i+1) + '.rotate') 523 | 524 | ''' 525 | multiFinalScl = pmc.createNode('multiplyDivide', n= name + 'multiFinalScl_j' + str(j+1) + '_c' + str(i+1)) 526 | pmc.setAttr(multiFinalScl + '.operation', 1) 527 | pmc.connectAttr(cond + '.outColor', multiFinalScl + '.input1') 528 | pmc.connectAttr(ctrl[0] + '.scale', multiFinalScl + '.input2') 529 | 530 | pmc.connectAttr(multiFinalScl + '.output', name + jointGroupPrefix + 'j' + str(j+1) + '_c' + str(i+1) + '.scale') 531 | ''' 532 | self._ctrlDBL(ctrl) 533 | 534 | if endChild: 535 | pmc.parent(linkJointEnd, endJoint) 536 | 537 | pmc.undoInfo(closeChunk=True) 538 | pmc.undoInfo(openChunk=True) 539 | 540 | pmc.skinCluster(joints, name + 'vfk_surface', mi=1) 541 | 542 | pmc.undoInfo(closeChunk=True) 543 | 544 | 545 | #################### 546 | # INTERNAL FUNCTIONS 547 | #################### 548 | def _jointRes(self, parentJoint, endJoint, boneTranslateAxis = '.tx', add=2.0, name="subJoint_"): 549 | pmc.undoInfo(openChunk=True) 550 | 551 | selJoints = pmc.ls(selection=True) 552 | 553 | boneLength = mc.getAttr(endJoint + boneTranslateAxis) 554 | boneRadius = mc.getAttr(parentJoint + ".radius") 555 | 556 | mc.select(cl=True) 557 | 558 | for i in xrange(int(round(add))): 559 | newJoint = pmc.joint(name=name + str(i+1), radius = (boneRadius*2)) 560 | mc.select(cl=True) 561 | pmc.parent(newJoint, parentJoint, relative=True) 562 | mc.setAttr(newJoint + boneTranslateAxis, (boneLength/(add + 1))) 563 | if i > 0: 564 | pmc.parent(newJoint, (name + str(i)), relative = True) 565 | if i == add - 1: 566 | pmc.parent(endJoint, newJoint) 567 | 568 | pmc.undoInfo(closeChunk=True) 569 | 570 | 571 | def _parentSurfaceFLCL(self, constrained_obj, geo, deleteCPOMS=1): 572 | """ 573 | Parents object to follicle at closest point on surface. 574 | Select child transform, then select mesh to hold parent follicle. 575 | 576 | """ 577 | cpos = pmc.createNode('closestPointOnSurface', n='cpos_flcl_' + geo) 578 | 579 | mc.connectAttr(pmc.listRelatives(geo, shapes=True, children=True)[0] + '.local', cpos + '.inputSurface') 580 | obj_mtx = pmc.xform(constrained_obj, q=True, m=True) 581 | pmc.setAttr(cpos + '.inPosition', [obj_mtx[12], obj_mtx[13], obj_mtx[14]]) 582 | 583 | flclShape = pmc.createNode('follicle', n='flclShape' + geo) 584 | flcl = pmc.listRelatives(flclShape, type='transform', parent=True) 585 | pmc.rename(flcl, 'flcl_' + geo + '_1') 586 | 587 | mc.connectAttr(flclShape + '.outRotate', flcl[0] + '.rotate') 588 | mc.connectAttr(flclShape + '.outTranslate', flcl[0] + '.translate') 589 | mc.connectAttr(geo + '.worldMatrix', flclShape + '.inputWorldMatrix') 590 | mc.connectAttr(geo + '.local', flclShape + '.inputSurface') 591 | mc.setAttr(flclShape + '.simulationMethod', 0) 592 | 593 | u = mc.getAttr(cpos + '.result.parameterU') 594 | v = mc.getAttr(cpos + '.result.parameterV') 595 | pmc.setAttr(flclShape + '.parameterU', u) 596 | pmc.setAttr(flclShape + '.parameterV', v) 597 | 598 | pmc.parent(constrained_obj, flcl) 599 | if deleteCPOMS == 1: 600 | pmc.delete(cpos) 601 | 602 | return flcl 603 | 604 | ### offset double tranlate/rotate tranforms on a given control 605 | def _ctrlDBL(self, controls): 606 | 607 | pmc.undoInfo(openChunk=True) 608 | 609 | if controls == []: 610 | controls = pmc.ls(sl=True) 611 | 612 | for control in controls: 613 | control_roo = pmc.xform(control, q=True, roo=True) 614 | control_mtx = pmc.xform(control, q=True, m=True, ws=True) 615 | control_parent = pmc.listRelatives(control, p=True) 616 | pmc.select(cl=True) 617 | 618 | locdbl_parent = pmc.spaceLocator(n='locDBL_parent_' + control) 619 | locdbl_offset = pmc.spaceLocator(n='locDBL_offset_' + control) 620 | 621 | pmc.xform(locdbl_parent, ws=True, m=control_mtx) 622 | pmc.xform(locdbl_offset, ws=True, m=control_mtx) 623 | 624 | pmc.parent(locdbl_offset, locdbl_parent) 625 | pmc.parent(locdbl_parent, control_parent) 626 | pmc.parent(control, locdbl_offset) 627 | 628 | if control_roo == 'xyz': 629 | pmc.xform(locdbl_offset, roo='zyx') 630 | if control_roo == 'yzx': 631 | pmc.xform(locdbl_offset, roo='xzy') 632 | if control_roo == 'zxy': 633 | pmc.xform(locdbl_offset, roo='yxz') 634 | if control_roo == 'xzy': 635 | pmc.xform(locdbl_offset, roo='yzx') 636 | if control_roo == 'yxz': 637 | pmc.xform(locdbl_offset, roo='zxy') 638 | if control_roo == 'zyx': 639 | pmc.xform(locdbl_offset, roo='xyz') 640 | 641 | md_trns = pmc.createNode('multiplyDivide', n='mdTRNS_locDBL_' + control) 642 | md_rot = pmc.createNode('multiplyDivide', n='mdROT_locDBL_' + control) 643 | md_scl = pmc.createNode('multiplyDivide', n='mdSCL_locDBL_' + control) 644 | 645 | pmc.setAttr(md_trns + '.input1', [-1,-1,-1]) 646 | pmc.setAttr(md_rot.input1, [-1,-1,-1]) 647 | pmc.setAttr(md_scl.input1, [ 1, 1, 1]) 648 | pmc.setAttr(md_scl.operation, 2) 649 | 650 | 651 | pmc.connectAttr(control + '.translate', md_trns + '.input2') 652 | pmc.connectAttr(control + '.rotate', md_rot + '.input2') 653 | pmc.connectAttr(control + '.scale', md_scl + '.input2') 654 | 655 | pmc.connectAttr(md_trns + '.output', locdbl_offset + '.translate') 656 | pmc.connectAttr(md_rot + '.output', locdbl_offset + '.rotate') 657 | pmc.connectAttr(md_scl + '.output', locdbl_offset + '.scale') 658 | 659 | pmc.setAttr(locdbl_parent + 'Shape.visibility', 0) 660 | pmc.setAttr(locdbl_offset + 'Shape.visibility', 0) 661 | 662 | pmc.undoInfo(closeChunk=True) 663 | 664 | 665 | 666 | if __name__ == '__main__': 667 | try: 668 | vfk_ui.deleteLater() 669 | except: 670 | pass 671 | 672 | vfk_ui = VFK_UI() 673 | 674 | try: 675 | vfk_ui.create_ui() 676 | vfk_ui.show() 677 | except: 678 | vfk_ui.deleteLater() --------------------------------------------------------------------------------