pyjallib 0.1.12__py3-none-any.whl → 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyjallib/__init__.py CHANGED
@@ -6,7 +6,7 @@ pyjallib Package
6
6
  Python library for game character development pipeline.
7
7
  """
8
8
 
9
- __version__ = '0.1.12'
9
+ __version__ = '0.1.14'
10
10
 
11
11
  # reload_modules 함수를 패키지 레벨에서 사용 가능하게 함
12
12
  from pyjallib.namePart import NamePart, NamePartType
pyjallib/max/__init__.py CHANGED
@@ -35,6 +35,11 @@ from pyjallib.max.volumeBone import VolumeBone
35
35
  from pyjallib.max.kneeBone import KneeBone
36
36
  from pyjallib.max.hip import Hip
37
37
 
38
+ from pyjallib.max.rootMotion import RootMotion
39
+
40
+ from pyjallib.max.fbxHandler import FBXHandler
41
+ from pyjallib.max.toolManager import ToolManager
42
+
38
43
  from pyjallib.max.ui.Container import Container
39
44
 
40
45
  # 모듈 내보내기
@@ -55,14 +60,13 @@ __all__ = [
55
60
  'Morph',
56
61
  'BoneChain',
57
62
  'TwistBone',
58
- 'TwistBoneChain',
59
63
  'GroinBone',
60
- 'GroinBoneChain',
61
64
  'AutoClavicle',
62
- 'AutoClavicleChain',
63
65
  'VolumeBone',
64
- 'VolumeBoneChain',
65
66
  'KneeBone',
66
67
  'Hip',
68
+ 'RootMotion',
69
+ 'FBXHandler',
70
+ 'ToolManager',
67
71
  'Container'
68
72
  ]
pyjallib/max/anim.py CHANGED
@@ -477,6 +477,40 @@ class Anim:
477
477
  """
478
478
  rt.deleteKeys(inObj, rt.Name('allKeys'))
479
479
 
480
+ def delete_keys_in_range(self, node, startFrame, endFrame):
481
+ """
482
+ 지정된 프레임 범위에서 노드의 모든 키를 삭제하는 함수
483
+
484
+ Args:
485
+ node: 키를 삭제할 노드
486
+ startFrame (int): 시작 프레임
487
+ endFrame (int): 끝 프레임
488
+
489
+ Returns:
490
+ bool: 성공 여부
491
+ """
492
+ if not rt.isValidNode(node):
493
+ return False
494
+
495
+ try:
496
+ maxscriptCode = f"""
497
+ (
498
+ selectKeys $'{node.name}'.position.controller (interval {startFrame} {endFrame})
499
+ deleteKeys $'{node.name}'.position.controller #selection
500
+
501
+ selectKeys $'{node.name}'.rotation.controller (interval {startFrame} {endFrame})
502
+ deleteKeys $'{node.name}'.rotation.controller #selection
503
+
504
+ selectKeys $'{node.name}'.scale.controller (interval {startFrame} {endFrame})
505
+ deleteKeys $'{node.name}'.scale.controller #selection
506
+ )
507
+ """
508
+ rt.execute(maxscriptCode)
509
+ return True
510
+ except Exception as e:
511
+ print(f"Error deleting keys in range: {e}")
512
+ return False
513
+
480
514
  def is_node_animated(self, node):
481
515
  """
482
516
  객체 및 그 하위 요소(애니메이션, 커스텀 속성 등)가 애니메이션 되었는지 재귀적으로 확인함.
@@ -574,6 +608,56 @@ class Anim:
574
608
 
575
609
  return result
576
610
 
611
+ def save_animation(self, inObjs, inSaveFilePath):
612
+ """
613
+ 객체의 애니메이션을 저장함.
614
+
615
+ 매개변수:
616
+ inObj : 애니메이션을 저장할 객체
617
+ """
618
+
619
+ if not(len(inObjs) > 0):
620
+ return False
621
+
622
+ for obj in inObjs:
623
+ if not(rt.isValidNode(obj)):
624
+ return False
625
+
626
+ animatedNodes = self.find_animated_nodes(inObjs)
627
+ rt.LoadSaveAnimation.setUpAnimsForSave(animatedNodes, animatedTracks=True, includeContraints=True, keyable=True)
628
+ rt.LoadSaveAnimation.saveAnimation(
629
+ inSaveFilePath,
630
+ animatedNodes,
631
+ "tempVal",
632
+ "tempVal",
633
+ animatedTracks=True,
634
+ includeConstraints=True,
635
+ keyableTracks=False,
636
+ SaveSegment=True,
637
+ segInterval=rt.animationRange,
638
+ segKeyPerFrame=True
639
+ )
640
+
641
+ return True
642
+
643
+ def load_animation(self, inObjs, inLoadFilePath):
644
+ """
645
+ 애니메이션을 로드함.
646
+
647
+ 매개변수:
648
+ inObjs : 애니메이션을 로드할 객체
649
+ inLoadFilePath : 애니메이션을 로드할 파일 경로
650
+ """
651
+
652
+ if not(rt.doesFileExist(inLoadFilePath)):
653
+ return False
654
+
655
+ rt.LoadSaveAnimation.setUpAnimsForLoad(inObjs, includePB2s=True, stripLayers=True)
656
+ rt.LoadSaveAnimation.loadAnimation(inLoadFilePath, inObjs, insert=False, relative=False, insertTime=0, stripLayers=True)
657
+
658
+ return True
659
+
660
+
577
661
  def save_xform(self, inObj):
578
662
  """
579
663
  객체의 현재 변환 행렬(월드, 부모 스페이스)을 저장하여 복원을 가능하게 함.
@@ -108,7 +108,7 @@ class AutoClavicle:
108
108
  autoClavicleBone.transform = inClavicle.transform
109
109
  self.anim.move_local(autoClavicleBone, clavicleLength/2.0, 0.0, 0.0)
110
110
  autoClavicleBone.parent = inClavicle
111
- genBones.extend(autoClavicleBone)
111
+ genBones.append(autoClavicleBone)
112
112
 
113
113
  # 타겟 헬퍼 포인트 생성 (쇄골과 상완용)
114
114
  rotTargetClavicle = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, self.name.get_name_part_value_by_description("Type", "Target")))
pyjallib/max/bip.py CHANGED
@@ -454,7 +454,7 @@ class Bip:
454
454
  rt.biped.collapseAtLayer(inBipRoot.controller, 0)
455
455
  layerNum = rt.biped.numLayers(inBipRoot.controller)
456
456
 
457
- def save_bip_file(self, inBipRoot, inFile, inBakeAllKeys=True, inCollapseLayers=True, progress_callback=None):
457
+ def save_bip_file(self, inBipRoot, inFile, inBakeAllKeys=True, inCollapseLayers=True, inUseAnimationRangeOnly=True, progress_callback=None):
458
458
  """
459
459
  Biped BIP 파일 저장
460
460
 
@@ -500,84 +500,105 @@ class Bip:
500
500
  rt.biped.addNewKey(item.controller, frame)
501
501
  if progress_callback:
502
502
  progress_callback(frame - startFrame + 1, totalFrame)
503
+
504
+ minFrame = 0.0
505
+ maxFrame = 0.0
506
+ allTargetBipedObjs = self.get_nodes(inBipRoot)
507
+ for item in allTargetBipedObjs:
508
+ if item == item.controller.rootNode:
509
+ horizontalController = rt.getPropertyController(item.controller, "horizontal")
510
+ verticalController = rt.getPropertyController(item.controller, "vertical")
511
+ turningController = rt.getPropertyController(item.controller, "turning")
512
+
513
+ minFrame = min(float(horizontalController.keys[0].time), minFrame)
514
+ minFrame = min(float(verticalController.keys[0].time), minFrame)
515
+ minFrame = min(float(turningController.keys[0].time), minFrame)
516
+
517
+ maxFrame = max(float(horizontalController.keys[-1].time), maxFrame)
518
+ maxFrame = max(float(verticalController.keys[-1].time), maxFrame)
519
+ maxFrame = max(float(turningController.keys[-1].time), maxFrame)
520
+ else:
521
+ minFrame = min(float(item.controller.keys[0].time), minFrame)
522
+ maxFrame = max(float(item.controller.keys[-1].time), maxFrame)
523
+
524
+ bakeStartFrame = minFrame
525
+ bakeEndFrame = maxFrame
526
+
527
+ if inUseAnimationRangeOnly:
528
+ allTargetBipedObjs = self.get_nodes(inBipRoot)
529
+ startFrame = rt.execute("(animationRange.start as integer) / TicksPerFrame")
530
+ endFrame = rt.execute("(animationRange.end as integer) / TicksPerFrame")
531
+ bakeStartFrame = startFrame
532
+ bakeEndFrame = endFrame
533
+
534
+ if inBakeAllKeys:
535
+ rt.biped.saveBipFileSegment(inBipRoot.controller, inFile, bakeStartFrame, bakeEndFrame, rt.name("keyPerFrame"))
536
+ else:
537
+ rt.biped.saveBipFileSegment(inBipRoot.controller, inFile, bakeStartFrame, bakeEndFrame)
503
538
 
504
- rt.biped.saveBipFile(inBipRoot.controller, inFile)
505
539
  return True
506
540
 
507
541
  def link_base_skeleton(self, skinBoneBaseName="b"):
508
542
  """
509
543
  기본 스켈레톤 링크 (Biped와 일반 뼈대 연결)
510
544
  """
511
- rt.setWaitCursor()
512
-
513
- bipSkel = self.get_bips()
514
- baseSkel = [None] * len(bipSkel)
515
-
516
- for i in range(len(bipSkel)):
517
- baseSkeletonName = self.name.replace_base(bipSkel[i].name, skinBoneBaseName)
518
- baseSkeletonName = self.name.replace_filteringChar(baseSkeletonName, "_")
519
- baseSkelObj = rt.getNodeByName(baseSkeletonName)
520
- if rt.isValidObj(baseSkelObj):
521
- baseSkel[i] = baseSkelObj
522
-
523
- self.anim.save_xform(bipSkel[i])
524
- self.anim.set_xform(bipSkel[i])
525
-
526
- self.anim.save_xform(baseSkel[i])
527
- self.anim.set_xform(baseSkel[i])
545
+ bipComs = self.get_coms()
546
+ if len(bipComs) != 1:
547
+ return False
528
548
 
529
- for i in range(len(baseSkel)):
530
- if baseSkel[i] is not None:
531
- baseSkel[i].scale.controller = rt.scaleXYZ()
532
- baseSkel[i].controller = rt.link_constraint()
533
-
534
- self.anim.set_xform([baseSkel[i]], space="World")
535
- baseSkel[i].transform.controller.AddTarget(bipSkel[i], 0)
549
+ bipCom = bipComs[0]
550
+ bipNodes = self.get_nodes(bipCom)
551
+
552
+ targetBones = [item for item in bipNodes
553
+ if (rt.classOf(item) == rt.Biped_Object)
554
+ and (not rt.matchPattern(item.name, pattern="*Twist*"))
555
+ and (item != item.controller.rootNode)]
556
+ sortedBipBones = self.bone.sort_bones_as_hierarchy(targetBones)
557
+
558
+ skinBones = []
559
+ for item in sortedBipBones:
560
+ skinBoneName = self.name.replace_name_part("Base", item.name, skinBoneBaseName)
561
+ skinBoneName = self.name.replace_filtering_char(skinBoneName, "_")
562
+ foundSkinBone = rt.getNodeByName(skinBoneName)
563
+ if rt.isValidObj(foundSkinBone):
564
+ skinBones.append(foundSkinBone)
565
+ else:
566
+ skinBones.append(None)
536
567
 
537
- for i in range(len(baseSkel)):
538
- if baseSkel[i] is not None:
539
- baseSkel[i].boneEnable = True
540
-
541
- rt.setArrowCursor()
568
+ for i, item in enumerate(sortedBipBones):
569
+ if skinBones[i] is not None:
570
+ self.bone.link_skin_bone(skinBones[i], item)
542
571
 
543
572
  def unlink_base_skeleton(self, skinBoneBaseName="b"):
544
573
  """
545
574
  기본 스켈레톤 링크 해제
546
575
  """
547
- rt.setWaitCursor()
548
-
549
576
  bipComs = self.get_coms()
550
- allBips = self.get_nodes(bipComs[0])
551
- bipSkel = [item for item in allBips if item != bipComs[0]]
552
- baseSkel = [None] * len(bipSkel)
553
-
554
- for i in range(len(bipSkel)):
555
- baseSkeletonName = self.name.replace_name_part("Base", bipSkel[i].name, skinBoneBaseName)
556
- baseSkeletonName = self.name.replace_filtering_char(baseSkeletonName, "_")
557
- print("baseSkeletonName", baseSkeletonName)
558
- baseSkelObj = rt.getNodeByName(baseSkeletonName)
559
- print("baseSkelObj", baseSkelObj)
560
- if rt.isValidObj(baseSkelObj):
561
- baseSkel[i] = baseSkelObj
562
-
563
- self.anim.save_xform(bipSkel[i])
564
- self.anim.set_xform(bipSkel[i])
565
-
566
- self.anim.save_xform(baseSkel[i])
567
- self.anim.set_xform(baseSkel[i])
568
-
569
- for i in range(len(baseSkel)):
570
- if baseSkel[i] is not None:
571
- baseSkel[i].controller = rt.prs()
572
- self.anim.set_xform([baseSkel[i]], space="World")
573
-
574
- for i in range(len(baseSkel)):
575
- if baseSkel[i] is not None:
576
- baseSkel[i].boneEnable = True
577
-
578
- rt.setArrowCursor()
577
+ if len(bipComs) != 1:
578
+ return False
579
579
 
580
- def convert_name_for_ue5(self, inBipRoot, inBipNameConfigFile):
580
+ bipCom = bipComs[0]
581
+ bipNodes = self.get_nodes(bipCom)
582
+ targetBones = [item for item in bipNodes
583
+ if (rt.classOf(item) == rt.Biped_Object)
584
+ and (not rt.matchPattern(item.name, pattern="*Twist*"))
585
+ and (item != item.controller.rootNode)]
586
+ sortedBipBones = self.bone.sort_bones_as_hierarchy(targetBones)
587
+ skinBones = []
588
+ for item in sortedBipBones:
589
+ skinBoneName = self.name.replace_name_part("Base", item.name, skinBoneBaseName)
590
+ skinBoneName = self.name.replace_filtering_char(skinBoneName, "_")
591
+ foundSkinBone = rt.getNodeByName(skinBoneName)
592
+ if rt.isValidObj(foundSkinBone):
593
+ skinBones.append(foundSkinBone)
594
+ else:
595
+ skinBones.append(None)
596
+
597
+ for item in skinBones:
598
+ if item is not None:
599
+ self.bone.unlink_skin_bone(item)
600
+
601
+ def convert_name_for_ue5(self, inBipRoot, inBipNameConfigFile, inRenameFingers=True):
581
602
  """
582
603
  Biped 이름을 UE5에 맞게 변환
583
604
 
@@ -587,17 +608,11 @@ class Bip:
587
608
  Returns:
588
609
  변환된 Biped 객체
589
610
  """
590
- bipComs = self.get_coms()
591
-
592
- if len(bipComs) > 1:
593
- rt.messageBox("Please select only one Biped object.")
594
- return False
595
-
596
611
  from pyjallib.max.name import Name
597
612
 
598
613
  bipNameTool = Name(configPath=inBipNameConfigFile)
599
614
 
600
- bipObj = bipComs[0]
615
+ bipObj = inBipRoot
601
616
  bipNodes = self.get_all(bipObj)
602
617
  for bipNode in bipNodes:
603
618
  if bipNode.name == bipObj.controller.rootName:
@@ -628,7 +643,7 @@ class Bip:
628
643
  if newNameDict["RealName"] == "Forearm":
629
644
  newNameDict["RealName"] = "Lowerarm"
630
645
 
631
- if newNameDict["RealName"] == "Spine" or newNameDict["RealName"] == "Neck":
646
+ if newNameDict["RealName"] == "Spine" or newNameDict["RealName"] == "Neck" or newNameDict["RealName"] == "Tail":
632
647
  if newNameDict["Index"] == "":
633
648
  newNameDict["Index"] = str(int(1)).zfill(self.name.get_padding_num())
634
649
  else:
@@ -639,13 +654,6 @@ class Bip:
639
654
  bipNode.name = newBipName.lower()
640
655
 
641
656
  # 손가락 바꾸는 부분
642
- # 5개가 아닌 손가락은 지원하지 않음
643
- # 손가락 하나의 최대 링크는 3개
644
- indices = []
645
- if bipObj.controller.knuckles:
646
- pass
647
- else:
648
- indices = list(range(0, 15, 3))
649
657
 
650
658
  fingerNum = bipObj.controller.fingers
651
659
  fingerLinkNum = bipObj.controller.fingerLinks
@@ -668,7 +676,9 @@ class Bip:
668
676
  fingers.append(fingerNode)
669
677
  rFingersList.append(fingers)
670
678
 
671
- fingerName = ["thumb", "index", "middle", "ring", "pinky"]
679
+ fingerName = ["finger0", "finger1", "finger2", "finger3", "finger4"]
680
+ if inRenameFingers:
681
+ fingerName = ["thumb", "index", "middle", "ring", "pinky"]
672
682
 
673
683
  for i, fingers in enumerate(lFingersList):
674
684
  for j, item in enumerate(fingers):
pyjallib/max/bone.py CHANGED
@@ -212,14 +212,14 @@ class Bone:
212
212
  return False
213
213
  return False
214
214
 
215
- def create_nub_bone(self, inName, inSize):
215
+ def create_nub_bone(self, inName, inSize, inBoneScaleType=rt.Name("none")):
216
216
  """
217
217
  Nub 뼈대 생성.
218
218
 
219
219
  Args:
220
220
  inName: 뼈대 이름
221
221
  inSize: 뼈대 크기
222
-
222
+ inBoneScaleType: 뼈대 스케일 타입 (기본값: rt.Name("none"))
223
223
  Returns:
224
224
  생성된 Nub 뼈대
225
225
  """
@@ -242,6 +242,8 @@ class Bone:
242
242
  nubBone.name = self.name.remove_name_part("Nub", nubBone.name)
243
243
  nubBone.name = self.name.replace_name_part("Nub", nubBone.name, self.name.get_name_part_value_by_description("Nub", "Nub"))
244
244
 
245
+ nubBone.boneScaleType = inBoneScaleType
246
+
245
247
  # 화면 갱신 재개
246
248
  rt.enableSceneRedraw()
247
249
  rt.redrawViews()
@@ -751,6 +753,34 @@ class Bone:
751
753
 
752
754
  return True
753
755
 
756
+ def unlink_skin_bone(self, inSkinBone):
757
+ """
758
+ 스킨 뼈대를 원본 뼈대에서 연결 해제.
759
+
760
+ Args:
761
+ inSkinBone: 연결 해제할 스킨 뼈대
762
+ """
763
+ self.anim.save_xform(inSkinBone)
764
+ self.anim.set_xform(inSkinBone)
765
+
766
+ inSkinBone.controller = rt.prs()
767
+ self.anim.set_xform(inSkinBone, space="World")
768
+
769
+ return True
770
+
771
+ def unlink_skin_bones(self, inSkinBoneArray):
772
+ """
773
+ 스킨 뼈대 배열을 원본 뼈대에서 연결 해제.
774
+
775
+ Args:
776
+ inSkinBoneArray: 연결 해제할 스킨 뼈대 배열
777
+ """
778
+ for item in inSkinBoneArray:
779
+ if rt.isValidObj(item):
780
+ self.unlink_skin_bone(item)
781
+
782
+ return True
783
+
754
784
  def create_skin_bone(self, inBoneArray, skipNub=True, mesh=True, link=True, skinBoneBaseName=""):
755
785
  """
756
786
  스킨 뼈대 생성.
@@ -1153,58 +1183,90 @@ class Bone:
1153
1183
  if len(skinBones) == 0:
1154
1184
  return False
1155
1185
 
1186
+ pelvisBone = None
1187
+ lastSpineBone = None
1188
+
1189
+ for item in skinBones:
1190
+ if rt.matchPattern(item.name.lower(), pattern="*pelvis*"):
1191
+ pelvisBone = item
1192
+ if rt.matchPattern(item.name.lower(), pattern="*spine*"):
1193
+ lastSpineBone = item
1194
+ foundChildren = self.get_every_children(item)
1195
+ for child in foundChildren:
1196
+ if rt.matchPattern(child.name.lower(), pattern="*spine*"):
1197
+ lastSpineBone = child
1198
+
1199
+ if pelvisBone is not None and lastSpineBone is not None:
1200
+ for item in skinBones:
1201
+ if rt.matchPattern(item.name.lower(), pattern="*thigh*"):
1202
+ item.parent = pelvisBone
1203
+ if rt.matchPattern(item.name.lower(), pattern="*clavicle*"):
1204
+ item.parent = lastSpineBone
1205
+
1156
1206
  for item in skinBones:
1157
- if rt.matchPattern(item.name, pattern="*pelvis*"):
1207
+ if rt.matchPattern(item.name.lower(), pattern="*pelvis*"):
1158
1208
  self.anim.rotate_local(item, 180, 0, 0, dontAffectChildren=True)
1159
- if rt.matchPattern(item.name, pattern="*spine*"):
1209
+ if rt.matchPattern(item.name.lower(), pattern="*spine*"):
1160
1210
  self.anim.rotate_local(item, 180, 0, 0, dontAffectChildren=True)
1161
- if rt.matchPattern(item.name, pattern="*neck*"):
1211
+ if rt.matchPattern(item.name.lower(), pattern="*neck*"):
1162
1212
  self.anim.rotate_local(item, 180, 0, 0, dontAffectChildren=True)
1163
- if rt.matchPattern(item.name, pattern="*head*"):
1213
+ if rt.matchPattern(item.name.lower(), pattern="*head*"):
1164
1214
  self.anim.rotate_local(item, 180, 0, 0, dontAffectChildren=True)
1165
- if rt.matchPattern(item.name, pattern="*thigh*l"):
1215
+ if rt.matchPattern(item.name.lower(), pattern="*thigh*l"):
1166
1216
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1167
- if rt.matchPattern(item.name, pattern="*calf*l"):
1217
+ if rt.matchPattern(item.name.lower(), pattern="*calf*l"):
1168
1218
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1169
- if rt.matchPattern(item.name, pattern="*foot*l"):
1219
+ if rt.matchPattern(item.name.lower(), pattern="*foot*l"):
1170
1220
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1171
- if rt.matchPattern(item.name, pattern="*ball*r"):
1221
+ if rt.matchPattern(item.name.lower(), pattern="*ball*r"):
1172
1222
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1173
1223
 
1174
- if rt.matchPattern(item.name, pattern="*clavicle*r"):
1224
+ if rt.matchPattern(item.name.lower(), pattern="*clavicle*r"):
1175
1225
  self.anim.rotate_local(item, 0, 0, -180, dontAffectChildren=True)
1176
- if rt.matchPattern(item.name, pattern="*upperarm*r"):
1226
+ if rt.matchPattern(item.name.lower(), pattern="*upperarm*r"):
1177
1227
  self.anim.rotate_local(item, 0, 0, -180, dontAffectChildren=True)
1178
- if rt.matchPattern(item.name, pattern="*lowerarm*r"):
1228
+ if rt.matchPattern(item.name.lower(), pattern="*lowerarm*r"):
1179
1229
  self.anim.rotate_local(item, 0, 0, -180, dontAffectChildren=True)
1180
- if rt.matchPattern(item.name, pattern="*hand*r"):
1230
+ if rt.matchPattern(item.name.lower(), pattern="*hand*r"):
1181
1231
  self.anim.rotate_local(item, 0, 0, -180, dontAffectChildren=True)
1182
1232
 
1183
- if rt.matchPattern(item.name, pattern="*thumb*r"):
1233
+ if rt.matchPattern(item.name.lower(), pattern="*thumb*r"):
1234
+ self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1235
+ if rt.matchPattern(item.name.lower(), pattern="*index*r"):
1236
+ self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1237
+ if rt.matchPattern(item.name.lower(), pattern="*middle*r"):
1238
+ self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1239
+ if rt.matchPattern(item.name.lower(), pattern="*ring*r"):
1184
1240
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1185
- if rt.matchPattern(item.name, pattern="*index*r"):
1241
+ if rt.matchPattern(item.name.lower(), pattern="*pinky*r"):
1186
1242
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1187
- if rt.matchPattern(item.name, pattern="*middle*r"):
1243
+
1244
+ if rt.matchPattern(item.name.lower(), pattern="*finger0*r"):
1245
+ self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1246
+ if rt.matchPattern(item.name.lower(), pattern="*finger1*r"):
1188
1247
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1189
- if rt.matchPattern(item.name, pattern="*ring*r"):
1248
+ if rt.matchPattern(item.name.lower(), pattern="*finger2*r"):
1190
1249
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1191
- if rt.matchPattern(item.name, pattern="*pinky*r"):
1250
+ if rt.matchPattern(item.name.lower(), pattern="*finger3*r"):
1251
+ self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1252
+ if rt.matchPattern(item.name.lower(), pattern="*finger4*r"):
1192
1253
  self.anim.rotate_local(item, 0, 0, 180, dontAffectChildren=True)
1193
1254
 
1194
- if rt.matchPattern(item.name, pattern="*metacarpal*"):
1255
+ if rt.matchPattern(item.name.lower(), pattern="*metacarpal*"):
1195
1256
  tempArray = self.name._split_to_array(item.name)
1196
1257
  item.name = self.name._combine(tempArray, inFilChar="_")
1197
1258
  item.name = self.name.remove_name_part("Base", item.name)
1198
1259
 
1199
1260
  self.anim.save_xform(item)
1200
1261
 
1201
- self.relink_missing_skin_bones_for_ue5manny(skinBones)
1262
+ if isHuman:
1263
+ self.relink_missing_skin_bones_for_ue5manny(skinBones)
1202
1264
 
1203
1265
  self.link_skin_bones(skinBones, sortedBipBones)
1204
1266
  for item in skinBones:
1205
1267
  self.anim.save_xform(item)
1206
1268
 
1207
- return skinBones
1269
+ return (skinBones + missingBipBones)
1208
1270
 
1209
1271
  def set_bone_on(self, inBone):
1210
1272
  """
pyjallib/max/boneChain.py CHANGED
@@ -74,17 +74,20 @@ class BoneChain:
74
74
  return False
75
75
 
76
76
  try:
77
- # 뼈대 삭제
77
+ # 유효한 노드들을 모아서 한 번에 삭제
78
+ valid_nodes = []
79
+
80
+ # 유효한 뼈대 수집
78
81
  if self.bones:
79
- for bone in self.bones:
80
- if rt.isValidNode(bone):
81
- rt.delete(bone)
82
+ valid_nodes.extend([bone for bone in self.bones if rt.isValidNode(bone)])
82
83
 
83
- # 헬퍼 삭제
84
+ # 유효한 헬퍼 수집
84
85
  if self.helpers:
85
- for helper in self.helpers:
86
- if rt.isValidNode(helper):
87
- rt.delete(helper)
86
+ valid_nodes.extend([helper for helper in self.helpers if rt.isValidNode(helper)])
87
+
88
+ # 수집된 노드가 있으면 한 번에 삭제
89
+ if valid_nodes:
90
+ rt.delete(valid_nodes)
88
91
 
89
92
  self.bones = []
90
93
  self.helpers = []
@@ -95,7 +98,7 @@ class BoneChain:
95
98
 
96
99
  def delete_all(self):
97
100
  """
98
- 체인의 모든 뼈대와 헬퍼를 3ds Max 씬에서 삭제
101
+ 체인의 모든 뼈대와 헬퍼를 3ds Max 씬에서 삭제하고 소스본과 파라미터도 초기화
99
102
 
100
103
  Returns:
101
104
  bool: 삭제 성공 여부
@@ -104,20 +107,14 @@ class BoneChain:
104
107
  return False
105
108
 
106
109
  try:
107
- # 뼈대 삭제
108
- if self.bones:
109
- for bone in self.bones:
110
- if rt.isValidNode(bone):
111
- rt.delete(bone)
110
+ # delete 메소드를 재사용하여 뼈대와 헬퍼 삭제
111
+ result = self.delete()
112
112
 
113
- # 헬퍼 삭제
114
- if self.helpers:
115
- for helper in self.helpers:
116
- if rt.isValidNode(helper):
117
- rt.delete(helper)
118
-
119
- self.clear()
120
- return True
113
+ # 추가로 소스본과 파라미터 초기화
114
+ self.sourceBones = []
115
+ self.parameters = []
116
+
117
+ return result
121
118
  except:
122
119
  return False
123
120