pyjallib 0.1.6__tar.gz → 0.1.8__tar.gz

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.
Files changed (58) hide show
  1. {pyjallib-0.1.6 → pyjallib-0.1.8}/PKG-INFO +1 -1
  2. {pyjallib-0.1.6 → pyjallib-0.1.8}/pyproject.toml +1 -1
  3. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/__init__.py +1 -1
  4. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json +2 -2
  5. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/__init__.py +8 -1
  6. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/autoClavicle.py +47 -0
  7. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/constraint.py +39 -2
  8. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/header.py +8 -0
  9. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/helper.py +6 -0
  10. pyjallib-0.1.8/src/pyjallib/max/morph.py +406 -0
  11. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/name.py +5 -29
  12. pyjallib-0.1.8/src/pyjallib/max/ui/Container.py +153 -0
  13. pyjallib-0.1.8/src/pyjallib/max/volumePreserveBone.py +171 -0
  14. pyjallib-0.1.8/tests/morph.ms +217 -0
  15. pyjallib-0.1.8/tests/volumePreserveBoneTest.py +35 -0
  16. {pyjallib-0.1.6 → pyjallib-0.1.8}/uv.lock +1 -1
  17. {pyjallib-0.1.6 → pyjallib-0.1.8}/.gitignore +0 -0
  18. {pyjallib-0.1.6 → pyjallib-0.1.8}/.python-version +0 -0
  19. {pyjallib-0.1.6 → pyjallib-0.1.8}/README.md +0 -0
  20. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/.nojekyll +0 -0
  21. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/index.html +0 -0
  22. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/max.html +0 -0
  23. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/namePart.html +0 -0
  24. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/nameToPath.html +0 -0
  25. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/naming.html +0 -0
  26. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/namingConfig.html +0 -0
  27. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/perforce.html +0 -0
  28. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/pymxs.html +0 -0
  29. {pyjallib-0.1.6 → pyjallib-0.1.8}/docs/reloadModules.html +0 -0
  30. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/ConfigFiles/namingConfig.json +0 -0
  31. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/align.py +0 -0
  32. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/anim.py +0 -0
  33. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/bip.py +0 -0
  34. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/bone.py +0 -0
  35. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/groinBone.py +0 -0
  36. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/layer.py +0 -0
  37. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/link.py +0 -0
  38. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_align.py +0 -0
  39. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_bone.py +0 -0
  40. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_constraint.py +0 -0
  41. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_helper.py +0 -0
  42. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_link.py +0 -0
  43. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/macro/jal_macro_select.py +0 -0
  44. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/mirror.py +0 -0
  45. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/select.py +0 -0
  46. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/skin.py +0 -0
  47. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/max/twistBone.py +0 -0
  48. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/namePart.py +0 -0
  49. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/nameToPath.py +0 -0
  50. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/naming.py +0 -0
  51. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/namingConfig.py +0 -0
  52. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/perforce.py +0 -0
  53. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/py.typed +0 -0
  54. {pyjallib-0.1.6 → pyjallib-0.1.8}/src/pyjallib/reloadModules.py +0 -0
  55. {pyjallib-0.1.6 → pyjallib-0.1.8}/tests/autoclavicle.ms +0 -0
  56. {pyjallib-0.1.6 → pyjallib-0.1.8}/tests/autoclavicleTest.py +0 -0
  57. {pyjallib-0.1.6 → pyjallib-0.1.8}/tests/globalVarTest.py +0 -0
  58. {pyjallib-0.1.6 → pyjallib-0.1.8}/tests/moduleImportTest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyjallib
3
- Version: 0.1.6
3
+ Version: 0.1.8
4
4
  Summary: A utility library for 3D game character development pipelines.
5
5
  Author-email: Dongseok Kim <jalnagakds@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyjallib"
3
- version = "0.1.6"
3
+ version = "0.1.8"
4
4
  description = "A utility library for 3D game character development pipelines."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -6,7 +6,7 @@ pyjallib Package
6
6
  Python library for game character development pipeline.
7
7
  """
8
8
 
9
- __version__ = '0.1.6'
9
+ __version__ = '0.1.8'
10
10
 
11
11
  # reload_modules 함수를 패키지 레벨에서 사용 가능하게 함
12
12
  from .namePart import NamePart, NamePartType
@@ -34,8 +34,8 @@
34
34
  {
35
35
  "name": "Type",
36
36
  "predefinedValues": [
37
- "P",
38
37
  "Dum",
38
+ "P",
39
39
  "Exp",
40
40
  "IK",
41
41
  "T",
@@ -57,8 +57,8 @@
57
57
  ],
58
58
  "type": "PREFIX",
59
59
  "descriptions": [
60
- "Parent",
61
60
  "Dummy",
61
+ "Parent",
62
62
  "ExposeTM",
63
63
  "IK",
64
64
  "Target",
@@ -24,10 +24,14 @@ from .link import Link
24
24
 
25
25
  from .bip import Bip
26
26
  from .skin import Skin
27
+ from .morph import Morph
27
28
 
28
29
  from .twistBone import TwistBone
29
30
  from .groinBone import GroinBone
30
31
  from .autoClavicle import AutoClavicle
32
+ from .volumePreserveBone import VolumePreserveBone
33
+
34
+ from .ui.Container import Container
31
35
 
32
36
  # 모듈 내보내기
33
37
  __all__ = [
@@ -44,7 +48,10 @@ __all__ = [
44
48
  'Link',
45
49
  'Bip',
46
50
  'Skin',
51
+ 'Morph',
47
52
  'TwistBone',
48
53
  'GroinBone',
49
- 'AutoClavicle'
54
+ 'AutoClavicle',
55
+ 'VolumePreserveBone',
56
+ 'Container'
50
57
  ]
@@ -120,3 +120,50 @@ class AutoClavicle:
120
120
  ikGoal.parent = autoClavicleRotHelper
121
121
 
122
122
  return autoClavicleBones
123
+
124
+ def get_bones(self, inClavicle, inUpperArm):
125
+ """
126
+ 자동 쇄골 뼈를 가져옵니다.
127
+
128
+ Args:
129
+ inClavicle: 쇄골 뼈 객체
130
+ inUpperArm: 상완 뼈 객체
131
+
132
+ Returns:
133
+ 자동 쇄골 뼈대 배열
134
+ """
135
+ clavicleChildren = [item for item in self.bone.get_every_children(inClavicle) if rt.classOf(item) == rt.BoneGeometry]
136
+ upperArmChildren = [item for item in self.bone.get_every_children(inUpperArm) if rt.classOf(item) == rt.BoneGeometry]
137
+ returnVal = []
138
+ for item in clavicleChildren:
139
+ if item not in returnVal:
140
+ returnVal.append(item)
141
+ for item in upperArmChildren:
142
+ if item not in returnVal:
143
+ returnVal.append(item)
144
+
145
+ return returnVal
146
+
147
+ def get_helpers(self, inClavicle, inUpperArm):
148
+ """
149
+ 자동 쇄골 헬퍼를 가져옵니다.
150
+
151
+ Args:
152
+ inClavicle: 쇄골 뼈 객체
153
+ inUpperArm: 상완 뼈 객체
154
+
155
+ Returns:
156
+ 자동 쇄골 헬퍼 배열
157
+ """
158
+ clavicleChildren = [item for item in self.bone.get_every_children(inClavicle) if rt.classOf(item) == rt.Point]
159
+ upperArmChildren = [item for item in self.bone.get_every_children(inUpperArm) if rt.classOf(item) == rt.Point]
160
+ returnVal = []
161
+ for item in clavicleChildren:
162
+ if item not in returnVal:
163
+ returnVal.append(item)
164
+ for item in upperArmChildren:
165
+ if item not in returnVal:
166
+ returnVal.append(item)
167
+
168
+ return returnVal
169
+
@@ -212,6 +212,8 @@ class Constraint:
212
212
  """
213
213
  for item in inTargetArray:
214
214
  self.assign_pos_const(inObj, item, keepInit=keepInit)
215
+
216
+ return self.get_pos_const(inObj)
215
217
 
216
218
  def add_target_to_pos_const(self, inObj, inTarget, inWeight):
217
219
  """
@@ -251,8 +253,36 @@ class Constraint:
251
253
  posList = self.assign_pos_list(inObj)
252
254
 
253
255
  # Position_XYZ 컨트롤러 할당
254
- rt.setPropertyController(posList, "Available", rt.Position_XYZ())
256
+ posXYZ = rt.Position_XYZ()
257
+ rt.setPropertyController(posList, "Available", posXYZ)
258
+ posList.setActive(posList.count)
259
+
260
+ return posXYZ
261
+
262
+ def assign_pos_script_controller(self, inObj):
263
+ """
264
+ 객체에 스크립트 기반 위치 컨트롤러를 할당.
265
+
266
+ Args:
267
+ inObj: 컨트롤러를 할당할 객체
268
+
269
+ Returns:
270
+ None
271
+ """
272
+ # 위치 컨트롤러가 리스트 형태가 아니면 변환
273
+ pos_controller = rt.getPropertyController(inObj.controller, "Position")
274
+ if rt.classOf(pos_controller) != rt.Position_list:
275
+ rt.setPropertyController(inObj.controller, "Position", rt.Position_list())
276
+
277
+ # 위치 리스트 컨트롤러 가져오기
278
+ posList = self.assign_pos_list(inObj)
279
+
280
+ # 스크립트 기반 위치 컨트롤러 할당
281
+ scriptPos = rt.Position_Script()
282
+ rt.setPropertyController(posList, "Available", scriptPos)
255
283
  posList.setActive(posList.count)
284
+
285
+ return scriptPos
256
286
 
257
287
  def get_rot_list_controller(self, inObj):
258
288
  """
@@ -392,6 +422,8 @@ class Constraint:
392
422
  """
393
423
  for item in inTargetArray:
394
424
  self.assign_rot_const(inObj, item, keepInit=keepInit)
425
+
426
+ return self.get_rot_const(inObj)
395
427
 
396
428
  def add_target_to_rot_const(self, inObj, inTarget, inWeight):
397
429
  """
@@ -431,8 +463,11 @@ class Constraint:
431
463
  rotList = self.assign_rot_list(inObj)
432
464
 
433
465
  # Euler_XYZ 컨트롤러 할당
434
- rt.setPropertyController(rotList, "Available", rt.Euler_XYZ())
466
+ eulerXYZ = rt.Euler_XYZ()
467
+ rt.setPropertyController(rotList, "Available", eulerXYZ)
435
468
  rotList.setActive(rotList.count)
469
+
470
+ return eulerXYZ
436
471
 
437
472
  def get_lookat(self, inObj):
438
473
  """
@@ -528,6 +563,8 @@ class Constraint:
528
563
  """
529
564
  for item in inTargetArray:
530
565
  self.assign_lookat(inObj, item, keepInit=keepInit)
566
+
567
+ return self.get_lookat(inObj)
531
568
 
532
569
  def assign_lookat_flipless(self, inObj, inTarget):
533
570
  """
@@ -27,6 +27,9 @@ from .skin import Skin
27
27
  from .twistBone import TwistBone
28
28
  from .groinBone import GroinBone
29
29
  from .autoClavicle import AutoClavicle
30
+ from .volumePreserveBone import VolumePreserveBone
31
+
32
+ from .morph import Morph
30
33
 
31
34
  class Header:
32
35
  """
@@ -68,6 +71,11 @@ class Header:
68
71
  self.twistBone = TwistBone(nameService=self.name, animService=self.anim, constService=self.constraint, bipService=self.bip, boneService=self.bone)
69
72
  self.groinBone = GroinBone(nameService=self.name, animService=self.anim, helperService=self.helper, constService=self.constraint, bipService=self.bip, boneService=self.bone, twistBoneService=self.twistBone)
70
73
  self.autoClavicle = AutoClavicle(nameService=self.name, animService=self.anim, helperService=self.helper, boneService=self.bone, constraintService=self.constraint, bipService=self.bip)
74
+ self.volumePreserveBone = VolumePreserveBone(nameService=self.name, animService=self.anim, constService=self.constraint, boneService=self.bone, helperService=self.helper)
75
+
76
+ self.morph = Morph()
77
+
78
+ self.tools = []
71
79
 
72
80
  # 모듈 레벨에서 전역 인스턴스 생성
73
81
  jal = Header.get_instance()
@@ -264,6 +264,7 @@ class Helper:
264
264
  부모 헬퍼 생성
265
265
  """
266
266
  # 선택된 객체가 있는 경우에만 처리
267
+ returnHelpers = []
267
268
  if rt.selection.count > 0:
268
269
  selArray = rt.getCurrentSelection()
269
270
 
@@ -297,6 +298,11 @@ class Helper:
297
298
  # 부모 헬퍼로 이름 변경
298
299
  finalName = self.name.replace_name_part("Type", genPoint.name, self.get_name_by_type("Parent"))
299
300
  rt.setProperty(genPoint, "name", finalName)
301
+
302
+ returnHelpers.append(genPoint)
303
+
304
+ return returnHelpers
305
+
300
306
 
301
307
  def create_exp_tm(self):
302
308
  """
@@ -0,0 +1,406 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 모프(Morph) 모듈 - 3ds Max용 모프 타겟 관련 기능 제공
6
+ 원본 MAXScript의 morph.ms를 Python으로 변환하였으며, pymxs 모듈 기반으로 구현됨
7
+ """
8
+
9
+ from dataclasses import dataclass
10
+ from pymxs import runtime as rt
11
+
12
+
13
+ @dataclass
14
+ class MorphChannel:
15
+ """
16
+ 모프 채널 정보를 저장하는 데이터 클래스
17
+ """
18
+ index: int = 0
19
+ name: str = ""
20
+ hasData: bool = False
21
+
22
+
23
+ class Morph:
24
+ """
25
+ 모프(Morph) 관련 기능을 제공하는 클래스.
26
+ MAXScript의 _Morph 구조체 개념을 Python으로 재구현한 클래스이며,
27
+ 3ds Max의 기능들을 pymxs API를 통해 제어합니다.
28
+ """
29
+
30
+ def __init__(self):
31
+ """클래스 초기화"""
32
+ self.channelMaxViewNum = 100
33
+
34
+ def get_modifier_index(self, inObj):
35
+ """
36
+ 객체에서 Morpher 모디파이어의 인덱스를 찾음
37
+
38
+ Args:
39
+ inObj: 검색할 객체
40
+
41
+ Returns:
42
+ Morpher 모디파이어의 인덱스 (없으면 0)
43
+ """
44
+ returnVal = 0
45
+ if len(inObj.modifiers) > 0:
46
+ for i in range(len(inObj.modifiers)):
47
+ if rt.classOf(inObj.modifiers[i]) == rt.Morpher:
48
+ returnVal = i + 1 # MaxScript는 1부터 시작하므로 +1 추가
49
+
50
+ return returnVal
51
+
52
+ def get_modifier(self, inObj):
53
+ """
54
+ 객체에서 Morpher 모디파이어를 찾음
55
+
56
+ Args:
57
+ inObj: 검색할 객체
58
+
59
+ Returns:
60
+ Morpher 모디파이어 (없으면 None)
61
+ """
62
+ returnVal = None
63
+ modIndex = self.get_modifier_index(inObj)
64
+ if modIndex > 0:
65
+ returnVal = inObj.modifiers[modIndex - 1] # Python 인덱스는 0부터 시작하므로 -1 조정
66
+
67
+ return returnVal
68
+
69
+ def get_channel_num(self, inObj):
70
+ """
71
+ 객체의 Morpher에 있는 채널 수를 반환
72
+
73
+ Args:
74
+ inObj: 검색할 객체
75
+
76
+ Returns:
77
+ 모프 채널 수
78
+ """
79
+ returnVal = 0
80
+ morphMod = self.get_modifier(inObj)
81
+ if morphMod is not None:
82
+ morphChannelExistance = True
83
+ morphChannelCounter = 0
84
+
85
+ while morphChannelExistance:
86
+ for i in range(morphChannelCounter + 1, morphChannelCounter + self.channelMaxViewNum + 1):
87
+ if not rt.WM3_MC_HasData(morphMod, i):
88
+ returnVal = i - 1
89
+ morphChannelExistance = False
90
+ break
91
+
92
+ morphChannelCounter += self.channelMaxViewNum
93
+
94
+ return returnVal
95
+
96
+ def get_all_channel_info(self, inObj):
97
+ """
98
+ 객체의 모든 모프 채널 정보를 가져옴
99
+
100
+ Args:
101
+ inObj: 검색할 객체
102
+
103
+ Returns:
104
+ MorphChannel 객체의 리스트
105
+ """
106
+ returnVal = []
107
+ morphMod = self.get_modifier(inObj)
108
+
109
+ if morphMod is not None:
110
+ channelNum = self.get_channel_num(inObj)
111
+ if channelNum > 0:
112
+ for i in range(1, channelNum + 1):
113
+ tempChannel = MorphChannel()
114
+ tempChannel.index = i
115
+ tempChannel.hasData = rt.WM3_MC_HasData(morphMod, i)
116
+ tempChannel.name = rt.WM3_MC_GetName(morphMod, i)
117
+ returnVal.append(tempChannel)
118
+
119
+ return returnVal
120
+
121
+ def add_target(self, inObj, inTarget, inIndex):
122
+ """
123
+ 특정 인덱스에 모프 타겟 추가
124
+
125
+ Args:
126
+ inObj: 모프를 적용할 객체
127
+ inTarget: 타겟 객체
128
+ inIndex: 채널 인덱스
129
+
130
+ Returns:
131
+ 성공 여부 (True/False)
132
+ """
133
+ returnVal = False
134
+ morphMod = self.get_modifier(inObj)
135
+
136
+ if morphMod is not None:
137
+ rt.WM3_MC_BuildFromNode(morphMod, inIndex, inTarget)
138
+ returnVal = rt.WM3_MC_HasData(morphMod, inIndex)
139
+
140
+ return returnVal
141
+
142
+ def add_targets(self, inObj, inTargetArray):
143
+ """
144
+ 여러 타겟 객체를 순서대로 모프 채널에 추가
145
+
146
+ Args:
147
+ inObj: 모프를 적용할 객체
148
+ inTargetArray: 타겟 객체 배열
149
+ """
150
+ morphMod = self.get_modifier(inObj)
151
+
152
+ if morphMod is not None:
153
+ for i in range(len(inTargetArray)):
154
+ rt.WM3_MC_BuildFromNode(morphMod, i + 1, inTargetArray[i])
155
+
156
+ def get_all_channel_name(self, inObj):
157
+ """
158
+ 객체의 모든 모프 채널 이름을 가져옴
159
+
160
+ Args:
161
+ inObj: 검색할 객체
162
+
163
+ Returns:
164
+ 채널 이름 리스트
165
+ """
166
+ returnVal = []
167
+ channelArray = self.get_all_channel_info(inObj)
168
+
169
+ if len(channelArray) > 0:
170
+ returnVal = [item.name for item in channelArray]
171
+
172
+ return returnVal
173
+
174
+ def get_channel_name(self, inObj, inIndex):
175
+ """
176
+ 특정 인덱스의 모프 채널 이름을 가져옴
177
+
178
+ Args:
179
+ inObj: 검색할 객체
180
+ inIndex: 채널 인덱스
181
+
182
+ Returns:
183
+ 채널 이름 (없으면 빈 문자열)
184
+ """
185
+ returnVal = ""
186
+ channelArray = self.get_all_channel_info(inObj)
187
+
188
+ try:
189
+ if len(channelArray) > 0:
190
+ returnVal = channelArray[inIndex - 1].name
191
+ except:
192
+ returnVal = ""
193
+
194
+ return returnVal
195
+
196
+ def get_channelIndex(self, inObj, inName):
197
+ """
198
+ 채널 이름으로 모프 채널 인덱스를 가져옴
199
+
200
+ Args:
201
+ inObj: 검색할 객체
202
+ inName: 채널 이름
203
+
204
+ Returns:
205
+ 채널 인덱스 (없으면 0)
206
+ """
207
+ returnVal = 0
208
+ channelArray = self.get_all_channel_info(inObj)
209
+
210
+ if len(channelArray) > 0:
211
+ for item in channelArray:
212
+ if item.name == inName:
213
+ returnVal = item.index
214
+ break
215
+
216
+ return returnVal
217
+
218
+ def get_channel_value_by_name(self, inObj, inName):
219
+ """
220
+ 채널 이름으로 모프 채널 값을 가져옴
221
+
222
+ Args:
223
+ inObj: 검색할 객체
224
+ inName: 채널 이름
225
+
226
+ Returns:
227
+ 채널 값 (0.0 ~ 100.0)
228
+ """
229
+ returnVal = 0.0
230
+ channelIndex = self.get_channelIndex(inObj, inName)
231
+ morphMod = self.get_modifier(inObj)
232
+
233
+ if channelIndex > 0:
234
+ try:
235
+ returnVal = rt.WM3_MC_GetValue(morphMod, channelIndex)
236
+ except:
237
+ returnVal = 0.0
238
+
239
+ return returnVal
240
+
241
+ def get_channel_value_by_index(self, inObj, inIndex):
242
+ """
243
+ 인덱스로 모프 채널 값을 가져옴
244
+
245
+ Args:
246
+ inObj: 검색할 객체
247
+ inIndex: 채널 인덱스
248
+
249
+ Returns:
250
+ 채널 값 (0.0 ~ 100.0)
251
+ """
252
+ returnVal = 0
253
+ morphMod = self.get_modifier(inObj)
254
+
255
+ if morphMod is not None:
256
+ try:
257
+ returnVal = rt.WM3_MC_GetValue(morphMod, inIndex)
258
+ except:
259
+ returnVal = 0
260
+
261
+ return returnVal
262
+
263
+ def set_channel_value_by_name(self, inObj, inName, inVal):
264
+ """
265
+ 채널 이름으로 모프 채널 값을 설정
266
+
267
+ Args:
268
+ inObj: 모프를 적용할 객체
269
+ inName: 채널 이름
270
+ inVal: 설정할 값 (0.0 ~ 100.0)
271
+
272
+ Returns:
273
+ 성공 여부 (True/False)
274
+ """
275
+ returnVal = False
276
+ morphMod = self.get_modifier(inObj)
277
+ channelIndex = self.get_channelIndex(inObj, inName)
278
+
279
+ if channelIndex > 0:
280
+ try:
281
+ rt.WM3_MC_SetValue(morphMod, channelIndex, inVal)
282
+ returnVal = True
283
+ except:
284
+ returnVal = False
285
+
286
+ return returnVal
287
+
288
+ def set_channel_value_by_index(self, inObj, inIndex, inVal):
289
+ """
290
+ 인덱스로 모프 채널 값을 설정
291
+
292
+ Args:
293
+ inObj: 모프를 적용할 객체
294
+ inIndex: 채널 인덱스
295
+ inVal: 설정할 값 (0.0 ~ 100.0)
296
+
297
+ Returns:
298
+ 성공 여부 (True/False)
299
+ """
300
+ returnVal = False
301
+ morphMod = self.get_modifier(inObj)
302
+
303
+ if morphMod is not None:
304
+ try:
305
+ rt.WM3_MC_SetValue(morphMod, inIndex, inVal)
306
+ returnVal = True
307
+ except:
308
+ returnVal = False
309
+
310
+ return returnVal
311
+
312
+ def set_channel_name_by_name(self, inObj, inTargetName, inNewName):
313
+ """
314
+ 채널 이름을 이름으로 검색하여 변경
315
+
316
+ Args:
317
+ inObj: 모프를 적용할 객체
318
+ inTargetName: 대상 채널의 현재 이름
319
+ inNewName: 설정할 새 이름
320
+
321
+ Returns:
322
+ 성공 여부 (True/False)
323
+ """
324
+ returnVal = False
325
+ channelIndex = self.get_channelIndex(inObj, inTargetName)
326
+ morphMod = self.get_modifier(inObj)
327
+
328
+ if channelIndex > 0:
329
+ rt.WM3_MC_SetName(morphMod, channelIndex, inNewName)
330
+ returnVal = True
331
+
332
+ return returnVal
333
+
334
+ def set_channel_name_by_index(self, inObj, inIndex, inName):
335
+ """
336
+ 채널 이름을 인덱스로 검색하여 변경
337
+
338
+ Args:
339
+ inObj: 모프를 적용할 객체
340
+ inIndex: 대상 채널 인덱스
341
+ inName: 설정할 이름
342
+
343
+ Returns:
344
+ 성공 여부 (True/False)
345
+ """
346
+ returnVal = False
347
+ morphMod = self.get_modifier(inObj)
348
+
349
+ if morphMod is not None:
350
+ try:
351
+ rt.WM3_MC_SetName(morphMod, inIndex, inName)
352
+ returnVal = True
353
+ except:
354
+ returnVal = False
355
+
356
+ return returnVal
357
+
358
+ def reset_all_channel_value(self, inObj):
359
+ """
360
+ 모든 모프 채널 값을 0으로 리셋
361
+
362
+ Args:
363
+ inObj: 리셋할 객체
364
+ """
365
+ totalChannelNum = self.get_channel_num(inObj)
366
+
367
+ if totalChannelNum > 0:
368
+ for i in range(1, totalChannelNum + 1):
369
+ self.set_channel_value_by_index(inObj, i, 0.0)
370
+
371
+ def extract_morph_channel_geometry(self, obj, _feedback_=False):
372
+ """
373
+ 모프 채널의 기하학적 형태를 추출하여 개별 객체로 생성
374
+
375
+ Args:
376
+ obj: 추출 대상 객체
377
+ _feedback_: 피드백 메시지 출력 여부
378
+
379
+ Returns:
380
+ 추출된 객체 배열
381
+ """
382
+ extractedObjs = []
383
+ morphMod = self.get_modifier(obj)
384
+
385
+ if rt.IsValidMorpherMod(morphMod):
386
+ # 데이터가 있는 모든 채널 인덱스 수집
387
+ channels = [i for i in range(1, rt.WM3_NumberOfChannels(morphMod) + 1)
388
+ if rt.WM3_MC_HasData(morphMod, i)]
389
+
390
+ for i in channels:
391
+ channelName = rt.WM3_MC_GetName(morphMod, i)
392
+ rt.WM3_MC_SetValue(morphMod, i, 100.0)
393
+
394
+ objSnapshot = rt.snapshot(obj)
395
+ objSnapshot.name = channelName
396
+ extractedObjs.append(objSnapshot)
397
+
398
+ rt.WM3_MC_SetValue(morphMod, i, 0.0)
399
+
400
+ if _feedback_:
401
+ print(f" - FUNCTION - [ extract_morph_channel_geometry ] - Extracted ---- {objSnapshot.name} ---- successfully!!")
402
+ else:
403
+ if _feedback_:
404
+ print(f" - FUNCTION - [ extract_morph_channel_geometry ] - No valid morpher found on ---- {obj.name} ---- ")
405
+
406
+ return extractedObjs
@@ -6,6 +6,8 @@
6
6
  3ds Max에 특화된 네이밍 기능 (pymxs 의존)
7
7
  """
8
8
 
9
+ import os
10
+
9
11
  from pymxs import runtime as rt
10
12
  from pyjallib.naming import Naming
11
13
  from pyjallib.namePart import NamePart, NamePartType
@@ -35,35 +37,9 @@ class Name(Naming):
35
37
  # 사용자가 지정한 설정 파일 사용
36
38
  self.load_from_config_file(configPath=configPath)
37
39
  else:
38
- # 설정 파일이 없는 경우, 기본 설정값으로 초기화
39
- # Base 부분 (PREFIX 타입)
40
- basePart = NamePart("Base", NamePartType.PREFIX,
41
- ["b", "Bip001"],
42
- ["SkinBone", "Biped"])
43
- # Type 부분 (PREFIX 타입)
44
- typePart = NamePart("Type", NamePartType.PREFIX,
45
- ["P", "Dum", "Exp", "IK", "T"],
46
- ["Parent", "Dummy", "ExposeTM", "IK", "Target"])
47
- # Side 부분 (PREFIX 타입)
48
- sidePart = NamePart("Side", NamePartType.PREFIX,
49
- ["L", "R"],
50
- ["Left", "Right"],
51
- True)
52
- # FrontBack 부분 (PREFIX 타입)
53
- frontBackPart = NamePart("FrontBack", NamePartType.PREFIX,
54
- ["F", "B"],
55
- ["Front", "Back"],
56
- True)
57
- # RealName 부분 (REALNAME 타입)
58
- realNamePart = NamePart("RealName", NamePartType.REALNAME, [], [])
59
- # Index 부분 (INDEX 타입)
60
- indexPart = NamePart("Index", NamePartType.INDEX, [], [])
61
- # Nub 부분 (SUFFIX 타입)
62
- nubPart = NamePart("Nub", NamePartType.SUFFIX,
63
- ["Nub"],
64
- ["Nub"])
65
- # 기본 순서대로 설정
66
- self._nameParts = [basePart, typePart, sidePart, frontBackPart, realNamePart, indexPart, nubPart]
40
+ configDir = os.path.join(os.path.dirname(__file__), "ConfigFiles")
41
+ nameConfigDir = os.path.join(configDir, "3DSMaxNamingConfig.json")
42
+ self.load_from_config_file(configPath=nameConfigDir)
67
43
 
68
44
  # NamePart 직접 액세스 메소드들
69
45
  # get_<NamePart 이름>_values 메소드들