├── BlendShape_Transfer ├── BlendShape_Transfer.py └── BlendShape_Transfer_CHS.py ├── BlendShape_Transfer_demonstration.mp4 ├── LICENSE ├── README.md ├── Uninstall BlendShape Transfer.py ├── install BlendShape Transfer CHS.py └── install BlendShape Transfer.py /BlendShape_Transfer/BlendShape_Transfer.py: -------------------------------------------------------------------------------- 1 | # -!- coding: utf-8 -!- 2 | # Author : AWACS 3 | # Time : 2022/9/10 4 | # version : 0.56 beta 5 | 6 | import maya.cmds as cmds 7 | import os 8 | 9 | # def Question_Button( COMMAND = None , blank_space = 1 , btn_label = " ? " ): 10 | # cmds.text( l= " " ,w = blank_space ) 11 | # cmds.button( c=lambda *args: eval( COMMAND ) , l= btn_label , w=20 ) 12 | 13 | # def Question_Window( imageNum = 0 , Window_name = 'Question_Window' , w = 540 , h = 540): 14 | # if cmds.window( Window_name , q=1, ex=1 ): 15 | # cmds.deleteUI( Window_name ) 16 | # cmds.window( Window_name ) 17 | # #cmds.dockControl( area='left', content=myWindow, allowedArea=allowedAreas ) 18 | # imagelist = ["Question_BlendShape_Transfer_1.jpg" , 19 | # "Question_BlendShape_Transfer_2.jpg" , 20 | # "Question_BlendShape_Transfer_3.jpg" , 21 | # "Question_BlendShape_Transfer_4.jpg" , 22 | # "Question_BlendShape_Transfer_5.jpg" , 23 | # ] 24 | # print ( Question_Window_image_path + "/" + imagelist[ imageNum ] ) 25 | # cmds.paneLayout() 26 | # cmds.image( image= Question_Window_image_path + "/" + imagelist[ imageNum ] ) 27 | 28 | # cmds.showWindow( Window_name ) 29 | 30 | def BlendShape_Transfer_GUI(): 31 | if cmds.window('BlendShape_Transfer', q = 1, ex = 1): 32 | cmds.deleteUI('BlendShape_Transfer') 33 | 34 | cmds.window('BlendShape_Transfer') 35 | cmds.showWindow('BlendShape_Transfer') 36 | cmds.columnLayout() 37 | 38 | cmds.rowLayout(nc = 6) 39 | cmds.text(label = " 1: ") 40 | cmds.text(label = "Transfer Method : ", w = 240, align = "right") 41 | cmds.optionMenu( "Transfer_Method" , w = 180 ) 42 | cmds.menuItem( label='Wrap deformer' ) 43 | cmds.menuItem( label='Sorry,No others method finished yet' ) 44 | # Question_Button("Question_Window( 0 )", 10) 45 | cmds.setParent('..') 46 | 47 | cmds.rowLayout(nc = 6) 48 | cmds.text(label = " 2: ") 49 | cmds.text(label = "Select Original Mesh ( with old BS Node ) : ", w = 240, align = "right") 50 | cmds.textField('Input_Source_Mesh', w = 140, h = 24, text = "") 51 | cmds.button(c = lambda *args: Set_Source_Geo(), label = "Confirm", w = 50) 52 | # Question_Button("Question_Window( 0 )", 10) 53 | cmds.setParent('..') 54 | 55 | cmds.rowLayout(nc = 6) 56 | cmds.text(label = " 3: ") 57 | cmds.text(label = "Select Target Mesh ( Transfer to Where ) : ", w = 240, align = "right") 58 | cmds.textField('Input_Target_Mesh', w = 140, h = 24, text = "") 59 | cmds.button(c = lambda *args: Set_Target_Geo(), label = "Confirm", w = 50) 60 | cmds.setParent('..') 61 | 62 | cmds.rowLayout(nc = 6) 63 | cmds.text(label = " 4: ") 64 | cmds.text(label = "Set Source Blendshape (Optional) : ", w = 240, align = "right") 65 | cmds.textField('InputBS', w = 140, h = 24, text = "") 66 | cmds.button(c = lambda *args: Set_Source_BS(), label = "Confirm", w = 50) 67 | cmds.setParent('..') 68 | 69 | cmds.rowLayout(nc = 6) 70 | cmds.text(label = " 5: ") 71 | cmds.text(label = " New Blendshape Name : ", w = 240, align = "right") 72 | cmds.textField('New_BS_Name', w = 180, h = 24, text = "BS_Transfered") 73 | cmds.setParent('..') 74 | 75 | cmds.rowLayout(nc = 6) 76 | cmds.text(label = " 6: ") 77 | # cmds.text(label = "Add to Existing Blendshape Node : ", w = 140) 78 | 79 | cmds.checkBox( "Add_to_exist_BS" , l =" Add to existing Blendshape : ", value = False, w = 240, align = "right") 80 | # cmds.text(label = " Existing_BS : " ,w = 80) 81 | cmds.textField('Existing_BS', w = 140, h = 24, text = "") 82 | cmds.button(c = lambda *args: Set_Existing_BS(), label = "Confirm", w = 50) 83 | cmds.setParent('..') 84 | 85 | # cmds.rowLayout(nc = 6) 86 | # cmds.text(label = " 6: ") 87 | # cmds.text(label = "Distant threshold : ") 88 | # cmds.floatField("Distant_threshold", max = 0.1, min = 0.0, value = 0.002) 89 | # cmds.setParent('..') 90 | cmds.rowLayout(nc = 6) 91 | cmds.text(label = " 7: ") 92 | # cmds.text(label = "Add to Existing Blendshape Node : ", w = 140) 93 | 94 | cmds.checkBox( "Append_Driven" , l =" Append Driven ", value = False, w = 240, align = "right") 95 | # cmds.text(label = " Existing_BS : " ,w = 80) 96 | cmds.setParent('..') 97 | 98 | 99 | cmds.rowLayout(nc = 6) 100 | cmds.text(label = " 8: ") 101 | cmds.button(c = lambda *args: ExecuteTransferBS(), label = "Execute BlendShape Transfer ", w = 180) 102 | cmds.setParent('..') 103 | 104 | 105 | cmds.rowLayout(nc = 6) 106 | cmds.text(label = " Notice : Scale X,Y,Z of mesh is better freeze at 1.0 ") 107 | cmds.setParent('..') 108 | 109 | def Object_Type_Checker( InputObject, Object_Type ): 110 | """ 111 | Check node type , check for shape if it is transform 112 | """ 113 | if cmds.nodeType(InputObject) == Object_Type: 114 | return True 115 | else: 116 | if cmds.nodeType(InputObject) == "transform": 117 | RelativeNodes = cmds.listRelatives(InputObject) 118 | if RelativeNodes: 119 | for indRelative in RelativeNodes: 120 | if cmds.nodeType(indRelative) == Object_Type: 121 | return True 122 | return False 123 | 124 | def Set_Source_Geo(): 125 | Selection = cmds.ls(selection = True) 126 | if Selection: 127 | if Object_Type_Checker(Selection[0], "mesh"): 128 | cmds.textField('Input_Source_Mesh', e = 1, tx = Selection[0]) 129 | print ("//// Source Mesh Selected : " + Selection[0]) 130 | else: 131 | print ("//// Error Object Select , Please Select A Polygon Mesh ") 132 | else: 133 | print("//// Nothing Selected") 134 | 135 | def Set_Target_Geo(): 136 | Selection = cmds.ls(selection = True) 137 | if Selection: 138 | if Object_Type_Checker(Selection[0], "mesh"): 139 | cmds.textField('Input_Target_Mesh', e = 1, tx = Selection[0]) 140 | print ("//// Target Mesh Selected : " + Selection[0]) 141 | else: 142 | print ("//// Error Object Select , Please Select A Polygon Mesh ") 143 | else: 144 | print("//// Nothing Selected") 145 | 146 | def Set_Source_BS(): 147 | Selection = cmds.ls(selection = True) 148 | if Selection: 149 | Source_BS_list = [] 150 | for indSel in Selection: 151 | if Object_Type_Checker(indSel, "blendShape"): 152 | Source_BS_list.append(indSel) 153 | print (Source_BS_list) 154 | if len(Source_BS_list) < 1: 155 | print ("//// Error Object Select , Please Select A BlendShape Node at least ") 156 | else: 157 | BS_Display = "" 158 | for indBS in Source_BS_list: 159 | BS_Display += indBS 160 | BS_Display += " " 161 | cmds.textField('InputBS', e = 1, tx = BS_Display) 162 | print ("//// Source BlendShape Selected : " + BS_Display) 163 | else: 164 | cmds.textField('InputBS', e = 1, tx = None) 165 | print ("//// Source BlendShape is empty,I will try to find a existing Blendshape if execute transfering ") 166 | 167 | def Set_Existing_BS( ): 168 | Selection = cmds.ls(selection = True) 169 | if Selection: 170 | if Object_Type_Checker(Selection[0], "blendShape"): 171 | cmds.textField('Existing_BS', e = 1, tx = Selection[0]) 172 | print ("//// Existing Blendshape Selected : " + Selection[0]) 173 | else: 174 | print ("//// Error Object Select , Please Select A BlendShape Node ") 175 | else: 176 | print("//// Nothing Selected") 177 | 178 | def SearchingForBlendshape( InputNode ): 179 | """ 180 | try to find Blendshape node from a mesh 181 | """ 182 | RelativeNodes = cmds.listRelatives(InputNode ,shapes = True) 183 | SearchResult = [] 184 | # Result_counter = 0 185 | if RelativeNodes: 186 | for indRel in RelativeNodes: 187 | # print (indRel) 188 | indRel_Rel_Nodes = cmds.listConnections(indRel) 189 | if indRel_Rel_Nodes: 190 | print (indRel_Rel_Nodes) 191 | for indRel_Rel_Rel in indRel_Rel_Nodes: 192 | if Object_Type_Checker(indRel_Rel_Rel, "blendShape"): 193 | SearchResult.append(indRel_Rel_Rel) 194 | # Result_counter += 1 195 | SearchResult = list(set(SearchResult)) 196 | print( InputNode + " BS Search Result : " + str(SearchResult) ) 197 | # if Result_counter > 0 : 198 | print (SearchResult) 199 | if SearchResult == []: 200 | return None 201 | return SearchResult 202 | # else: 203 | # return None 204 | else: 205 | return None 206 | 207 | 208 | def ExecuteTransferBS(): 209 | Source_Geo = cmds.textField('Input_Source_Mesh', query = 1, text = 1) 210 | Target_Geo = cmds.textField('Input_Target_Mesh', query = 1, text = 1) 211 | Source_BS_Text = cmds.textField('InputBS', query = 1, text = 1) 212 | NewBSName = cmds.textField('New_BS_Name', query = 1, text = 1) 213 | 214 | Append_Driven = cmds.checkBox( "Append_Driven" , query = 1, value = 1) 215 | Add_to_Exist_BS = cmds.checkBox( "Add_to_exist_BS" , query = 1, value = 1) 216 | Existing_BS = cmds.textField( "Existing_BS" , query = 1, text = 1) 217 | 218 | if Add_to_Exist_BS: 219 | if len(Existing_BS) < 1 : 220 | Existing_BS = SearchingForBlendshape(Target_Geo) 221 | 222 | if Existing_BS is None : 223 | Add_to_Exist_BS = False 224 | 225 | if len(Source_BS_Text) > 0: 226 | Source_BS = Source_BS_Text.split(" ") 227 | else: 228 | Source_BS = SearchingForBlendshape(Source_Geo) 229 | 230 | print(Source_BS) 231 | 232 | if Source_BS: 233 | for IndSrcBS in Source_BS: 234 | if IndSrcBS == "" : 235 | continue 236 | if len(IndSrcBS) > 0 : 237 | print((Source_Geo, IndSrcBS, Target_Geo, Add_to_Exist_BS , Existing_BS , False, Append_Driven ,NewBSName)) 238 | BlendShape_Transfer_Main(Source_Geo, IndSrcBS, Target_Geo, Add_to_Exist_BS , Existing_BS , False, Append_Driven ,NewBSName) 239 | else: 240 | print( "No Blendshape Node Found , please select Blendshape Node And Confirm Manually" ) 241 | 242 | def Create_Wrap( DriveR_Mesh, DriveN_Mesh, name, threshold = 0, maxDistance = 1.0, exclusiveBind = True, 243 | autoWeightThreshold = True, 244 | falloffMode = 0 ): 245 | """ 246 | Create Wrap and setting up parameter 247 | """ 248 | cmds.select(cl = 1) 249 | cmds.select(DriveN_Mesh) 250 | cmds.select(DriveR_Mesh, add = 1) 251 | tempString = cmds.deformer(type = 'wrap', n = name) 252 | wrapNode = tempString[0] 253 | cmds.setAttr(wrapNode + ".weightThreshold", threshold) 254 | cmds.setAttr(wrapNode + ".maxDistance", maxDistance) 255 | cmds.setAttr(wrapNode + ".exclusiveBind", exclusiveBind) 256 | cmds.setAttr(wrapNode + ".autoWeightThreshold", autoWeightThreshold) 257 | cmds.setAttr(wrapNode + ".falloffMode", falloffMode) 258 | 259 | cmds.AddWrapInfluence() 260 | cmds.select(cl = 1) 261 | return wrapNode 262 | 263 | def Create_BS( SourceGeo, BS_Name ): 264 | cmds.select(cl = 1) 265 | cmds.select(SourceGeo) 266 | BS = cmds.blendShape(automatic = 1) 267 | renameResult = cmds.rename(BS[0], BS_Name) 268 | return renameResult 269 | 270 | 271 | def Initialize_BS( BSNode ): 272 | """ 273 | Zero out Blendshape attributes 274 | """ 275 | BS_Node_target_list = cmds.listAttr(BSNode + ".w", k = True, m = True) 276 | cmds.setAttr(BSNode + ".envelope", 1) 277 | for indBS_Node_target in BS_Node_target_list: 278 | try: 279 | cmds.setAttr(BSNode + '.' + indBS_Node_target, 0) 280 | except: 281 | print("Initialize BS Func Failed , Failed to set 0 at ------" + BSNode + '.' + indBS_Node_target) 282 | 283 | 284 | def polySmooth_Custom( InputObject, Level = 1 ): 285 | cmds.polySmooth(InputObject, 286 | constructionHistory = 1, 287 | osdSmoothTriangles = 0, 288 | keepHardEdge = 0, 289 | pushStrength = 0.1, 290 | keepMapBorders = 1, 291 | boundaryRule = 1, 292 | method = 0, 293 | smoothUVs = 1, 294 | propagateEdgeHardness = 0, 295 | keepSelectionBorder = 1, 296 | roundness = 1, 297 | subdivisionType = 2, 298 | osdFvarPropagateCorners = 0, 299 | keepTessellation = 1, 300 | osdVertBoundary = 1, 301 | divisions = 1, 302 | osdFvarBoundary = 3, 303 | keepBorder = 1, 304 | continuity = 1, 305 | osdCreaseMethod = 0, 306 | divisionsPerEdge = 1, 307 | subdivisionLevels = Level) 308 | 309 | 310 | def Point_distance_Checker ( inputgeoA , inputgeoB , threshold , faster = True ): 311 | """ 312 | check if mismatch pointpos 313 | mismatch return True 314 | match return False 315 | """ 316 | ptnum = cmds.polyEvaluate( inputgeoA )['vertex'] 317 | cmds.cycleCheck(e=False) 318 | 319 | if faster: 320 | for i in range( ptnum ): 321 | VPosA = cmds.xform( inputgeoA + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 322 | VPosB = cmds.xform( inputgeoB + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 323 | 324 | if abs( VPosB[0] - VPosA[0] ) > threshold: 325 | cmds.cycleCheck(e=True) 326 | return True 327 | if abs( VPosB[1] - VPosA[1] ) > threshold: 328 | cmds.cycleCheck(e=True) 329 | return True 330 | if abs( VPosB[2] - VPosA[2]) > threshold: 331 | cmds.cycleCheck(e=True) 332 | return True 333 | cmds.cycleCheck(e=True) 334 | return False 335 | else: 336 | for i in range( ptnum ): 337 | VPosA = cmds.xform( inputgeoA + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 338 | VPosB = cmds.xform( inputgeoB + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 339 | 340 | dist3 = ( ( VPosB[0] - VPosA[0] ) ** 2 + ( VPosB[1] - VPosA[1] ) ** 2 + ( VPosB[2] - VPosA[2] ) ** 2 ) 341 | 342 | if dist3 > threshold**2 : 343 | cmds.cycleCheck(e=True) 344 | return True 345 | 346 | cmds.cycleCheck(e=True) 347 | return False 348 | 349 | def BlendShape_Transfer_Main( Source_Geo, BS_Node, Target_Geo, Add_to_Existing_BS, Existing_BS, 350 | Subdivided_Transfer, Append_Driven , NewBSName = "BS_Transfered" ): 351 | # LowPolySrc , BS_of_LowPolySrc ):, HighPolyGeo , HighPoly_is_Extended , Transfer_Vertex , Transfer_Method , New_name_list ): 352 | # transfer the low Poly Mesh Blendshape to a Extended High Poly Mesh 353 | #=====================initialize=================== 354 | BS_Node_target_list = cmds.listAttr(BS_Node + ".w", k = True, m = True) 355 | print (BS_Node_target_list) 356 | #=====================initialize=================== 357 | 358 | 359 | # ----- recording input Connection of Source BS 360 | Target_input_Connections = {} 361 | for indTarget in BS_Node_target_list: 362 | indTarget_plugs = BS_Node + '.' + indTarget 363 | try: 364 | indTarget_Driven_input = cmds.listConnections( indTarget_plugs , plugs = True )[0] 365 | Target_input_Connections[indTarget] = indTarget_Driven_input 366 | except: 367 | Target_input_Connections[indTarget] = False 368 | 369 | # ----- disconnectAttr Source BS 370 | for indTarget , indInput_connection in Target_input_Connections.items(): 371 | indTarget_plugs = BS_Node + '.' + indTarget 372 | if indInput_connection : 373 | try: 374 | cmds.disconnectAttr( indInput_connection , indTarget_plugs) 375 | except: 376 | print( "Failed to disconnectAttr {} ---- {} ".format( indTarget_plugs , indInput_connection)) 377 | 378 | Initialize_BS(BS_Node) 379 | 380 | #=====================initialize End=================== 381 | 382 | #=====================Wraping Start=================== 383 | #1first Duplicate a Target Geo 384 | Target_Geo_Transfer_Ready = cmds.duplicate(Target_Geo, n = Target_Geo + "_Temp_Transfer_Ready")[0] 385 | 386 | # ----------- duplicate entire Source geometry with upstreamNodes is safer ? 387 | # Duplicated_Wrap_Src = cmds.duplicate(Source_Geo, upstreamNodes = True , renameChildren = True ) 388 | # Duplicated_Wrap_Src = [ Source_Geo ] 389 | 390 | 391 | Wrap_Src_Geo = Source_Geo 392 | Wrap_Src_BS = BS_Node 393 | 394 | WarpNode = Create_Wrap(Wrap_Src_Geo, Target_Geo_Transfer_Ready, "WarpNode_Temp_For_BS_Transfer", 0, 1.0, False, False, 0) 395 | 396 | 397 | # try update????????????? 398 | # there may be some issue about maya dont update deformer before first time executing duplicate command 399 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 400 | Temp_duplicateFirst = cmds.duplicate( Target_Geo_Transfer_Ready, n = Target_Geo_Transfer_Ready + "_Temp_" + "duplicateFirst_" + indTarget) 401 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 0) 402 | cmds.delete(Temp_duplicateFirst) 403 | # try update Finished ?????????????-------------- 404 | 405 | 406 | # --------- get individual wrap result 407 | Duped_Target_Geo_list = [] 408 | for indTarget in BS_Node_target_list: 409 | 410 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 411 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 412 | # cmds.setAttr(WarpNode + ".envelope", 0) 413 | # cmds.setAttr(WarpNode + ".envelope", 1) 414 | # cmds.refresh( f = 1 ) 415 | # cmds.refresh( f = 1 ) 416 | ind_tmp_BS_Target = cmds.duplicate( Target_Geo_Transfer_Ready, n = Target_Geo_Transfer_Ready + "_Temp_" + indTarget) 417 | 418 | 419 | Duped_Target_Geo_list.append(ind_tmp_BS_Target[0]) 420 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 0) 421 | 422 | # ----- reconnectAttr Source BS 423 | for indTarget , indInput_connection in Target_input_Connections.items(): 424 | indTarget_plugs = Wrap_Src_BS + '.' + indTarget 425 | if indInput_connection : 426 | try: 427 | cmds.connectAttr( indInput_connection , indTarget_plugs , f=1) 428 | except: 429 | print( "Failed to disconnectAttr {} ---- {} ".format( indTarget_plugs , indInput_connection)) 430 | pass 431 | 432 | cmds.delete(WarpNode) 433 | cmds.delete(Target_Geo_Transfer_Ready) 434 | #=====================Wraping End=================== 435 | #=====================Target BS setting up Start=================== 436 | Target_Index = 0 437 | 438 | if Add_to_Existing_BS: 439 | FinalBS = Existing_BS[0] 440 | Existing_BS_Node_target_list = cmds.listAttr( FinalBS + ".w", k = True, m = True) 441 | print( Existing_BS_Node_target_list ) 442 | # ----- increaseing after 2 443 | Target_Index += len(Existing_BS_Node_target_list) + 2 444 | else: 445 | if len(NewBSName) < 1: 446 | FinalBS = Create_BS(Target_Geo, Target_Geo + "_BS") 447 | else: 448 | FinalBS = Create_BS(Target_Geo, NewBSName) 449 | 450 | print ("Transfer to Blendshape : Blendshape name : {}".format (FinalBS) ) 451 | #=====================Target BS setting up End=================== 452 | 453 | #=====================Add Target to New BS Start=================== 454 | cmds.select(cl = 1) 455 | for indTargetindex in range(len(Duped_Target_Geo_list)): 456 | print("transfering : {} ".format( BS_Node_target_list[indTargetindex] )) 457 | ind_tmp_BS_Target = Duped_Target_Geo_list[indTargetindex] 458 | BypassUnuseful_target = False 459 | 460 | if BypassUnuseful_target: 461 | mismatch = Point_distance_Checker( Target_Geo , ind_tmp_BS_Target , 0.02 ) 462 | if mismatch is False : 463 | print("below point distance check threshold , by passed ") 464 | continue 465 | 466 | Target_Geo_Shape = cmds.listRelatives( Target_Geo , shapes = True )[0] 467 | 468 | cmds.select( ind_tmp_BS_Target ) 469 | cmds.blendShape( FinalBS , e = 1, tc = True, w = (Target_Index, 0), 470 | t = ( Target_Geo , Target_Index, ind_tmp_BS_Target + 'Shape', 1)) 471 | 472 | indTarget = BS_Node_target_list[ indTargetindex ] 473 | indTarget_name = indTarget 474 | try: 475 | cmds.aliasAttr(indTarget_name, FinalBS + '.w[' + str(Target_Index) + ']') 476 | except: 477 | print( "name conflict might appear : " + indTarget_name ) 478 | indTarget_name = indTarget_name + '_Name_Conflict' 479 | cmds.aliasAttr(indTarget_name , FinalBS + '.w[' + str(Target_Index) + ']') 480 | 481 | #=====================Addend driven attr Start=================== 482 | if Append_Driven: 483 | indTarget_plugs = FinalBS + '.' + indTarget 484 | if Target_input_Connections[ indTarget ] : 485 | try: 486 | cmds.connectAttr( Target_input_Connections[ indTarget ] , indTarget_plugs , f=1) 487 | except: 488 | print( "Failed to disconnectAttr {} ---- {} ".format( Target_input_Connections[ indTarget ] , indTarget_plugs)) 489 | pass 490 | #=====================Addend driven attr End=================== 491 | 492 | Target_Index += 1 493 | cmds.select(cl = 1) 494 | #=====================Add Target to New BS End =================== 495 | 496 | #===================== clean up =================== 497 | print ("cleanup ===================================================") 498 | 499 | cmds.delete(Duped_Target_Geo_list) 500 | 501 | 502 | # From PSD_Point_Snap_GUI_2 503 | # removed AxisLimit input 504 | def SnapToPointBatch_Fullgeo( inputgeoA, inputgeoB, WorldSpace, threshold, VP_Refresh ): 505 | ptnum = cmds.polyEvaluate(inputgeoA)['vertex'] 506 | cmds.cycleCheck(e = False) 507 | point_counter = 0 508 | if WorldSpace: 509 | for i in range(ptnum): 510 | Pointid = i 511 | 512 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 513 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 514 | movedistant = [] 515 | movedistant.append(VPosB[0] - VPosA[0]) 516 | movedistant.append(VPosB[1] - VPosA[1]) 517 | movedistant.append(VPosB[2] - VPosA[2]) 518 | 519 | if DistanceMethod_Full(movedistant, threshold): 520 | continue 521 | point_counter += 1 522 | 523 | cmds.move(VPosB[0], VPosB[1], VPosB[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), worldSpace = 1, a = 1) 524 | if VP_Refresh: 525 | cmds.refresh() 526 | else: 527 | 528 | for i in range(ptnum): 529 | Pointid = i 530 | 531 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 532 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 533 | movedistant = [] 534 | movedistant.append(VPosB[0] - VPosA[0]) 535 | movedistant.append(VPosB[1] - VPosA[1]) 536 | movedistant.append(VPosB[2] - VPosA[2]) 537 | 538 | if DistanceMethod_Full(movedistant, threshold): 539 | continue 540 | point_counter += 1 541 | 542 | cmds.move(movedistant[0], movedistant[1], movedistant[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), 543 | objectSpace = 1, r = 1) 544 | if VP_Refresh: 545 | cmds.refresh() 546 | 547 | cmds.cycleCheck(e = True) 548 | print(str(point_counter) + " point Snap to the Target position") 549 | 550 | 551 | def SnapToPointBatch_Selected_Points_only( inputgeoA, inputgeoB, PointList, WorldSpace, threshold, VP_Refresh ): 552 | cmds.cycleCheck(e = False) 553 | point_counter = 0 554 | if WorldSpace: 555 | for i in PointList: 556 | Pointid = int(i[:-1].split("[")[1]) 557 | 558 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 559 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 560 | movedistant = [] 561 | movedistant.append(VPosB[0] - VPosA[0]) 562 | movedistant.append(VPosB[1] - VPosA[1]) 563 | movedistant.append(VPosB[2] - VPosA[2]) 564 | 565 | if DistanceMethod_Full(movedistant, threshold): 566 | continue 567 | point_counter += 1 568 | 569 | cmds.move(VPosB[0], VPosB[1], VPosB[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), worldSpace = 1, a = 1) 570 | if VP_Refresh: 571 | cmds.refresh() 572 | else: 573 | 574 | for i in PointList: 575 | Pointid = int(i[:-1].split("[")[1]) 576 | 577 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 578 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 579 | movedistant = [] 580 | movedistant.append(VPosB[0] - VPosA[0]) 581 | movedistant.append(VPosB[1] - VPosA[1]) 582 | movedistant.append(VPosB[2] - VPosA[2]) 583 | 584 | if DistanceMethod_Full(movedistant, threshold): 585 | continue 586 | point_counter += 1 587 | 588 | cmds.move(movedistant[0], movedistant[1], movedistant[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), 589 | objectSpace = 1, r = 1) 590 | if VP_Refresh: 591 | cmds.refresh() 592 | 593 | cmds.cycleCheck(e = True) 594 | print(str(point_counter) + " point Snap to the Target position") 595 | 596 | 597 | def DistanceMethod_Simplify( movedistant, threshold ): 598 | if abs(movedistant[0]) < threshold: 599 | if abs(movedistant[1]) < threshold: 600 | if abs(movedistant[2]) < threshold: 601 | return False 602 | return True 603 | 604 | 605 | def DistanceMethod_Full( movedistant, threshold ): 606 | dx = movedistant[0] 607 | dy = movedistant[1] 608 | dz = movedistant[2] 609 | Distance = dx * dx + dy * dy + dz * dz 610 | if Distance > (threshold * threshold * threshold): 611 | return False 612 | else: 613 | return True 614 | 615 | # global Question_Window_image_path 616 | # Question_Window_image_path = os.path.join(os.path.dirname(__file__), 'Question_BlendShape_Transfer') 617 | BlendShape_Transfer_GUI() 618 | -------------------------------------------------------------------------------- /BlendShape_Transfer/BlendShape_Transfer_CHS.py: -------------------------------------------------------------------------------- 1 | # -!- coding: utf-8 -!- 2 | # Author : AWACS 3 | # Time : 2022/9/10 4 | # version : 0.56 beta 5 | 6 | import maya.cmds as cmds 7 | import os 8 | 9 | # def Question_Button( COMMAND = None , blank_space = 1 , btn_label = " ? " ): 10 | # cmds.text( l= " " ,w = blank_space ) 11 | # cmds.button( c=lambda *args: eval( COMMAND ) , l= btn_label , w=20 ) 12 | 13 | # def Question_Window( imageNum = 0 , Window_name = 'Question_Window' , w = 540 , h = 540): 14 | # if cmds.window( Window_name , q=1, ex=1 ): 15 | # cmds.deleteUI( Window_name ) 16 | # cmds.window( Window_name ) 17 | # #cmds.dockControl( area='left', content=myWindow, allowedArea=allowedAreas ) 18 | # imagelist = ["Question_BlendShape_Transfer_1.jpg" , 19 | # "Question_BlendShape_Transfer_2.jpg" , 20 | # "Question_BlendShape_Transfer_3.jpg" , 21 | # "Question_BlendShape_Transfer_4.jpg" , 22 | # "Question_BlendShape_Transfer_5.jpg" , 23 | # ] 24 | # print ( Question_Window_image_path + "/" + imagelist[ imageNum ] ) 25 | # cmds.paneLayout() 26 | # cmds.image( image= Question_Window_image_path + "/" + imagelist[ imageNum ] ) 27 | 28 | # cmds.showWindow( Window_name ) 29 | 30 | def BlendShape_Transfer_GUI(): 31 | if cmds.window('BlendShape_Transfer', q = 1, ex = 1): 32 | cmds.deleteUI('BlendShape_Transfer') 33 | 34 | cmds.window('BlendShape_Transfer') 35 | cmds.showWindow('BlendShape_Transfer') 36 | cmds.columnLayout() 37 | 38 | textwidth = 300 39 | 40 | cmds.rowLayout(nc = 6) 41 | cmds.text(label = " 1: ") 42 | cmds.text(label = u"传递方式 : ", w = textwidth, align = "right") 43 | cmds.optionMenu( "Transfer_Method" , w = 180 ) 44 | cmds.menuItem( label='Wrap deformer' ) 45 | cmds.menuItem( label=u'目前别的还没写出来咕咕咕' ) 46 | # Question_Button("Question_Window( 0 )", 10) 47 | cmds.setParent('..') 48 | 49 | cmds.rowLayout(nc = 6) 50 | cmds.text(label = " 2: ") 51 | cmds.text(label = u"选择原模型 ( 带有原先BS的模型 ) : ", w = textwidth, align = "right") 52 | cmds.textField('Input_Source_Mesh', w = 140, h = 24, text = "") 53 | cmds.button(c = lambda *args: Set_Source_Geo(), label = u"确认", w = 50) 54 | # Question_Button("Question_Window( 0 )", 10) 55 | cmds.setParent('..') 56 | 57 | cmds.rowLayout(nc = 6) 58 | cmds.text(label = " 3: ") 59 | cmds.text(label = u"选择新模型 ( 你要传递到的BS的模型 ) : ", w = textwidth, align = "right") 60 | cmds.textField('Input_Target_Mesh', w = 140, h = 24, text = "") 61 | cmds.button(c = lambda *args: Set_Target_Geo(), label = u"确认", w = 50) 62 | cmds.setParent('..') 63 | 64 | cmds.rowLayout(nc = 6) 65 | cmds.text(label = " 4: ") 66 | cmds.text(label = u"选定BS节点 (如未选定,会尝试自动查找模型已有BS节点): ", w = textwidth, align = "right") 67 | cmds.textField('InputBS', w = 140, h = 24, text = "") 68 | cmds.button(c = lambda *args: Set_Source_BS(), label = u"确认", w = 50) 69 | cmds.setParent('..') 70 | 71 | cmds.rowLayout(nc = 6) 72 | cmds.text(label = " 5: ") 73 | cmds.text(label = u" 新的BS的名称 : ", w = textwidth, align = "right") 74 | cmds.textField('New_BS_Name', w = 180, h = 24, text = "BS_Transfered") 75 | cmds.setParent('..') 76 | 77 | cmds.rowLayout(nc = 6) 78 | cmds.text(label = " 6: ") 79 | # cmds.text(label = "Add to Existing Blendshape Node : ", w = 140) 80 | 81 | cmds.checkBox( "Add_to_exist_BS" , l = u"添加到现有BS节点: ", value = False, w = textwidth, align = "right") 82 | # cmds.text(label = " Existing_BS : " ,w = 80) 83 | cmds.textField('Existing_BS', w = 140, h = 24, text = "") 84 | cmds.button(c = lambda *args: Set_Existing_BS(), label = u"确认", w = 50) 85 | cmds.setParent('..') 86 | 87 | # cmds.rowLayout(nc = 6) 88 | # cmds.text(label = " 6: ") 89 | # cmds.text(label = "Distant threshold : ") 90 | # cmds.floatField("Distant_threshold", max = 0.1, min = 0.0, value = 0.002) 91 | # cmds.setParent('..') 92 | cmds.rowLayout(nc = 6) 93 | cmds.text(label = " 7: ") 94 | # cmds.text(label = "Add to Existing Blendshape Node : ", w = 140) 95 | 96 | cmds.checkBox( "Append_Driven" , l =u"自动链接驱动属性", value = False, w = textwidth, align = "right") 97 | # cmds.text(label = " Existing_BS : " ,w = 80) 98 | cmds.setParent('..') 99 | 100 | 101 | cmds.rowLayout(nc = 6) 102 | cmds.text(label = " 8: ") 103 | cmds.button(c = lambda *args: ExecuteTransferBS(), label = u"执行 BlendShape Transfer ", w = 180) 104 | cmds.setParent('..') 105 | 106 | 107 | cmds.rowLayout(nc = 6) 108 | cmds.text(label = u" 注意 : 模型的Scale(尺寸) X,Y,Z 最好冻结到 1.0 ") 109 | cmds.setParent('..') 110 | 111 | def Object_Type_Checker( InputObject, Object_Type ): 112 | """ 113 | Check node type , check for shape if it is transform 114 | """ 115 | if cmds.nodeType(InputObject) == Object_Type: 116 | return True 117 | else: 118 | if cmds.nodeType(InputObject) == "transform": 119 | RelativeNodes = cmds.listRelatives(InputObject) 120 | if RelativeNodes: 121 | for indRelative in RelativeNodes: 122 | if cmds.nodeType(indRelative) == Object_Type: 123 | return True 124 | return False 125 | 126 | def Set_Source_Geo(): 127 | Selection = cmds.ls(selection = True) 128 | if Selection: 129 | if Object_Type_Checker(Selection[0], "mesh"): 130 | cmds.textField('Input_Source_Mesh', e = 1, tx = Selection[0]) 131 | print (u"//// 原模型已选 : " + Selection[0]) 132 | else: 133 | print (u"//// 错误的选择 , 请选择个多边形网格 ") 134 | else: 135 | print(u"//// 选择是空的") 136 | 137 | def Set_Target_Geo(): 138 | Selection = cmds.ls(selection = True) 139 | if Selection: 140 | if Object_Type_Checker(Selection[0], "mesh"): 141 | cmds.textField('Input_Target_Mesh', e = 1, tx = Selection[0]) 142 | print (u"//// 目标模型已选 : " + Selection[0]) 143 | else: 144 | print (u"//// 错误的选择 , 请选择个多边形网格 ") 145 | else: 146 | print(u"//// 选择是空的") 147 | 148 | def Set_Source_BS(): 149 | Selection = cmds.ls(selection = True) 150 | if Selection: 151 | Source_BS_list = [] 152 | for indSel in Selection: 153 | if Object_Type_Checker(indSel, "blendShape"): 154 | Source_BS_list.append(indSel) 155 | print (Source_BS_list) 156 | if len(Source_BS_list) < 1: 157 | print (u"//// 错误的选择 , 请选择至少一个blendShape节点 ") 158 | else: 159 | BS_Display = "" 160 | for indBS in Source_BS_list: 161 | BS_Display += indBS 162 | BS_Display += " " 163 | cmds.textField('InputBS', e = 1, tx = BS_Display) 164 | print (u"//// blendShape节点已选 : " +BS_Display) 165 | else: 166 | cmds.textField('InputBS', e = 1, tx = None) 167 | print ("//// 原BlendShape未选定,执行时我会试图找到链接了的现有Blendshape ") 168 | 169 | def Set_Existing_BS( ): 170 | Selection = cmds.ls(selection = True) 171 | if Selection: 172 | if Object_Type_Checker(Selection[0], "blendShape"): 173 | cmds.textField('Existing_BS', e = 1, tx = Selection[0]) 174 | print (u"//// blendShape节点已选 : " + Selection[0]) 175 | else: 176 | print (u"//// 错误的选择 , 请选择现有的目标blendShape节点 ") 177 | else: 178 | print(u"//// 选择是空的") 179 | 180 | def SearchingForBlendshape( InputNode ): 181 | """ 182 | try to find Blendshape node from a mesh 183 | """ 184 | RelativeNodes = cmds.listRelatives(InputNode ,shapes = True) 185 | SearchResult = [] 186 | # Result_counter = 0 187 | if RelativeNodes: 188 | for indRel in RelativeNodes: 189 | # print (indRel) 190 | indRel_Rel_Nodes = cmds.listConnections(indRel) 191 | if indRel_Rel_Nodes: 192 | print (indRel_Rel_Nodes) 193 | for indRel_Rel_Rel in indRel_Rel_Nodes: 194 | if Object_Type_Checker(indRel_Rel_Rel, "blendShape"): 195 | SearchResult.append(indRel_Rel_Rel) 196 | # Result_counter += 1 197 | SearchResult = list(set(SearchResult)) 198 | print( InputNode + u" blendShape节点查找结果 : " + str(SearchResult) ) 199 | # if Result_counter > 0 : 200 | print (SearchResult) 201 | if SearchResult == []: 202 | return None 203 | return SearchResult 204 | # else: 205 | # return None 206 | else: 207 | return None 208 | 209 | 210 | def ExecuteTransferBS(): 211 | Source_Geo = cmds.textField('Input_Source_Mesh', query = 1, text = 1) 212 | Target_Geo = cmds.textField('Input_Target_Mesh', query = 1, text = 1) 213 | Source_BS_Text = cmds.textField('InputBS', query = 1, text = 1) 214 | NewBSName = cmds.textField('New_BS_Name', query = 1, text = 1) 215 | 216 | Append_Driven = cmds.checkBox( "Append_Driven" , query = 1, value = 1) 217 | Add_to_Exist_BS = cmds.checkBox( "Add_to_exist_BS" , query = 1, value = 1) 218 | Existing_BS = cmds.textField( "Existing_BS" , query = 1, text = 1) 219 | 220 | if Add_to_Exist_BS: 221 | if len(Existing_BS) < 1 : 222 | Existing_BS = SearchingForBlendshape(Target_Geo) 223 | 224 | if Existing_BS is None : 225 | Add_to_Exist_BS = False 226 | 227 | if len(Source_BS_Text) > 0: 228 | Source_BS = Source_BS_Text.split(" ") 229 | else: 230 | Source_BS = SearchingForBlendshape(Source_Geo) 231 | 232 | print(Source_BS) 233 | 234 | if Source_BS: 235 | for IndSrcBS in Source_BS: 236 | if IndSrcBS == "" : 237 | continue 238 | if len(IndSrcBS) > 0 : 239 | print((Source_Geo, IndSrcBS, Target_Geo, Add_to_Exist_BS , Existing_BS , False, Append_Driven ,NewBSName)) 240 | BlendShape_Transfer_Main(Source_Geo, IndSrcBS, Target_Geo, Add_to_Exist_BS , Existing_BS , False, Append_Driven ,NewBSName) 241 | else: 242 | print( u"没有找的blendShape节点 , 请手动选择blendShape节点并确认" ) 243 | 244 | def Create_Wrap( DriveR_Mesh, DriveN_Mesh, name, threshold = 0, maxDistance = 1.0, exclusiveBind = True, 245 | autoWeightThreshold = True, 246 | falloffMode = 0 ): 247 | """ 248 | Create Wrap and setting up parameter 249 | """ 250 | cmds.select(cl = 1) 251 | cmds.select(DriveN_Mesh) 252 | cmds.select(DriveR_Mesh, add = 1) 253 | tempString = cmds.deformer(type = 'wrap', n = name) 254 | wrapNode = tempString[0] 255 | cmds.setAttr(wrapNode + ".weightThreshold", threshold) 256 | cmds.setAttr(wrapNode + ".maxDistance", maxDistance) 257 | cmds.setAttr(wrapNode + ".exclusiveBind", exclusiveBind) 258 | cmds.setAttr(wrapNode + ".autoWeightThreshold", autoWeightThreshold) 259 | cmds.setAttr(wrapNode + ".falloffMode", falloffMode) 260 | 261 | cmds.AddWrapInfluence() 262 | cmds.select(cl = 1) 263 | return wrapNode 264 | 265 | def Create_BS( SourceGeo, BS_Name ): 266 | cmds.select(cl = 1) 267 | cmds.select(SourceGeo) 268 | BS = cmds.blendShape(automatic = 1) 269 | renameResult = cmds.rename(BS[0], BS_Name) 270 | return renameResult 271 | 272 | 273 | def Initialize_BS( BSNode ): 274 | """ 275 | Zero out Blendshape attributes 276 | """ 277 | BS_Node_target_list = cmds.listAttr(BSNode + ".w", k = True, m = True) 278 | cmds.setAttr(BSNode + ".envelope", 1) 279 | for indBS_Node_target in BS_Node_target_list: 280 | try: 281 | cmds.setAttr(BSNode + '.' + indBS_Node_target, 0) 282 | except: 283 | print( "Initialize BS Func Failed , Failed to set 0 at ------" + BSNode + '.' + indBS_Node_target) 284 | 285 | 286 | def polySmooth_Custom( InputObject, Level = 1 ): 287 | cmds.polySmooth(InputObject, 288 | constructionHistory = 1, 289 | osdSmoothTriangles = 0, 290 | keepHardEdge = 0, 291 | pushStrength = 0.1, 292 | keepMapBorders = 1, 293 | boundaryRule = 1, 294 | method = 0, 295 | smoothUVs = 1, 296 | propagateEdgeHardness = 0, 297 | keepSelectionBorder = 1, 298 | roundness = 1, 299 | subdivisionType = 2, 300 | osdFvarPropagateCorners = 0, 301 | keepTessellation = 1, 302 | osdVertBoundary = 1, 303 | divisions = 1, 304 | osdFvarBoundary = 3, 305 | keepBorder = 1, 306 | continuity = 1, 307 | osdCreaseMethod = 0, 308 | divisionsPerEdge = 1, 309 | subdivisionLevels = Level) 310 | 311 | 312 | def Point_distance_Checker ( inputgeoA , inputgeoB , threshold , faster = True ): 313 | """ 314 | check if mismatch pointpos 315 | mismatch return True 316 | match return False 317 | """ 318 | ptnum = cmds.polyEvaluate( inputgeoA )['vertex'] 319 | cmds.cycleCheck(e=False) 320 | 321 | if faster: 322 | for i in range( ptnum ): 323 | VPosA = cmds.xform( inputgeoA + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 324 | VPosB = cmds.xform( inputgeoB + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 325 | 326 | if abs( VPosB[0] - VPosA[0] ) > threshold: 327 | cmds.cycleCheck(e=True) 328 | return True 329 | if abs( VPosB[1] - VPosA[1] ) > threshold: 330 | cmds.cycleCheck(e=True) 331 | return True 332 | if abs( VPosB[2] - VPosA[2]) > threshold: 333 | cmds.cycleCheck(e=True) 334 | return True 335 | cmds.cycleCheck(e=True) 336 | return False 337 | else: 338 | for i in range( ptnum ): 339 | VPosA = cmds.xform( inputgeoA + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 340 | VPosB = cmds.xform( inputgeoB + '.vtx[' + str(i) + ']', query = 1, worldSpace = 1, translation = 1) 341 | 342 | dist3 = ( ( VPosB[0] - VPosA[0] ) ** 2 + ( VPosB[1] - VPosA[1] ) ** 2 + ( VPosB[2] - VPosA[2] ) ** 2 ) 343 | 344 | if dist3 > threshold**2 : 345 | cmds.cycleCheck(e=True) 346 | return True 347 | 348 | cmds.cycleCheck(e=True) 349 | return False 350 | 351 | def BlendShape_Transfer_Main( Source_Geo, BS_Node, Target_Geo, Add_to_Existing_BS, Existing_BS, 352 | Subdivided_Transfer, Append_Driven , NewBSName = "BS_Transfered" ): 353 | # LowPolySrc , BS_of_LowPolySrc ):, HighPolyGeo , HighPoly_is_Extended , Transfer_Vertex , Transfer_Method , New_name_list ): 354 | # transfer the low Poly Mesh Blendshape to a Extended High Poly Mesh 355 | #=====================initialize=================== 356 | BS_Node_target_list = cmds.listAttr(BS_Node + ".w", k = True, m = True) 357 | print (BS_Node_target_list) 358 | #=====================initialize=================== 359 | 360 | 361 | # ----- recording input Connection of Source BS 362 | Target_input_Connections = {} 363 | for indTarget in BS_Node_target_list: 364 | indTarget_plugs = BS_Node + '.' + indTarget 365 | try: 366 | indTarget_Driven_input = cmds.listConnections( indTarget_plugs , plugs = True )[0] 367 | Target_input_Connections[indTarget] = indTarget_Driven_input 368 | except: 369 | Target_input_Connections[indTarget] = False 370 | 371 | # ----- disconnectAttr Source BS 372 | for indTarget , indInput_connection in Target_input_Connections.items(): 373 | indTarget_plugs = BS_Node + '.' + indTarget 374 | if indInput_connection : 375 | try: 376 | cmds.disconnectAttr( indInput_connection , indTarget_plugs) 377 | except: 378 | print( u"未成功断开属性 : {} ---- {} ".format( indTarget_plugs , indInput_connection)) 379 | 380 | Initialize_BS(BS_Node) 381 | 382 | #=====================initialize End=================== 383 | 384 | #=====================Wraping Start=================== 385 | #1first Duplicate a Target Geo 386 | Target_Geo_Transfer_Ready = cmds.duplicate(Target_Geo, n = Target_Geo + "_Temp_Transfer_Ready")[0] 387 | 388 | # ----------- duplicate entire Source geometry with upstreamNodes is safer ? 389 | # Duplicated_Wrap_Src = cmds.duplicate(Source_Geo, upstreamNodes = True , renameChildren = True ) 390 | # Duplicated_Wrap_Src = [ Source_Geo ] 391 | 392 | 393 | Wrap_Src_Geo = Source_Geo 394 | Wrap_Src_BS = BS_Node 395 | 396 | WarpNode = Create_Wrap(Wrap_Src_Geo, Target_Geo_Transfer_Ready, "WarpNode_Temp_For_BS_Transfer", 0, 1.0, False, False, 0) 397 | 398 | 399 | # try update????????????? 400 | # there may be some issue about maya dont update deformer before first time executing duplicate command 401 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 402 | Temp_duplicateFirst = cmds.duplicate( Target_Geo_Transfer_Ready, n = Target_Geo_Transfer_Ready + "_Temp_" + "duplicateFirst_" + indTarget) 403 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 0) 404 | cmds.delete(Temp_duplicateFirst) 405 | # try update Finished ?????????????-------------- 406 | 407 | 408 | # --------- get individual wrap result 409 | Duped_Target_Geo_list = [] 410 | for indTarget in BS_Node_target_list: 411 | 412 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 413 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 1) 414 | # cmds.setAttr(WarpNode + ".envelope", 0) 415 | # cmds.setAttr(WarpNode + ".envelope", 1) 416 | # cmds.refresh( f = 1 ) 417 | # cmds.refresh( f = 1 ) 418 | ind_tmp_BS_Target = cmds.duplicate( Target_Geo_Transfer_Ready, n = Target_Geo_Transfer_Ready + "_Temp_" + indTarget) 419 | 420 | 421 | Duped_Target_Geo_list.append(ind_tmp_BS_Target[0]) 422 | cmds.setAttr(Wrap_Src_BS + '.' + indTarget, 0) 423 | 424 | # ----- reconnectAttr Source BS 425 | for indTarget , indInput_connection in Target_input_Connections.items(): 426 | indTarget_plugs = Wrap_Src_BS + '.' + indTarget 427 | if indInput_connection : 428 | try: 429 | cmds.connectAttr( indInput_connection , indTarget_plugs , f=1) 430 | except: 431 | print( u"未成功链接属性 : {} ---- {} ".format( indTarget_plugs , indInput_connection)) 432 | pass 433 | 434 | cmds.delete(WarpNode) 435 | cmds.delete(Target_Geo_Transfer_Ready) 436 | #=====================Wraping End=================== 437 | #=====================Target BS setting up Start=================== 438 | Target_Index = 0 439 | 440 | if Add_to_Existing_BS: 441 | FinalBS = Existing_BS[0] 442 | Existing_BS_Node_target_list = cmds.listAttr( FinalBS + ".w", k = True, m = True) 443 | print( Existing_BS_Node_target_list ) 444 | # ----- increaseing after 2 445 | Target_Index += len(Existing_BS_Node_target_list) + 2 446 | else: 447 | if len(NewBSName) < 1: 448 | FinalBS = Create_BS(Target_Geo, Target_Geo + "_BS") 449 | else: 450 | FinalBS = Create_BS(Target_Geo, NewBSName) 451 | 452 | print (u"传递到Blendshape : Blendshape 名称 : {}".format (FinalBS) ) 453 | #=====================Target BS setting up End=================== 454 | 455 | #=====================Add Target to New BS Start=================== 456 | cmds.select(cl = 1) 457 | for indTargetindex in range(len(Duped_Target_Geo_list)): 458 | print(u"传递中 : {} ".format( BS_Node_target_list[indTargetindex] )) 459 | ind_tmp_BS_Target = Duped_Target_Geo_list[indTargetindex] 460 | BypassUnuseful_target = False 461 | 462 | if BypassUnuseful_target: 463 | mismatch = Point_distance_Checker( Target_Geo , ind_tmp_BS_Target , 0.02 ) 464 | if mismatch is False : 465 | print(u" 低于点距离检查阈值 , 跳过") 466 | continue 467 | 468 | Target_Geo_Shape = cmds.listRelatives( Target_Geo , shapes = True )[0] 469 | 470 | cmds.select( ind_tmp_BS_Target ) 471 | cmds.blendShape( FinalBS , e = 1, tc = True, w = (Target_Index, 0), 472 | t = ( Target_Geo , Target_Index, ind_tmp_BS_Target + 'Shape', 1)) 473 | 474 | indTarget = BS_Node_target_list[ indTargetindex ] 475 | indTarget_name = indTarget 476 | try: 477 | cmds.aliasAttr(indTarget_name, FinalBS + '.w[' + str(Target_Index) + ']') 478 | except: 479 | print( u"有命名冲突! : " + indTarget_name ) 480 | indTarget_name = indTarget_name + '_Name_Conflict' 481 | cmds.aliasAttr(indTarget_name , FinalBS + '.w[' + str(Target_Index) + ']') 482 | 483 | #=====================Addend driven attr Start=================== 484 | if Append_Driven: 485 | indTarget_plugs = FinalBS + '.' + indTarget 486 | if Target_input_Connections[ indTarget ] : 487 | try: 488 | cmds.connectAttr( Target_input_Connections[ indTarget ] , indTarget_plugs , f=1) 489 | except: 490 | print( u"未成功链接属性 : ---- {} ".format( Target_input_Connections[ indTarget ] , indTarget_plugs)) 491 | pass 492 | #=====================Addend driven attr End=================== 493 | 494 | Target_Index += 1 495 | cmds.select(cl = 1) 496 | #=====================Add Target to New BS End =================== 497 | 498 | #===================== clean up =================== 499 | print (u"清理节点中 ===================================================") 500 | 501 | cmds.delete(Duped_Target_Geo_list) 502 | 503 | 504 | # From PSD_Point_Snap_GUI_2 505 | # removed AxisLimit input 506 | def SnapToPointBatch_Fullgeo( inputgeoA, inputgeoB, WorldSpace, threshold, VP_Refresh ): 507 | ptnum = cmds.polyEvaluate(inputgeoA)['vertex'] 508 | cmds.cycleCheck(e = False) 509 | point_counter = 0 510 | if WorldSpace: 511 | for i in range(ptnum): 512 | Pointid = i 513 | 514 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 515 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 516 | movedistant = [] 517 | movedistant.append(VPosB[0] - VPosA[0]) 518 | movedistant.append(VPosB[1] - VPosA[1]) 519 | movedistant.append(VPosB[2] - VPosA[2]) 520 | 521 | if DistanceMethod_Full(movedistant, threshold): 522 | continue 523 | point_counter += 1 524 | 525 | cmds.move(VPosB[0], VPosB[1], VPosB[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), worldSpace = 1, a = 1) 526 | if VP_Refresh: 527 | cmds.refresh() 528 | else: 529 | 530 | for i in range(ptnum): 531 | Pointid = i 532 | 533 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 534 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 535 | movedistant = [] 536 | movedistant.append(VPosB[0] - VPosA[0]) 537 | movedistant.append(VPosB[1] - VPosA[1]) 538 | movedistant.append(VPosB[2] - VPosA[2]) 539 | 540 | if DistanceMethod_Full(movedistant, threshold): 541 | continue 542 | point_counter += 1 543 | 544 | cmds.move(movedistant[0], movedistant[1], movedistant[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), 545 | objectSpace = 1, r = 1) 546 | if VP_Refresh: 547 | cmds.refresh() 548 | 549 | cmds.cycleCheck(e = True) 550 | print(str(point_counter) + " point Snap to the Target position") 551 | 552 | 553 | def SnapToPointBatch_Selected_Points_only( inputgeoA, inputgeoB, PointList, WorldSpace, threshold, VP_Refresh ): 554 | cmds.cycleCheck(e = False) 555 | point_counter = 0 556 | if WorldSpace: 557 | for i in PointList: 558 | Pointid = int(i[:-1].split("[")[1]) 559 | 560 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 561 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, worldSpace = 1, translation = 1) 562 | movedistant = [] 563 | movedistant.append(VPosB[0] - VPosA[0]) 564 | movedistant.append(VPosB[1] - VPosA[1]) 565 | movedistant.append(VPosB[2] - VPosA[2]) 566 | 567 | if DistanceMethod_Full(movedistant, threshold): 568 | continue 569 | point_counter += 1 570 | 571 | cmds.move(VPosB[0], VPosB[1], VPosB[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), worldSpace = 1, a = 1) 572 | if VP_Refresh: 573 | cmds.refresh() 574 | else: 575 | 576 | for i in PointList: 577 | Pointid = int(i[:-1].split("[")[1]) 578 | 579 | VPosA = cmds.xform(inputgeoA + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 580 | VPosB = cmds.xform(inputgeoB + '.vtx[' + str(Pointid) + ']', query = 1, objectSpace = 1, translation = 1) 581 | movedistant = [] 582 | movedistant.append(VPosB[0] - VPosA[0]) 583 | movedistant.append(VPosB[1] - VPosA[1]) 584 | movedistant.append(VPosB[2] - VPosA[2]) 585 | 586 | if DistanceMethod_Full(movedistant, threshold): 587 | continue 588 | point_counter += 1 589 | 590 | cmds.move(movedistant[0], movedistant[1], movedistant[2], (inputgeoA + '.vtx[' + str(Pointid) + ']'), 591 | objectSpace = 1, r = 1) 592 | if VP_Refresh: 593 | cmds.refresh() 594 | 595 | cmds.cycleCheck(e = True) 596 | print(str(point_counter) + " point Snap to the Target position") 597 | 598 | 599 | def DistanceMethod_Simplify( movedistant, threshold ): 600 | if abs(movedistant[0]) < threshold: 601 | if abs(movedistant[1]) < threshold: 602 | if abs(movedistant[2]) < threshold: 603 | return False 604 | return True 605 | 606 | 607 | def DistanceMethod_Full( movedistant, threshold ): 608 | dx = movedistant[0] 609 | dy = movedistant[1] 610 | dz = movedistant[2] 611 | Distance = dx * dx + dy * dy + dz * dz 612 | if Distance > (threshold * threshold * threshold): 613 | return False 614 | else: 615 | return True 616 | 617 | # global Question_Window_image_path 618 | # Question_Window_image_path = os.path.join(os.path.dirname(__file__), 'Question_BlendShape_Transfer') 619 | BlendShape_Transfer_GUI() 620 | -------------------------------------------------------------------------------- /BlendShape_Transfer_demonstration.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AWACC2020/Blendshape-Transfer-for-Maya/616a6a90a41e81af6ff36f33f79de180c791ef55/BlendShape_Transfer_demonstration.mp4 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 AWACC2020 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blendshape-Transfer-for-Maya 2 | Transfer Blendshape between meshes with different topology 3 | 4 | ## 安装/Install: 5 | - 请先在工具架上切换到你要添加此工具的tab 6 | - 拖曳install BlendShape Transfer.py这个文件到maya窗口,如中文需要的话请选择拖曳install BlendShape Transfer(CHS).py 7 | - 其实也不需要安装直接在脚本编辑器里运行也行,这又不是什么复杂并且对路径有依赖的脚本 8 | - Please switch to the tab that you want to add this tool in the tool shelf 9 | - Simply drag and drop this file : "install BlendShape Transfer.py" to the maya window 10 | - Don't have to install actually. it can running directly in the script editor. This script isn't complicated and do not depends on the working directory 11 | 12 | ## 卸载/Uninstall: 13 | - 请先在工具架上切换到你要添加此工具的tab 14 | - 拖曳Uninstall BlendShape Transfer.py这个文件到maya窗口 15 | - 工具架上的还是要手动删除 16 | - Please switch to the tab that you want to add this tool in the tool shelf 17 | - Simply drag and drop this file : "Uninstall BlendShape Transfer.py" to the maya window 18 | - Delete the icon on the shelf manually 19 | -------------------------------------------------------------------------------- /Uninstall BlendShape Transfer.py: -------------------------------------------------------------------------------- 1 | # Author : AWACS 2 | # Time : 2020/09/03 3 | 4 | import os 5 | import shutil 6 | 7 | try: 8 | import maya.mel 9 | import maya.cmds as cmds 10 | isMaya = True 11 | except ImportError: 12 | isMaya = False 13 | 14 | def onMayaDroppedPythonFile(*args, **kwargs): 15 | pass 16 | 17 | def MayaDropRemove(): 18 | """Drag and drop this file into the scene executes the file.""" 19 | dic_script = { 20 | "author_folder" : "AWACS", 21 | "Contain_Folder" : "BlendShape_Transfer", 22 | "Script_folder" : "BlendShape_Transfer", 23 | "Script_module_import" : "BlendShape_Transfer", 24 | 25 | "Script_name" : "BlendShape_Transfer", 26 | "Script_Shelf_icon" : None , 27 | "imageOverlayLabel":"BsTransfer", 28 | "Script_annotation" : 29 | "Transfer Blendshape between meshes with different topology", 30 | } 31 | Popup_Dialog = True 32 | 33 | User_script_dir = cmds.internalVar(userScriptDir=1) 34 | author_folder_dir = os.path.join( User_script_dir , dic_script["author_folder"]) 35 | 36 | Script_folder_fullpath = os.path.join( author_folder_dir , dic_script["Script_folder"]) 37 | 38 | if os.path.isdir( Script_folder_fullpath ): 39 | shutil.rmtree( Script_folder_fullpath ) 40 | confirmDialog_message = "{Script_name} has been Removed From your software, \ 41 | you can remove the command from shelf now".format( Script_name = dic_script["Script_name"]) 42 | print (confirmDialog_message) 43 | if Popup_Dialog: 44 | cmds.confirmDialog( title='Uninstall Success', message=confirmDialog_message, button=['OK'] ) 45 | else : 46 | confirmDialog_message = "No such path , Nothing good to remove or unistall or whatever" 47 | print (confirmDialog_message) 48 | if Popup_Dialog: 49 | 50 | cmds.confirmDialog( title='Uninstall Failed', message=confirmDialog_message, button=['OK'] ) 51 | return 52 | 53 | # check if there is any thing ,if not remove entire author_folder 54 | # print (os.listdir ( author_folder_dir ) ) 55 | if len( os.listdir ( author_folder_dir ) ) < 1: 56 | shutil.rmtree( author_folder_dir ) 57 | 58 | 59 | 60 | 61 | if isMaya: 62 | MayaDropRemove() 63 | -------------------------------------------------------------------------------- /install BlendShape Transfer CHS.py: -------------------------------------------------------------------------------- 1 | 2 | # Author : AWACS 3 | # Time : 2020/08/15 4 | 5 | import os 6 | import shutil 7 | 8 | try: 9 | import maya.mel 10 | import maya.cmds as cmds 11 | isMaya = True 12 | except ImportError: 13 | isMaya = False 14 | 15 | def onMayaDroppedPythonFile(*args, **kwargs): 16 | pass 17 | 18 | def MayaDropinstall(): 19 | """Drag and drop this file into the scene executes the file.""" 20 | 21 | dic_script = { 22 | "author_folder" : "AWACS", 23 | "Contain_Folder" : "BlendShape_Transfer", 24 | "Script_folder" : "BlendShape_Transfer", 25 | "Script_module_import" : "BlendShape_Transfer_CHS", 26 | 27 | "Script_name" : "BlendShape_Transfer", 28 | "Script_Shelf_icon" : None , 29 | "imageOverlayLabel":"BsTransfer", 30 | "Script_annotation" : 31 | "Transfer Blendshape between meshes with different topology", 32 | } 33 | 34 | 35 | ContainPath = os.path.join(os.path.dirname(__file__), dic_script["Contain_Folder"]) 36 | 37 | if not os.path.exists(ContainPath): 38 | raise IOError('Cannot find ' + ContainPath) 39 | 40 | User_script_dir = cmds.internalVar(userScriptDir=1) 41 | author_folder_dir = os.path.join( User_script_dir , dic_script['author_folder']) 42 | 43 | try: 44 | os.makedirs( author_folder_dir ) 45 | except: 46 | # author_folder_dir Already exist 47 | pass 48 | 49 | # Script_folder_fullpath = os.path.join( author_folder_dir , dic_script["Script_folder"] ) 50 | Script_folder_fullpath = author_folder_dir +"/"+ dic_script["Script_folder"] 51 | # print (Script_folder_fullpath) 52 | # Script_folder_fullpath = os.path.normpath(Script_folder_fullpath) 53 | # print (Script_folder_fullpath) 54 | # print ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") 55 | # Script_folder_fullpath = os.path.normpath(Script_folder_fullpath) 56 | # print (Script_folder_fullpath) 57 | 58 | if os.path.isdir( Script_folder_fullpath ): 59 | print ( str( Script_folder_fullpath ) + " is Already Exist " + " -------------reinstalling") 60 | shutil.rmtree( Script_folder_fullpath ) 61 | else : 62 | pass 63 | #Create Script folder and copy Contains from contain Folder 64 | shutil.copytree( ContainPath , Script_folder_fullpath ) 65 | 66 | print ('// install success') 67 | if dic_script["Script_Shelf_icon"]: 68 | iconPath = os.path.join(Script_folder_fullpath, dic_script["Script_Shelf_icon"]) 69 | iconPath = os.path.normpath(iconPath) 70 | # module_name = "BlendShape_Transfer" 71 | else: 72 | iconPath = "commandButton.png" 73 | 74 | command = '''import sys 75 | script_path = "{path}" 76 | if script_path not in sys.path: 77 | sys.path.append(script_path) 78 | import {module_name} 79 | if sys.version_info.major > 2 : 80 | from imp import reload 81 | reload ({module_name}) 82 | '''.format(path=Script_folder_fullpath , module_name = dic_script["Script_module_import"] ) 83 | 84 | shelf = maya.mel.eval('$gShelfTopLevel=$gShelfTopLevel') 85 | parent = cmds.tabLayout(shelf, query=True, selectTab=True) 86 | cmds.shelfButton( 87 | command=command, 88 | annotation=dic_script["Script_annotation"] , 89 | sourceType='Python', 90 | image=iconPath, 91 | image1=iconPath, 92 | imageOverlayLabel = dic_script["imageOverlayLabel"], 93 | parent=parent 94 | ) 95 | 96 | print("// {Script_name} has been added to current shelf".format( Script_name = dic_script["Script_name"])) 97 | 98 | 99 | if isMaya: 100 | MayaDropinstall() 101 | -------------------------------------------------------------------------------- /install BlendShape Transfer.py: -------------------------------------------------------------------------------- 1 | # Author : AWACS 2 | # Time : 2020/08/15 3 | 4 | import os 5 | import shutil 6 | 7 | try: 8 | import maya.mel 9 | import maya.cmds as cmds 10 | isMaya = True 11 | except ImportError: 12 | isMaya = False 13 | 14 | def onMayaDroppedPythonFile(*args, **kwargs): 15 | pass 16 | 17 | def MayaDropinstall(): 18 | """Drag and drop this file into the scene executes the file.""" 19 | 20 | dic_script = { 21 | "author_folder" : "AWACS", 22 | "Contain_Folder" : "BlendShape_Transfer", 23 | "Script_folder" : "BlendShape_Transfer", 24 | "Script_module_import" : "BlendShape_Transfer", 25 | 26 | "Script_name" : "BlendShape_Transfer", 27 | "Script_Shelf_icon" : None , 28 | "imageOverlayLabel":"BsTransfer", 29 | "Script_annotation" : 30 | "Transfer Blendshape between meshes with different topology", 31 | } 32 | 33 | 34 | ContainPath = os.path.join(os.path.dirname(__file__), dic_script["Contain_Folder"]) 35 | 36 | if not os.path.exists(ContainPath): 37 | raise IOError('Cannot find ' + ContainPath) 38 | 39 | User_script_dir = cmds.internalVar(userScriptDir=1) 40 | author_folder_dir = os.path.join( User_script_dir , dic_script['author_folder']) 41 | 42 | try: 43 | os.makedirs( author_folder_dir ) 44 | except: 45 | # author_folder_dir Already exist 46 | pass 47 | 48 | # Script_folder_fullpath = os.path.join( author_folder_dir , dic_script["Script_folder"] ) 49 | Script_folder_fullpath = author_folder_dir +"/"+ dic_script["Script_folder"] 50 | # print (Script_folder_fullpath) 51 | # Script_folder_fullpath = os.path.normpath(Script_folder_fullpath) 52 | # print (Script_folder_fullpath) 53 | # print ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") 54 | # Script_folder_fullpath = os.path.normpath(Script_folder_fullpath) 55 | # print (Script_folder_fullpath) 56 | 57 | if os.path.isdir( Script_folder_fullpath ): 58 | print ( str( Script_folder_fullpath ) + " is Already Exist " + " -------------reinstalling") 59 | shutil.rmtree( Script_folder_fullpath ) 60 | else : 61 | pass 62 | #Create Script folder and copy Contains from contain Folder 63 | shutil.copytree( ContainPath , Script_folder_fullpath ) 64 | 65 | print ('// install success') 66 | if dic_script["Script_Shelf_icon"]: 67 | iconPath = os.path.join(Script_folder_fullpath, dic_script["Script_Shelf_icon"]) 68 | iconPath = os.path.normpath(iconPath) 69 | # module_name = "BlendShape_Transfer" 70 | else: 71 | iconPath = "commandButton.png" 72 | 73 | command = '''import sys 74 | script_path = "{path}" 75 | if script_path not in sys.path: 76 | sys.path.append(script_path) 77 | import {module_name} 78 | if sys.version_info.major > 2 : 79 | from imp import reload 80 | reload ({module_name}) 81 | '''.format(path=Script_folder_fullpath , module_name = dic_script["Script_module_import"] ) 82 | 83 | shelf = maya.mel.eval('$gShelfTopLevel=$gShelfTopLevel') 84 | parent = cmds.tabLayout(shelf, query=True, selectTab=True) 85 | cmds.shelfButton( 86 | command=command, 87 | annotation=dic_script["Script_annotation"] , 88 | sourceType='Python', 89 | image=iconPath, 90 | image1=iconPath, 91 | imageOverlayLabel = dic_script["imageOverlayLabel"], 92 | parent=parent 93 | ) 94 | 95 | print("// {Script_name} has been added to current shelf".format( Script_name = dic_script["Script_name"])) 96 | 97 | 98 | if isMaya: 99 | MayaDropinstall() 100 | --------------------------------------------------------------------------------