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 +1 -1
- pyjallib/max/__init__.py +8 -4
- pyjallib/max/anim.py +84 -0
- pyjallib/max/autoClavicle.py +1 -1
- pyjallib/max/bip.py +89 -79
- pyjallib/max/bone.py +84 -22
- pyjallib/max/boneChain.py +19 -22
- pyjallib/max/fbxHandler.py +215 -0
- pyjallib/max/groinBone.py +3 -3
- pyjallib/max/header.py +10 -13
- pyjallib/max/hip.py +4 -4
- pyjallib/max/kneeBone.py +44 -20
- pyjallib/max/layer.py +12 -6
- pyjallib/max/mocap.py +376 -0
- pyjallib/max/rootMotion.py +639 -0
- pyjallib/max/toolManager.py +92 -0
- pyjallib/max/twistBone.py +5 -1
- pyjallib/max/volumeBone.py +2 -1
- pyjallib/perforce.py +116 -4
- {pyjallib-0.1.12.dist-info → pyjallib-0.1.14.dist-info}/METADATA +1 -1
- {pyjallib-0.1.12.dist-info → pyjallib-0.1.14.dist-info}/RECORD +22 -18
- {pyjallib-0.1.12.dist-info → pyjallib-0.1.14.dist-info}/WHEEL +0 -0
pyjallib/max/mocap.py
ADDED
@@ -0,0 +1,376 @@
|
|
1
|
+
"""
|
2
|
+
3ds Max Motion Capture 및 Biped 처리를 위한 모듈
|
3
|
+
|
4
|
+
이 모듈은 FBX 모션 캡처 데이터를 3ds Max Biped로 변환하고
|
5
|
+
리타겟팅하는 기능을 제공합니다.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
from typing import Dict, List, Optional, Any
|
11
|
+
from pathlib import Path
|
12
|
+
|
13
|
+
class BipedBoneMapping:
|
14
|
+
"""Biped 본 매핑 관리 클래스"""
|
15
|
+
|
16
|
+
def __init__(self):
|
17
|
+
self.mapping_data = self._create_default_mapping()
|
18
|
+
|
19
|
+
def _create_default_mapping(self) -> Dict[str, Any]:
|
20
|
+
"""기본 Biped 본 매핑 구조 생성"""
|
21
|
+
mapping = {
|
22
|
+
"metadata": {
|
23
|
+
"UpAxis": "Z"
|
24
|
+
},
|
25
|
+
"biped_mapping": []
|
26
|
+
}
|
27
|
+
|
28
|
+
# COM (Center of Mass)
|
29
|
+
mapping["biped_mapping"].append({
|
30
|
+
"bip": "Com",
|
31
|
+
"fbx": "",
|
32
|
+
"biped_index": 13,
|
33
|
+
"biped_link": 1
|
34
|
+
})
|
35
|
+
|
36
|
+
# Pelvis
|
37
|
+
mapping["biped_mapping"].append({
|
38
|
+
"bip": "Pelvis",
|
39
|
+
"fbx": "",
|
40
|
+
"biped_index": 12,
|
41
|
+
"biped_link": 1
|
42
|
+
})
|
43
|
+
|
44
|
+
# Spine (최대 10개)
|
45
|
+
spine_names = ["Spine", "Spine1", "Spine2", "Spine3", "Spine4",
|
46
|
+
"Spine5", "Spine6", "Spine7", "Spine8", "Spine9"]
|
47
|
+
for i, name in enumerate(spine_names):
|
48
|
+
mapping["biped_mapping"].append({
|
49
|
+
"bip": name,
|
50
|
+
"fbx": "",
|
51
|
+
"biped_index": 9,
|
52
|
+
"biped_link": i + 1
|
53
|
+
})
|
54
|
+
|
55
|
+
# Neck (최대 25개)
|
56
|
+
neck_names = ["Neck"] + [f"Neck{i}" for i in range(1, 25)]
|
57
|
+
for i, name in enumerate(neck_names):
|
58
|
+
mapping["biped_mapping"].append({
|
59
|
+
"bip": name,
|
60
|
+
"fbx": "",
|
61
|
+
"biped_index": 17,
|
62
|
+
"biped_link": i + 1
|
63
|
+
})
|
64
|
+
|
65
|
+
# Head
|
66
|
+
mapping["biped_mapping"].append({
|
67
|
+
"bip": "Head",
|
68
|
+
"fbx": "",
|
69
|
+
"biped_index": 11,
|
70
|
+
"biped_link": 1
|
71
|
+
})
|
72
|
+
|
73
|
+
# Left Arm (4 links: Clavicle, UpperArm, Forearm, Hand)
|
74
|
+
left_arm_names = ["L Clavicle", "L UpperArm", "L Forearm", "L Hand"]
|
75
|
+
for i, name in enumerate(left_arm_names):
|
76
|
+
mapping["biped_mapping"].append({
|
77
|
+
"bip": name,
|
78
|
+
"fbx": "",
|
79
|
+
"biped_index": 1,
|
80
|
+
"biped_link": i + 1
|
81
|
+
})
|
82
|
+
|
83
|
+
# Right Arm (4 links: Clavicle, UpperArm, Forearm, Hand)
|
84
|
+
right_arm_names = ["R Clavicle", "R UpperArm", "R Forearm", "R Hand"]
|
85
|
+
for i, name in enumerate(right_arm_names):
|
86
|
+
mapping["biped_mapping"].append({
|
87
|
+
"bip": name,
|
88
|
+
"fbx": "",
|
89
|
+
"biped_index": 2,
|
90
|
+
"biped_link": i + 1
|
91
|
+
})
|
92
|
+
|
93
|
+
# Left Fingers (5 fingers x 3 links each = 15 links)
|
94
|
+
finger_names = ["Finger0", "Finger1", "Finger2", "Finger3", "Finger4"]
|
95
|
+
for finger_idx, finger_base in enumerate(finger_names):
|
96
|
+
for link_idx in range(3):
|
97
|
+
if link_idx == 0:
|
98
|
+
finger_name = f"L {finger_base}"
|
99
|
+
else:
|
100
|
+
finger_name = f"L {finger_base}{link_idx}"
|
101
|
+
|
102
|
+
mapping["biped_mapping"].append({
|
103
|
+
"bip": finger_name,
|
104
|
+
"fbx": "",
|
105
|
+
"biped_index": 3,
|
106
|
+
"biped_link": finger_idx * 3 + link_idx + 1
|
107
|
+
})
|
108
|
+
|
109
|
+
# Right Fingers (5 fingers x 3 links each = 15 links)
|
110
|
+
for finger_idx, finger_base in enumerate(finger_names):
|
111
|
+
for link_idx in range(3):
|
112
|
+
if link_idx == 0:
|
113
|
+
finger_name = f"R {finger_base}"
|
114
|
+
else:
|
115
|
+
finger_name = f"R {finger_base}{link_idx}"
|
116
|
+
|
117
|
+
mapping["biped_mapping"].append({
|
118
|
+
"bip": finger_name,
|
119
|
+
"fbx": "",
|
120
|
+
"biped_index": 4,
|
121
|
+
"biped_link": finger_idx * 3 + link_idx + 1
|
122
|
+
})
|
123
|
+
|
124
|
+
# Left Leg (4 links: Thigh, Calf, HorseLink, Foot)
|
125
|
+
left_leg_names = ["L Thigh", "L Calf", "L HorseLink", "L Foot"]
|
126
|
+
for i, name in enumerate(left_leg_names):
|
127
|
+
mapping["biped_mapping"].append({
|
128
|
+
"bip": name,
|
129
|
+
"fbx": "",
|
130
|
+
"biped_index": 5,
|
131
|
+
"biped_link": i + 1
|
132
|
+
})
|
133
|
+
|
134
|
+
# Right Leg (4 links: Thigh, Calf, HorseLink, Foot)
|
135
|
+
right_leg_names = ["R Thigh", "R Calf", "R HorseLink", "R Foot"]
|
136
|
+
for i, name in enumerate(right_leg_names):
|
137
|
+
mapping["biped_mapping"].append({
|
138
|
+
"bip": name,
|
139
|
+
"fbx": "",
|
140
|
+
"biped_index": 6,
|
141
|
+
"biped_link": i + 1
|
142
|
+
})
|
143
|
+
|
144
|
+
# Left Toes (5 toes x 3 links each = 15 links)
|
145
|
+
toe_names = ["Toe0", "Toe1", "Toe2", "Toe3", "Toe4"]
|
146
|
+
for toe_idx, toe_base in enumerate(toe_names):
|
147
|
+
for link_idx in range(3):
|
148
|
+
if link_idx == 0:
|
149
|
+
toe_name = f"L {toe_base}"
|
150
|
+
else:
|
151
|
+
toe_name = f"L {toe_base}{link_idx}"
|
152
|
+
|
153
|
+
mapping["biped_mapping"].append({
|
154
|
+
"bip": toe_name,
|
155
|
+
"fbx": "",
|
156
|
+
"biped_index": 7,
|
157
|
+
"biped_link": toe_idx * 3 + link_idx + 1
|
158
|
+
})
|
159
|
+
|
160
|
+
# Right Toes (5 toes x 3 links each = 15 links)
|
161
|
+
for toe_idx, toe_base in enumerate(toe_names):
|
162
|
+
for link_idx in range(3):
|
163
|
+
if link_idx == 0:
|
164
|
+
toe_name = f"R {toe_base}"
|
165
|
+
else:
|
166
|
+
toe_name = f"R {toe_base}{link_idx}"
|
167
|
+
|
168
|
+
mapping["biped_mapping"].append({
|
169
|
+
"bip": toe_name,
|
170
|
+
"fbx": "",
|
171
|
+
"biped_index": 8,
|
172
|
+
"biped_link": toe_idx * 3 + link_idx + 1
|
173
|
+
})
|
174
|
+
|
175
|
+
# Tail (최대 25개)
|
176
|
+
tail_names = ["Tail"] + [f"Tail{i}" for i in range(1, 25)]
|
177
|
+
for i, name in enumerate(tail_names):
|
178
|
+
mapping["biped_mapping"].append({
|
179
|
+
"bip": name,
|
180
|
+
"fbx": "",
|
181
|
+
"biped_index": 10,
|
182
|
+
"biped_link": i + 1
|
183
|
+
})
|
184
|
+
|
185
|
+
# Ponytail1 (최대 25개)
|
186
|
+
ponytail1_names = ["Ponytail1"] + [f"Ponytail1{i}" for i in range(1, 25)]
|
187
|
+
for i, name in enumerate(ponytail1_names):
|
188
|
+
mapping["biped_mapping"].append({
|
189
|
+
"bip": name,
|
190
|
+
"fbx": "",
|
191
|
+
"biped_index": 18,
|
192
|
+
"biped_link": i + 1
|
193
|
+
})
|
194
|
+
|
195
|
+
# Ponytail2 (최대 25개)
|
196
|
+
ponytail2_names = ["Ponytail2"] + [f"Ponytail2{i}" for i in range(1, 25)]
|
197
|
+
for i, name in enumerate(ponytail2_names):
|
198
|
+
mapping["biped_mapping"].append({
|
199
|
+
"bip": name,
|
200
|
+
"fbx": "",
|
201
|
+
"biped_index": 19,
|
202
|
+
"biped_link": i + 1
|
203
|
+
})
|
204
|
+
|
205
|
+
# Props
|
206
|
+
for i in range(1, 4):
|
207
|
+
mapping["biped_mapping"].append({
|
208
|
+
"bip": f"Prop{i}",
|
209
|
+
"fbx": "",
|
210
|
+
"biped_index": 19 + i,
|
211
|
+
"biped_link": 1
|
212
|
+
})
|
213
|
+
|
214
|
+
return mapping
|
215
|
+
|
216
|
+
def save_mapping(self, in_file_path: str) -> bool:
|
217
|
+
"""매핑 데이터를 JSON 파일로 저장
|
218
|
+
|
219
|
+
Args:
|
220
|
+
in_file_path: 저장할 파일 경로
|
221
|
+
|
222
|
+
Returns:
|
223
|
+
성공 여부
|
224
|
+
"""
|
225
|
+
try:
|
226
|
+
file_path = Path(in_file_path)
|
227
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
228
|
+
|
229
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
230
|
+
json.dump(self.mapping_data, f, indent=2, ensure_ascii=False)
|
231
|
+
|
232
|
+
return True
|
233
|
+
except Exception as e:
|
234
|
+
print(f"매핑 저장 실패: {e}")
|
235
|
+
return False
|
236
|
+
|
237
|
+
def load_mapping(self, in_file_path: str) -> bool:
|
238
|
+
"""JSON 파일에서 매핑 데이터 로드
|
239
|
+
|
240
|
+
Args:
|
241
|
+
in_file_path: 로드할 파일 경로
|
242
|
+
|
243
|
+
Returns:
|
244
|
+
성공 여부
|
245
|
+
"""
|
246
|
+
try:
|
247
|
+
file_path = Path(in_file_path)
|
248
|
+
if not file_path.exists():
|
249
|
+
print(f"파일이 존재하지 않습니다: {file_path}")
|
250
|
+
return False
|
251
|
+
|
252
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
253
|
+
self.mapping_data = json.load(f)
|
254
|
+
|
255
|
+
return True
|
256
|
+
except Exception as e:
|
257
|
+
print(f"매핑 로드 실패: {e}")
|
258
|
+
return False
|
259
|
+
|
260
|
+
def set_fbx_bone_name(self, in_bip_name: str, in_fbx_name: str) -> bool:
|
261
|
+
"""특정 Biped 본에 FBX 본 이름 설정
|
262
|
+
|
263
|
+
Args:
|
264
|
+
in_bip_name: Biped 본 이름
|
265
|
+
in_fbx_name: 대응하는 FBX 본 이름
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
성공 여부
|
269
|
+
"""
|
270
|
+
for mapping in self.mapping_data["biped_mapping"]:
|
271
|
+
if mapping["bip"] == in_bip_name:
|
272
|
+
mapping["fbx"] = in_fbx_name
|
273
|
+
return True
|
274
|
+
|
275
|
+
print(f"Biped 본을 찾을 수 없습니다: {in_bip_name}")
|
276
|
+
return False
|
277
|
+
|
278
|
+
def get_fbx_bone_name(self, in_bip_name: str) -> Optional[str]:
|
279
|
+
"""특정 Biped 본에 대응하는 FBX 본 이름 반환
|
280
|
+
|
281
|
+
Args:
|
282
|
+
in_bip_name: Biped 본 이름
|
283
|
+
|
284
|
+
Returns:
|
285
|
+
FBX 본 이름 또는 None
|
286
|
+
"""
|
287
|
+
for mapping in self.mapping_data["biped_mapping"]:
|
288
|
+
if mapping["bip"] == in_bip_name:
|
289
|
+
return mapping["fbx"] if mapping["fbx"] else None
|
290
|
+
|
291
|
+
return None
|
292
|
+
|
293
|
+
def get_biped_info(self, in_bip_name: str) -> Optional[Dict[str, Any]]:
|
294
|
+
"""특정 Biped 본의 정보 반환
|
295
|
+
|
296
|
+
Args:
|
297
|
+
in_bip_name: Biped 본 이름
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
Biped 본 정보 딕셔너리 또는 None
|
301
|
+
"""
|
302
|
+
for mapping in self.mapping_data["biped_mapping"]:
|
303
|
+
if mapping["bip"] == in_bip_name:
|
304
|
+
return mapping.copy()
|
305
|
+
|
306
|
+
return None
|
307
|
+
|
308
|
+
def get_all_mappings(self) -> List[Dict[str, Any]]:
|
309
|
+
"""모든 매핑 데이터 반환
|
310
|
+
|
311
|
+
Returns:
|
312
|
+
매핑 데이터 리스트
|
313
|
+
"""
|
314
|
+
return self.mapping_data["biped_mapping"].copy()
|
315
|
+
|
316
|
+
def clear_all_fbx_mappings(self):
|
317
|
+
"""모든 FBX 매핑을 빈 문자열로 초기화"""
|
318
|
+
for mapping in self.mapping_data["biped_mapping"]:
|
319
|
+
mapping["fbx"] = ""
|
320
|
+
|
321
|
+
|
322
|
+
class MocapRetargeter:
|
323
|
+
"""모션 캡처 리타겟팅 클래스"""
|
324
|
+
|
325
|
+
def __init__(self):
|
326
|
+
self.bone_mapping = BipedBoneMapping()
|
327
|
+
|
328
|
+
def analyze_fbx_structure(self, in_fbx_nodes: List[str]) -> Dict[str, Any]:
|
329
|
+
"""FBX 본 구조 분석
|
330
|
+
|
331
|
+
Args:
|
332
|
+
in_fbx_nodes: FBX 노드 이름 리스트
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
분석 결과 딕셔너리
|
336
|
+
"""
|
337
|
+
# TODO: FBX 본 구조 분석 로직 구현
|
338
|
+
pass
|
339
|
+
|
340
|
+
def create_biped(self, in_height: float = 170.0) -> bool:
|
341
|
+
"""Biped 생성
|
342
|
+
|
343
|
+
Args:
|
344
|
+
in_height: Biped 높이
|
345
|
+
|
346
|
+
Returns:
|
347
|
+
성공 여부
|
348
|
+
"""
|
349
|
+
# TODO: Biped 생성 로직 구현
|
350
|
+
pass
|
351
|
+
|
352
|
+
def resize_biped(self) -> bool:
|
353
|
+
"""Biped 크기 조정
|
354
|
+
|
355
|
+
Returns:
|
356
|
+
성공 여부
|
357
|
+
"""
|
358
|
+
# TODO: Biped 크기 조정 로직 구현
|
359
|
+
pass
|
360
|
+
|
361
|
+
def retarget_animation(self, in_start_frame: int, in_end_frame: int) -> bool:
|
362
|
+
"""애니메이션 리타겟팅
|
363
|
+
|
364
|
+
Args:
|
365
|
+
in_start_frame: 시작 프레임
|
366
|
+
in_end_frame: 종료 프레임
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
성공 여부
|
370
|
+
"""
|
371
|
+
# TODO: 애니메이션 리타겟팅 로직 구현
|
372
|
+
pass
|
373
|
+
|
374
|
+
|
375
|
+
# 기본 인스턴스 생성
|
376
|
+
default_bone_mapping = BipedBoneMapping()
|