pyjallib 0.1.0__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/bip.py ADDED
@@ -0,0 +1,508 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ Biped 모듈 - 3ds Max의 Biped 객체 관련 기능 제공
6
+ 원본 MAXScript의 bip.ms를 Python으로 변환하였으며, pymxs 모듈 기반으로 구현됨
7
+ """
8
+
9
+ from pymxs import runtime as rt
10
+
11
+ # Import necessary service classes for default initialization
12
+ from .anim import Anim
13
+ from .name import Name
14
+ from .bone import Bone
15
+
16
+
17
+ class Bip:
18
+ """
19
+ Biped 객체 관련 기능을 제공하는 클래스.
20
+ MAXScript의 _Bip 구조체 개념을 Python으로 재구현한 클래스이며,
21
+ 3ds Max의 기능들을 pymxs API를 통해 제어합니다.
22
+ """
23
+
24
+ def __init__(self, animService=None, nameService=None, boneService=None):
25
+ """
26
+ 클래스 초기화
27
+
28
+ Args:
29
+ animService: Anim 서비스 인스턴스 (제공되지 않으면 새로 생성)
30
+ nameService: Name 서비스 인스턴스 (제공되지 않으면 새로 생성)
31
+ boneService: Bone 서비스 인스턴스 (제공되지 않으면 새로 생성)
32
+ """
33
+ self.anim = animService if animService else Anim()
34
+ self.name = nameService if nameService else Name()
35
+ self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim) # Pass potentially new instances
36
+
37
+ def get_bips(self):
38
+ """
39
+ 씬 내의 모든 Biped_Object를 찾음
40
+
41
+ Returns:
42
+ Biped_Object 리스트
43
+ """
44
+ return [obj for obj in rt.objects if rt.isKindOf(obj, rt.Biped_Object)]
45
+
46
+ def get_coms_name(self):
47
+ """
48
+ 씬 내 모든 Biped COM(Center of Mass)의 이름 리스트 반환
49
+
50
+ Returns:
51
+ Biped COM 이름 리스트
52
+ """
53
+ bips = self.get_bips()
54
+ bipComsName = []
55
+
56
+ for obj in bips:
57
+ rootName = obj.controller.rootName
58
+ if rootName not in bipComsName:
59
+ bipComsName.append(rootName)
60
+
61
+ return bipComsName
62
+
63
+ def get_coms(self):
64
+ """
65
+ 씬 내 모든 Biped COM(Center of Mass) 객체 리스트 반환
66
+
67
+ Returns:
68
+ Biped COM 객체 리스트
69
+ """
70
+ bips = self.get_bips()
71
+ bipComs = []
72
+
73
+ for obj in bips:
74
+ rootNode = obj.controller.rootNode
75
+ if rootNode not in bipComs:
76
+ bipComs.append(rootNode)
77
+
78
+ return bipComs
79
+
80
+ def is_biped_object(self, inObj):
81
+ """
82
+ 객체가 Biped 관련 객체인지 확인
83
+
84
+ Args:
85
+ inObj: 확인할 객체
86
+
87
+ Returns:
88
+ Biped 관련 객체이면 True, 아니면 False
89
+ """
90
+ return (rt.classOf(inObj.controller) == rt.BipSlave_control or
91
+ rt.classOf(inObj.controller) == rt.Footsteps or
92
+ rt.classOf(inObj.controller) == rt.Vertical_Horizontal_Turn)
93
+
94
+ def get_com(self, inBip):
95
+ """
96
+ Biped 객체의 COM(Center of Mass) 반환
97
+
98
+ Args:
99
+ inBip: COM을 찾을 Biped 객체
100
+
101
+ Returns:
102
+ Biped의 COM 객체 또는 None
103
+ """
104
+ if self.is_biped_object(inBip):
105
+ return inBip.controller.rootNode
106
+ return None
107
+
108
+ def get_all(self, inBip):
109
+ """
110
+ Biped와 관련된 모든 객체 반환
111
+
112
+ Args:
113
+ inBip: 기준 Biped 객체
114
+
115
+ Returns:
116
+ Biped 관련 모든 객체 리스트
117
+ """
118
+ returnVal = []
119
+
120
+ if self.is_biped_object(inBip):
121
+ root = self.get_com(inBip)
122
+ allNodes = [root]
123
+ returnVal = [root]
124
+
125
+ for obj in allNodes:
126
+ for child in obj.children:
127
+ if child not in allNodes:
128
+ allNodes.append(child)
129
+ if self.is_biped_object(child) and child not in returnVal:
130
+ returnVal.append(child)
131
+
132
+ if obj.parent is not None:
133
+ if obj.parent not in allNodes:
134
+ allNodes.append(obj.parent)
135
+ if self.is_biped_object(obj.parent) and obj.parent not in returnVal:
136
+ returnVal.append(obj.parent)
137
+
138
+ return returnVal
139
+
140
+ def get_nodes(self, inBip):
141
+ """
142
+ Biped의 실제 노드만 반환 (더미나 Footstep은 제외)
143
+
144
+ Args:
145
+ inBip: 기준 Biped 객체
146
+
147
+ Returns:
148
+ Biped의 노드 객체 리스트
149
+ """
150
+ returnVal = []
151
+
152
+ if self.is_biped_object(inBip):
153
+ root = self.get_com(inBip)
154
+ allNodes = [root]
155
+ returnVal = [root]
156
+
157
+ for obj in allNodes:
158
+ for child in obj.children:
159
+ if rt.classOf(child) != rt.Dummy and rt.classOf(child.controller) != rt.Footsteps:
160
+ if child not in allNodes:
161
+ allNodes.append(child)
162
+ if self.is_biped_object(child) and child not in returnVal:
163
+ returnVal.append(child)
164
+
165
+ if obj.parent is not None:
166
+ if rt.classOf(obj.parent) != rt.Dummy and rt.classOf(obj.parent.controller) != rt.Footsteps:
167
+ if obj.parent not in allNodes:
168
+ allNodes.append(obj.parent)
169
+ if self.is_biped_object(obj.parent) and obj.parent not in returnVal:
170
+ returnVal.append(obj.parent)
171
+
172
+ return returnVal
173
+
174
+ def get_dummy_and_footstep(self, inBip):
175
+ """
176
+ Biped의 더미와 Footstep 객체만 반환
177
+
178
+ Args:
179
+ inBip: 기준 Biped 객체
180
+
181
+ Returns:
182
+ 더미와 Footstep 객체 리스트
183
+ """
184
+ returnVal = []
185
+
186
+ if self.is_biped_object(inBip):
187
+ bipArray = self.get_all(inBip)
188
+ returnVal = [item for item in bipArray if rt.classOf(item) == rt.Dummy or rt.classOf(item.controller) == rt.Footsteps]
189
+
190
+ return returnVal
191
+
192
+ def get_all_grouped_nodes(self, inBip):
193
+ """
194
+ Biped의 체인 이름으로 노드 반환
195
+
196
+ Args:
197
+ inBip: 기준 Biped 객체
198
+
199
+ Returns:
200
+ 해당 체인에 속하는 Biped 노드 리스트
201
+ """
202
+ # Define node categories with their corresponding index numbers
203
+ NODE_CATEGORIES = {
204
+ 1: "lArm",
205
+ 2: "rArm",
206
+ 3: "lFingers",
207
+ 4: "rFingers",
208
+ 5: "lLeg",
209
+ 6: "rLeg",
210
+ 7: "lToes",
211
+ 8: "rToes",
212
+ 9: "spine",
213
+ 10: "tail",
214
+ 11: "head",
215
+ 12: "pelvis",
216
+ 17: "neck",
217
+ 18: "pony1",
218
+ 19: "pony2",
219
+ 20: "prop1",
220
+ 21: "prop2",
221
+ 22: "prop3"
222
+ }
223
+
224
+ # Initialize node collections dictionary
225
+ nodes = {category: [] for category in NODE_CATEGORIES.values()}
226
+
227
+ com = inBip.controller.rootNode
228
+ if rt.classOf(inBip) != rt.Biped_Object:
229
+ return nodes
230
+
231
+ nn = rt.biped.maxNumNodes(com)
232
+ nl = rt.biped.maxNumLinks(com)
233
+
234
+ # Collect nodes by category
235
+ for i in range(1, nn + 1):
236
+ if i not in NODE_CATEGORIES:
237
+ continue
238
+
239
+ category = NODE_CATEGORIES[i]
240
+ anode = rt.biped.getNode(com, i)
241
+
242
+ if not anode:
243
+ continue
244
+
245
+ for j in range(1, nl + 1):
246
+ alink = rt.biped.getNode(com, i, link=j)
247
+ if alink:
248
+ nodes[category].append(alink)
249
+
250
+ return nodes
251
+
252
+ def get_grouped_nodes(self, inBip,inGroupName):
253
+ """
254
+ Biped의 체인 이름으로 노드 반환
255
+
256
+ Args:
257
+ inBip: 기준 Biped 객체
258
+ inGroupName: 체인 이름 (예: "lArm", "rLeg" 등)
259
+
260
+ Returns:
261
+ 해당 체인에 속하는 Biped 노드 리스트
262
+ """
263
+ nodes = self.get_all_grouped_nodes(inBip)
264
+
265
+ if inGroupName in nodes:
266
+ return nodes[inGroupName]
267
+
268
+ return []
269
+
270
+ def is_left_node(self, inNode):
271
+ """
272
+ 노드가 왼쪽인지 확인
273
+
274
+ Args:
275
+ inNode: 확인할 노드 객체
276
+
277
+ Returns:
278
+ 왼쪽 노드이면 True, 아니면 False
279
+ """
280
+ if rt.classOf(inNode) != rt.Biped_Object:
281
+ return False
282
+ com = self.get_com(inNode)
283
+ nodes = self.get_all_grouped_nodes(com)
284
+
285
+ categories = ["lArm", "lFingers", "lLeg", "lToes"]
286
+ for category in categories:
287
+ groupedNodes = nodes[category]
288
+ if inNode in groupedNodes:
289
+ return True
290
+
291
+ return False
292
+
293
+ def is_right_node(self, inNode):
294
+ """
295
+ 노드가 오른쪽인지 확인
296
+
297
+ Args:
298
+ inNode: 확인할 노드 객체
299
+
300
+ Returns:
301
+ 오른쪽 노드이면 True, 아니면 False
302
+ """
303
+ if rt.classOf(inNode) != rt.Biped_Object:
304
+ return False
305
+ com = self.get_com(inNode)
306
+ nodes = self.get_all_grouped_nodes(com)
307
+
308
+ categories = ["rArm", "rFingers", "rLeg", "rToes"]
309
+ for category in categories:
310
+ groupedNodes = nodes[category]
311
+ if inNode in groupedNodes:
312
+ return True
313
+
314
+ return False
315
+
316
+ def get_nodes_by_skeleton_order(self, inBip):
317
+ """
318
+ 스켈레톤 순서대로 Biped 노드 반환
319
+
320
+ Args:
321
+ inBip: 기준 Biped 객체
322
+
323
+ Returns:
324
+ 순서대로 정렬된 Biped 노드 리스트
325
+ """
326
+ nodes = self.get_all_grouped_nodes(inBip)
327
+
328
+ # Define the order of categories in final array
329
+ ORDER = [
330
+ "head", "pelvis", "lArm", "lFingers", "lLeg", "lToes", "neck",
331
+ "rArm", "rFingers", "rLeg", "rToes", "spine", "tail",
332
+ "pony1", "pony2", "prop1", "prop2", "prop3"
333
+ ]
334
+
335
+ # Build final array in the desired order
336
+ bipNodeArray = []
337
+ for category in ORDER:
338
+ bipNodeArray.extend(nodes[category])
339
+
340
+ return bipNodeArray
341
+
342
+ def load_bip_file(self, inBipRoot, inFile):
343
+ """
344
+ Biped BIP 파일 로드
345
+
346
+ Args:
347
+ inBipRoot: 로드 대상 Biped 루트 노드
348
+ inFile: 로드할 BIP 파일 경로
349
+ """
350
+ bipNodeArray = self.get_all(inBipRoot)
351
+
352
+ inBipRoot.controller.figureMode = False
353
+ rt.biped.loadBipFile(inBipRoot.controller, inFile)
354
+ inBipRoot.controller.figureMode = True
355
+ inBipRoot.controller.figureMode = False
356
+
357
+ keyRange = []
358
+ for i in range(1, len(bipNodeArray)):
359
+ if bipNodeArray[i].controller.keys.count != 0 and bipNodeArray[i].controller.keys.count != -1:
360
+ keyTime = bipNodeArray[i].controller.keys[bipNodeArray[i].controller.keys.count - 1].time
361
+ if keyTime not in keyRange:
362
+ keyRange.append(keyTime)
363
+
364
+ if keyRange and max(keyRange) != 0:
365
+ rt.animationRange = rt.interval(0, max(keyRange))
366
+ rt.sliderTime = 0
367
+
368
+ def load_fig_file(self, inBipRoot, inFile):
369
+ """
370
+ Biped FIG 파일 로드
371
+
372
+ Args:
373
+ inBipRoot: 로드 대상 Biped 루트 노드
374
+ inFile: 로드할 FIG 파일 경로
375
+ """
376
+ inBipRoot.controller.figureMode = False
377
+ inBipRoot.controller.figureMode = True
378
+ rt.biped.LoadFigFile(inBipRoot.controller, inFile)
379
+ inBipRoot.controller.figureMode = False
380
+
381
+ def save_fig_file(self, inBipRoot, fileName):
382
+ """
383
+ Biped FIG 파일 저장
384
+
385
+ Args:
386
+ inBipRoot: 저장 대상 Biped 루트 노드
387
+ fileName: 저장할 FIG 파일 경로
388
+ """
389
+ inBipRoot.controller.figureMode = False
390
+ inBipRoot.controller.figureMode = True
391
+ rt.biped.saveFigFile(inBipRoot.controller, fileName)
392
+
393
+ def turn_on_figure_mode(self, inBipRoot):
394
+ """
395
+ Biped Figure 모드 켜기
396
+
397
+ Args:
398
+ inBipRoot: 대상 Biped 객체
399
+ """
400
+ inBipRoot.controller.figureMode = True
401
+
402
+ def turn_off_figure_mode(self, inBipRoot):
403
+ """
404
+ Biped Figure 모드 끄기
405
+
406
+ Args:
407
+ inBipRoot: 대상 Biped 객체
408
+ """
409
+ inBipRoot.controller.figureMode = False
410
+
411
+ def delete_copy_collection(self, inBipRoot, inName):
412
+ """
413
+ Biped 복사 컬렉션 삭제
414
+
415
+ Args:
416
+ inBipRoot: 대상 Biped 객체
417
+ inName: 삭제할 컬렉션 이름
418
+ """
419
+ if self.is_biped_object(inBipRoot):
420
+ colNum = rt.biped.numCopyCollections(inBipRoot.controller)
421
+ if colNum > 0:
422
+ for i in range(1, colNum + 1):
423
+ if rt.biped.getCopyCollection(inBipRoot.controller, i).name == inName:
424
+ rt.biped.deleteCopyCollection(inBipRoot.controller, i)
425
+ break
426
+
427
+ def delete_all_copy_collection(self, inBipRoot):
428
+ """
429
+ Biped 모든 복사 컬렉션 삭제
430
+
431
+ Args:
432
+ inBipRoot: 대상 Biped 객체
433
+ """
434
+ if self.is_biped_object(inBipRoot):
435
+ colNum = rt.biped.numCopyCollections(inBipRoot.controller)
436
+ if colNum > 0:
437
+ rt.biped.deleteAllCopyCollections(inBipRoot.controller)
438
+
439
+ def link_base_skeleton(self):
440
+ """
441
+ 기본 스켈레톤 링크 (Biped와 일반 뼈대 연결)
442
+ """
443
+ rt.setWaitCursor()
444
+ skinBoneBaseName = "b"
445
+
446
+ bipSkel = self.get_bips()
447
+ baseSkel = [None] * len(bipSkel)
448
+
449
+ for i in range(len(bipSkel)):
450
+ baseSkeletonName = self.name.replace_base(bipSkel[i].name, skinBoneBaseName)
451
+ baseSkeletonName = self.name.replace_filteringChar(baseSkeletonName, "_")
452
+ baseSkelObj = rt.getNodeByName(baseSkeletonName)
453
+ if rt.isValidObj(baseSkelObj):
454
+ baseSkel[i] = baseSkelObj
455
+
456
+ self.anim.save_xform(bipSkel)
457
+ self.anim.set_xform(bipSkel)
458
+
459
+ self.anim.save_xform(baseSkel)
460
+ self.anim.set_xform(baseSkel)
461
+
462
+ for i in range(len(baseSkel)):
463
+ if baseSkel[i] is not None:
464
+ baseSkel[i].scale.controller = rt.scaleXYZ()
465
+ baseSkel[i].controller = rt.link_constraint()
466
+
467
+ self.anim.set_xform([baseSkel[i]], space="World")
468
+ baseSkel[i].transform.controller.AddTarget(bipSkel[i], 0)
469
+
470
+ for i in range(len(baseSkel)):
471
+ if baseSkel[i] is not None:
472
+ baseSkel[i].boneEnable = True
473
+
474
+ rt.setArrowCursor()
475
+
476
+ def unlink_base_skeleton(self):
477
+ """
478
+ 기본 스켈레톤 링크 해제
479
+ """
480
+ rt.setWaitCursor()
481
+ skinBoneBaseName = "b"
482
+
483
+ bipSkel = self.get_bips()
484
+ baseSkel = [None] * len(bipSkel)
485
+
486
+ for i in range(len(bipSkel)):
487
+ baseSkeletonName = self.name.replace_base(bipSkel[i].name, skinBoneBaseName)
488
+ baseSkeletonName = self.name.replace_filteringChar(baseSkeletonName, "_")
489
+ baseSkelObj = rt.getNodeByName(baseSkeletonName)
490
+ if rt.isValidObj(baseSkelObj):
491
+ baseSkel[i] = baseSkelObj
492
+
493
+ self.anim.save_xform(bipSkel)
494
+ self.anim.set_xform(bipSkel)
495
+
496
+ self.anim.save_xform(baseSkel)
497
+ self.anim.set_xform(baseSkel)
498
+
499
+ for i in range(len(baseSkel)):
500
+ if baseSkel[i] is not None:
501
+ baseSkel[i].controller = rt.prs()
502
+ self.anim.set_xform([baseSkel[i]], space="World")
503
+
504
+ for i in range(len(baseSkel)):
505
+ if baseSkel[i] is not None:
506
+ baseSkel[i].boneEnable = True
507
+
508
+ rt.setArrowCursor()