pyjallib 0.1.10__py3-none-any.whl → 0.1.12__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,12 +6,12 @@ pyjallib Package
6
6
  Python library for game character development pipeline.
7
7
  """
8
8
 
9
- __version__ = '0.1.10'
9
+ __version__ = '0.1.12'
10
10
 
11
11
  # reload_modules 함수를 패키지 레벨에서 사용 가능하게 함
12
- from .namePart import NamePart, NamePartType
13
- from .naming import Naming
14
- from .namingConfig import NamingConfig
15
- from .nameToPath import NameToPath
16
- from .perforce import Perforce
17
- from .reloadModules import reload_modules
12
+ from pyjallib.namePart import NamePart, NamePartType
13
+ from pyjallib.naming import Naming
14
+ from pyjallib.namingConfig import NamingConfig
15
+ from pyjallib.nameToPath import NameToPath
16
+ from pyjallib.perforce import Perforce
17
+ from pyjallib.reloadModules import reload_modules
pyjallib/max/__init__.py CHANGED
@@ -7,37 +7,35 @@ JalTools 3DS 패키지
7
7
  """
8
8
 
9
9
  # 모듈 임포트
10
- from .header import Header
10
+ from pyjallib.max.header import Header
11
11
 
12
- from .name import Name
13
- from .anim import Anim
12
+ from pyjallib.max.name import Name
13
+ from pyjallib.max.anim import Anim
14
14
 
15
- from .helper import Helper
16
- from .constraint import Constraint
17
- from .bone import Bone
15
+ from pyjallib.max.helper import Helper
16
+ from pyjallib.max.constraint import Constraint
17
+ from pyjallib.max.bone import Bone
18
18
 
19
- from .mirror import Mirror
20
- from .layer import Layer
21
- from .align import Align
22
- from .select import Select
23
- from .link import Link
19
+ from pyjallib.max.mirror import Mirror
20
+ from pyjallib.max.layer import Layer
21
+ from pyjallib.max.align import Align
22
+ from pyjallib.max.select import Select
23
+ from pyjallib.max.link import Link
24
24
 
25
- from .bip import Bip
26
- from .skin import Skin
27
- from .morph import Morph
25
+ from pyjallib.max.bip import Bip
26
+ from pyjallib.max.skin import Skin
27
+ from pyjallib.max.morph import Morph
28
28
 
29
- from .twistBone import TwistBone
30
- from .twistBoneChain import TwistBoneChain
31
- from .groinBone import GroinBone
32
- from .groinBoneChain import GroinBoneChain
33
- from .autoClavicle import AutoClavicle
34
- from .autoClavicleChain import AutoClavicleChain
35
- from .volumeBone import VolumeBone
36
- from .volumeBoneChain import VolumeBoneChain
37
- from .kneeBone import KneeBone
38
- from .hip import Hip
29
+ from pyjallib.max.boneChain import BoneChain
39
30
 
40
- from .ui.Container import Container
31
+ from pyjallib.max.twistBone import TwistBone
32
+ from pyjallib.max.groinBone import GroinBone
33
+ from pyjallib.max.autoClavicle import AutoClavicle
34
+ from pyjallib.max.volumeBone import VolumeBone
35
+ from pyjallib.max.kneeBone import KneeBone
36
+ from pyjallib.max.hip import Hip
37
+
38
+ from pyjallib.max.ui.Container import Container
41
39
 
42
40
  # 모듈 내보내기
43
41
  __all__ = [
@@ -55,6 +53,7 @@ __all__ = [
55
53
  'Bip',
56
54
  'Skin',
57
55
  'Morph',
56
+ 'BoneChain',
58
57
  'TwistBone',
59
58
  'TwistBoneChain',
60
59
  'GroinBone',
@@ -16,6 +16,8 @@ from .bone import Bone
16
16
  from .constraint import Constraint
17
17
  from .bip import Bip
18
18
 
19
+ from .boneChain import BoneChain
20
+
19
21
 
20
22
  class AutoClavicle:
21
23
  """
@@ -166,12 +168,45 @@ class AutoClavicle:
166
168
  result = {
167
169
  "Bones": genBones,
168
170
  "Helpers": genHelpers,
169
- "Clavicle": inClavicle,
170
- "UpperArm": inUpperArm,
171
- "LiftScale": liftScale
171
+ "SourceBones": [inClavicle, inUpperArm],
172
+ "Parameters": [liftScale]
172
173
  }
173
174
 
174
175
  # 메소드 호출 후 데이터 초기화
175
176
  self.reset()
176
177
 
177
- return result
178
+ return BoneChain.from_result(result)
179
+
180
+ def create_bones_from_chain(self, inBoneChain: BoneChain):
181
+ """
182
+ 기존 BoneChain 객체에서 자동 쇄골 뼈를 생성합니다.
183
+ 기존 설정을 복원하거나 저장된 데이터에서 쇄골 셋업을 재생성할 때 사용합니다.
184
+
185
+ Args:
186
+ inBoneChain (BoneChain): 자동 쇄골 정보를 포함한 BoneChain 객체
187
+
188
+ Returns:
189
+ BoneChain: 업데이트된 BoneChain 객체 또는 실패 시 None
190
+ """
191
+ if not inBoneChain or inBoneChain.is_empty():
192
+ return None
193
+
194
+ inBoneChain.delete()
195
+
196
+ # BoneChain에서 필요한 정보 추출
197
+ sourceBones = inBoneChain.sourceBones
198
+ parameters = inBoneChain.parameters
199
+
200
+ # 필수 소스 본 확인
201
+ if len(sourceBones) < 2 or not rt.isValidNode(sourceBones[0]) or not rt.isValidNode(sourceBones[1]):
202
+ return None
203
+
204
+ # 파라미터 가져오기 (또는 기본값 사용)
205
+ liftScale = parameters[0] if len(parameters) > 0 else 0.8
206
+
207
+ # 쇄골 생성
208
+ inClavicle = sourceBones[0]
209
+ inUpperArm = sourceBones[1]
210
+
211
+ # 새로운 쇄골 생성
212
+ return self.create_bones(inClavicle, inUpperArm, liftScale)
pyjallib/max/bip.py CHANGED
@@ -439,6 +439,71 @@ class Bip:
439
439
  if colNum > 0:
440
440
  rt.biped.deleteAllCopyCollections(inBipRoot.controller)
441
441
 
442
+ def collapse_layers(self, inBipRoot):
443
+ """
444
+ Biped 레이어 병합
445
+
446
+ Args:
447
+ inBipRoot: 대상 Biped 객체
448
+ """
449
+ if not self.is_biped_object(inBipRoot):
450
+ return False
451
+
452
+ layerNum = rt.biped.numLayers(inBipRoot.controller)
453
+ while layerNum > 0:
454
+ rt.biped.collapseAtLayer(inBipRoot.controller, 0)
455
+ layerNum = rt.biped.numLayers(inBipRoot.controller)
456
+
457
+ def save_bip_file(self, inBipRoot, inFile, inBakeAllKeys=True, inCollapseLayers=True, progress_callback=None):
458
+ """
459
+ Biped BIP 파일 저장
460
+
461
+ Args:
462
+ inBipRoot: 저장 대상 Biped 루트 노드
463
+ inFile: 저장할 BIP 파일 경로
464
+ inBakeAllKeys: 모든 키를 베이크할지 여부 (기본값: True)
465
+ inCollapseLayers: 레이어를 병합할지 여부 (기본값: True)
466
+
467
+ Returns:
468
+ bool: 저장 성공 시 True, 실패 시 False
469
+ """
470
+ if not self.is_biped_object(inBipRoot):
471
+ return False
472
+
473
+ directory = os.path.dirname(inFile)
474
+ if directory and not os.path.exists(directory):
475
+ try:
476
+ os.makedirs(directory, exist_ok=True)
477
+ except OSError as e:
478
+ return False
479
+
480
+ if inCollapseLayers:
481
+ self.collapse_layers(inBipRoot)
482
+
483
+ if inBakeAllKeys:
484
+ allTargetBipedObjs = self.get_nodes(inBipRoot)
485
+ startFrame = rt.execute("(animationRange.start as integer) / TicksPerFrame")
486
+ endFrame = rt.execute("(animationRange.end as integer) / TicksPerFrame")
487
+ totalFrame = endFrame - startFrame + 1
488
+
489
+ for frame in range(startFrame, endFrame + 1):
490
+ for item in allTargetBipedObjs:
491
+ if item == item.controller.rootNode:
492
+ horizontalController = rt.getPropertyController(item.controller, "horizontal")
493
+ verticalController = rt.getPropertyController(item.controller, "vertical")
494
+ turningController = rt.getPropertyController(item.controller, "turning")
495
+
496
+ rt.biped.addNewKey(horizontalController, frame)
497
+ rt.biped.addNewKey(verticalController, frame)
498
+ rt.biped.addNewKey(turningController, frame)
499
+ else:
500
+ rt.biped.addNewKey(item.controller, frame)
501
+ if progress_callback:
502
+ progress_callback(frame - startFrame + 1, totalFrame)
503
+
504
+ rt.biped.saveBipFile(inBipRoot.controller, inFile)
505
+ return True
506
+
442
507
  def link_base_skeleton(self, skinBoneBaseName="b"):
443
508
  """
444
509
  기본 스켈레톤 링크 (Biped와 일반 뼈대 연결)
@@ -608,6 +673,7 @@ class Bip:
608
673
  for i, fingers in enumerate(lFingersList):
609
674
  for j, item in enumerate(fingers):
610
675
  item.name = self.name.replace_name_part("RealName", item.name, fingerName[i])
676
+ item.name = self.name.replace_name_part("Side", item.name, self.name.get_name_part_value_by_description("Side", "Left"))
611
677
  item.name = self.name.replace_name_part("Index", item.name, str(j+1))
612
678
 
613
679
  fingerNub = self.bone.get_every_children(fingers[-1])[0]
@@ -618,6 +684,7 @@ class Bip:
618
684
  for i, fingers in enumerate(rFingersList):
619
685
  for j, item in enumerate(fingers):
620
686
  item.name = self.name.replace_name_part("RealName", item.name, fingerName[i])
687
+ item.name = self.name.replace_name_part("Side", item.name, self.name.get_name_part_value_by_description("Side", "Right"))
621
688
  item.name = self.name.replace_name_part("Index", item.name, str(j+1))
622
689
 
623
690
  fingerNub = self.bone.get_every_children(fingers[-1])[0]
@@ -688,8 +755,8 @@ class Bip:
688
755
  toeNub.name = self.name.replace_name_part("Nub", toeNub.name, self.name.get_name_part_value_by_description("Nub", "Nub"))
689
756
 
690
757
  if toeLinkNum == 1:
691
- rToesList[0][0].name = self.name.replace_name_part("RealName", lToesList[0][0].name, "ball")
692
- rToesList[0][0].name = self.name.remove_name_part("Index", lToesList[0][0].name)
758
+ rToesList[0][0].name = self.name.replace_name_part("RealName", rToesList[0][0].name, "ball")
759
+ rToesList[0][0].name = self.name.remove_name_part("Index", rToesList[0][0].name)
693
760
  else:
694
761
  for i, item in enumerate(rToesList[0]):
695
762
  item.name = self.name.replace_name_part("RealName", item.name, "ball")
pyjallib/max/bone.py CHANGED
@@ -239,6 +239,7 @@ class Bone:
239
239
  nubBone.backfin = False
240
240
  nubBone.sidefins = False
241
241
  nubBone.name = self.name.remove_name_part("Index", inName)
242
+ nubBone.name = self.name.remove_name_part("Nub", nubBone.name)
242
243
  nubBone.name = self.name.replace_name_part("Nub", nubBone.name, self.name.get_name_part_value_by_description("Nub", "Nub"))
243
244
 
244
245
  # 화면 갱신 재개
@@ -888,6 +889,7 @@ class Bone:
888
889
  returnBones = []
889
890
  spine3 = None
890
891
  neck = None
892
+ head = None
891
893
 
892
894
  handL = None
893
895
  handR = None
@@ -905,6 +907,8 @@ class Bone:
905
907
  spine3 = item
906
908
  if rt.matchPattern(item.name, pattern="*neck 01"):
907
909
  neck = item
910
+ if rt.matchPattern(item.name, pattern="*head"):
911
+ head = item
908
912
  if rt.matchPattern(item.name, pattern="*hand*l"):
909
913
  handL = item
910
914
  if rt.matchPattern(item.name, pattern="*hand*r"):
@@ -924,6 +928,8 @@ class Bone:
924
928
 
925
929
  filteringChar = self.name._get_filtering_char(inBoneArray[-1].name)
926
930
  isLower = inBoneArray[-1].name[0].islower()
931
+
932
+ # Spine 4,5 생성
927
933
  spineName = self.name.get_name_part_value_by_description("Base", "Biped") + filteringChar + "Spine"
928
934
 
929
935
  spine4 = self.create_nub_bone(spineName, 2)
@@ -946,6 +952,23 @@ class Bone:
946
952
  returnBones.append(spine4)
947
953
  returnBones.append(spine5)
948
954
 
955
+ # 목 생성
956
+ neckName = self.name.get_name_part_value_by_description("Base", "Biped") + filteringChar + "Neck"
957
+
958
+ nekc2 = self.create_nub_bone(neckName, 2)
959
+
960
+ nekc2.name = self.name.replace_name_part("Index", nekc2.name, "2")
961
+ nekc2.name = self.name.remove_name_part("Nub", nekc2.name)
962
+ if isLower:
963
+ nekc2.name = nekc2.name.lower()
964
+
965
+ neckDistance = rt.distance(neck, head)/2.0
966
+ rt.setProperty(nekc2, "transform", neck.transform)
967
+ self.anim.move_local(nekc2, neckDistance, 0, 0)
968
+
969
+ returnBones.append(nekc2)
970
+
971
+ # 손가락용 메타카팔 생성
949
972
  for i, finger in enumerate(lFingers):
950
973
  knuckleBoneName = self.name.add_suffix_to_real_name(finger.name, filteringChar+knuckleName)
951
974
  knuckleBoneName = self.name.remove_name_part("Index", knuckleBoneName)
@@ -992,6 +1015,7 @@ class Bone:
992
1015
  returnBones = []
993
1016
 
994
1017
  spine3 = None
1018
+ neck = None
995
1019
 
996
1020
  handL = None
997
1021
  handR = None
@@ -1001,6 +1025,8 @@ class Bone:
1001
1025
  for item in inBipArray:
1002
1026
  if rt.matchPattern(item.name, pattern="*spine 03"):
1003
1027
  spine3 = item
1028
+ if rt.matchPattern(item.name, pattern="*neck 01"):
1029
+ neck = item
1004
1030
  if rt.matchPattern(item.name, pattern="*hand*l"):
1005
1031
  handL = item
1006
1032
  if rt.matchPattern(item.name, pattern="*hand*r"):
@@ -1009,6 +1035,8 @@ class Bone:
1009
1035
  for item in inMissingBoneArray:
1010
1036
  if rt.matchPattern(item.name, pattern="*spine*"):
1011
1037
  item.parent = spine3
1038
+ if rt.matchPattern(item.name, pattern="*neck*"):
1039
+ item.parent = neck
1012
1040
  if rt.matchPattern(item.name, pattern=f"*{knuckleName}*l"):
1013
1041
  item.parent = handL
1014
1042
  if rt.matchPattern(item.name, pattern=f"*{knuckleName}*r"):
@@ -1025,6 +1053,8 @@ class Bone:
1025
1053
  spine5 = None
1026
1054
 
1027
1055
  neck = None
1056
+ neck2 = None
1057
+ head = None
1028
1058
  clavicleL = None
1029
1059
  clavicleR = None
1030
1060
 
@@ -1042,6 +1072,8 @@ class Bone:
1042
1072
  spine3 = item
1043
1073
  if rt.matchPattern(item.name, pattern="*neck*01"):
1044
1074
  neck = item
1075
+ if rt.matchPattern(item.name, pattern="*head*"):
1076
+ head = item
1045
1077
  if rt.matchPattern(item.name, pattern="*clavicle*l"):
1046
1078
  clavicleL = item
1047
1079
  if rt.matchPattern(item.name, pattern="*clavicle*r"):
@@ -1069,6 +1101,11 @@ class Bone:
1069
1101
  neck.parent = spine5
1070
1102
  clavicleL.parent = spine5
1071
1103
  clavicleR.parent = spine5
1104
+
1105
+ if rt.matchPattern(item.name, pattern="*neck*02"):
1106
+ neck2 = item
1107
+ item.parent = neck
1108
+ head.parent = neck2
1072
1109
 
1073
1110
  if rt.matchPattern(item.name, pattern=f"*{knuckleName}*l"):
1074
1111
  item.parent = handL
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 뼈대 체인(Bone Chain) 기본 클래스 - 뼈대 체인 관리를 위한 공통 기능 제공
6
+
7
+ 이 모듈은 다양한 뼈대 체인 클래스의 기본 부모 클래스로 사용됩니다.
8
+ AutoClavicleChain, GroinBoneChain, VolumeBoneChain, TwistBoneChain 등의
9
+ 특수 목적 뼈대 체인들이 상속받아 사용하며, 공통된 관리 기능을 제공합니다.
10
+
11
+ 기본 클래스는 다음과 같은 공통 기능을 제공합니다:
12
+ - 체인의 뼈대 및 헬퍼 관리
13
+ - 체인 비우기/삭제 기능
14
+ - 체인 상태 확인 기능
15
+ """
16
+
17
+ from pymxs import runtime as rt
18
+
19
+
20
+ class BoneChain:
21
+ """
22
+ 뼈대 체인을 관리하는 기본 클래스
23
+
24
+ 다양한 뼈대 체인의 공통 기능을 담당하는 부모 클래스입니다.
25
+ 뼈대와 헬퍼를 저장하고 기본적인 조작 기능을 제공합니다.
26
+ """
27
+
28
+ def __init__(self, inResult=None):
29
+ """
30
+ 클래스 초기화
31
+
32
+ Args:
33
+ inResult (dict, optional): 뼈대 생성 결과 데이터를 담은 딕셔너리. 기본값은 None
34
+ """
35
+ # 기본 속성 초기화
36
+ if inResult is None:
37
+ # inResult가 None이면 속성들을 빈 리스트로 초기화
38
+ self.bones = []
39
+ self.helpers = []
40
+ self.result = {} # 빈 딕셔너리로 초기화
41
+ self.sourceBones = []
42
+ self.parameters = []
43
+ else:
44
+ self.bones = inResult.get("Bones", [])
45
+ self.helpers = inResult.get("Helpers", [])
46
+ self.result = inResult # 원본 결과 보존
47
+ self.sourceBones = inResult.get("SourceBones", [])
48
+ self.parameters = inResult.get("Parameters", [])
49
+
50
+ def is_empty(self):
51
+ """
52
+ 체인이 비어있는지 확인
53
+
54
+ Returns:
55
+ bool: 체인이 비어있으면 True, 아니면 False
56
+ """
57
+ return len(self.bones) == 0
58
+
59
+ def clear(self):
60
+ """체인의 모든 뼈대와 헬퍼 참조 제거"""
61
+ self.bones = []
62
+ self.helpers = []
63
+ self.sourceBones = []
64
+ self.parameters = []
65
+
66
+ def delete(self):
67
+ """
68
+ 체인의 모든 뼈대와 헬퍼를 3ds Max 씬에서 삭제
69
+
70
+ Returns:
71
+ bool: 삭제 성공 여부
72
+ """
73
+ if self.is_empty() and not self.helpers:
74
+ return False
75
+
76
+ try:
77
+ # 뼈대 삭제
78
+ if self.bones:
79
+ for bone in self.bones:
80
+ if rt.isValidNode(bone):
81
+ rt.delete(bone)
82
+
83
+ # 헬퍼 삭제
84
+ if self.helpers:
85
+ for helper in self.helpers:
86
+ if rt.isValidNode(helper):
87
+ rt.delete(helper)
88
+
89
+ self.bones = []
90
+ self.helpers = []
91
+
92
+ return True
93
+ except:
94
+ return False
95
+
96
+ def delete_all(self):
97
+ """
98
+ 체인의 모든 뼈대와 헬퍼를 3ds Max 씬에서 삭제
99
+
100
+ Returns:
101
+ bool: 삭제 성공 여부
102
+ """
103
+ if self.is_empty() and not self.helpers:
104
+ return False
105
+
106
+ try:
107
+ # 뼈대 삭제
108
+ if self.bones:
109
+ for bone in self.bones:
110
+ if rt.isValidNode(bone):
111
+ rt.delete(bone)
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
121
+ except:
122
+ return False
123
+
124
+ def get_bones(self):
125
+ """
126
+ 체인의 모든 뼈대 가져오기
127
+
128
+ Returns:
129
+ list: 모든 뼈대 객체의 배열
130
+ """
131
+ if self.is_empty():
132
+ return []
133
+
134
+ return self.bones
135
+
136
+ def get_helpers(self):
137
+ """
138
+ 체인의 모든 헬퍼 가져오기
139
+
140
+ Returns:
141
+ list: 모든 헬퍼 객체의 배열
142
+ """
143
+ if not self.helpers:
144
+ return []
145
+
146
+ return self.helpers
147
+
148
+ @classmethod
149
+ def from_result(cls, inResult):
150
+ """
151
+ 결과 딕셔너리로부터 체인 인스턴스 생성
152
+
153
+ Args:
154
+ inResult (dict): 뼈대 생성 결과를 담은 딕셔너리
155
+
156
+ Returns:
157
+ BoneChain: 생성된 체인 인스턴스
158
+ """
159
+ return cls(inResult)
160
+
161
+ def update_from_result(self, inResult):
162
+ """
163
+ 기존 체인 인스턴스를 결과 딕셔너리로부터 업데이트
164
+
165
+ 이미 생성된 체인 객체의 내용을 새로운 결과 데이터로 갱신합니다.
166
+
167
+ Args:
168
+ inResult (dict): 뼈대 생성 결과를 담은 딕셔너리
169
+
170
+ Returns:
171
+ self: 메서드 체이닝을 위한 자기 자신 반환
172
+ """
173
+ if inResult is None:
174
+ return self
175
+
176
+ self.bones = inResult.get("Bones", [])
177
+ self.helpers = inResult.get("Helpers", [])
178
+ self.result = inResult # 원본 결과 보존
179
+ self.sourceBones = inResult.get("SourceBones", [])
180
+ self.parameters = inResult.get("Parameters", [])
181
+
182
+ return self