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/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()