pyjallib 0.1.7__py3-none-any.whl → 0.1.8__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/ConfigFiles/3DSMaxNamingConfig.json +2 -2
- pyjallib/max/__init__.py +8 -1
- pyjallib/max/constraint.py +39 -2
- pyjallib/max/header.py +6 -0
- pyjallib/max/helper.py +6 -0
- pyjallib/max/morph.py +406 -0
- pyjallib/max/ui/Container.py +153 -0
- pyjallib/max/volumePreserveBone.py +171 -0
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.8.dist-info}/METADATA +1 -1
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.8.dist-info}/RECORD +12 -9
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.8.dist-info}/WHEEL +0 -0
pyjallib/__init__.py
CHANGED
@@ -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",
|
pyjallib/max/__init__.py
CHANGED
@@ -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
|
]
|
pyjallib/max/constraint.py
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
"""
|
pyjallib/max/header.py
CHANGED
@@ -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,9 @@ 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()
|
71
77
|
|
72
78
|
self.tools = []
|
73
79
|
|
pyjallib/max/helper.py
CHANGED
@@ -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
|
"""
|
pyjallib/max/morph.py
ADDED
@@ -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
|
@@ -0,0 +1,153 @@
|
|
1
|
+
"""
|
2
|
+
PySide2 Collapsible Widget/ frameLayout
|
3
|
+
|
4
|
+
Origianlly created by: aronamao
|
5
|
+
on GitHub: https://github.com/aronamao/PySide2-Collapsible-Widget
|
6
|
+
"""
|
7
|
+
|
8
|
+
|
9
|
+
from PySide2 import QtWidgets, QtGui, QtCore
|
10
|
+
|
11
|
+
|
12
|
+
class Header(QtWidgets.QWidget):
|
13
|
+
"""Header class for collapsible group"""
|
14
|
+
|
15
|
+
def __init__(self, name, content_widget):
|
16
|
+
"""Header Class Constructor to initialize the object.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
name (str): Name for the header
|
20
|
+
content_widget (QtWidgets.QWidget): Widget containing child elements
|
21
|
+
"""
|
22
|
+
super(Header, self).__init__()
|
23
|
+
self.content = content_widget
|
24
|
+
|
25
|
+
# Try to load icons from resources, use fallback if not available
|
26
|
+
self.expand_ico = QtGui.QPixmap(":teDownArrow.png")
|
27
|
+
self.collapse_ico = QtGui.QPixmap(":teRightArrow.png")
|
28
|
+
|
29
|
+
# Check if icons were loaded properly (not empty)
|
30
|
+
if self.expand_ico.isNull() or self.collapse_ico.isNull():
|
31
|
+
# Create fallback icons programmatically
|
32
|
+
self.expand_ico = self._create_arrow_icon("down")
|
33
|
+
self.collapse_ico = self._create_arrow_icon("right")
|
34
|
+
|
35
|
+
self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
|
36
|
+
|
37
|
+
stacked = QtWidgets.QStackedLayout(self)
|
38
|
+
stacked.setStackingMode(QtWidgets.QStackedLayout.StackAll)
|
39
|
+
background = QtWidgets.QLabel()
|
40
|
+
background.setStyleSheet("QLabel{ background-color: rgb(81, 81, 81); border-radius:2px}")
|
41
|
+
|
42
|
+
widget = QtWidgets.QWidget()
|
43
|
+
layout = QtWidgets.QHBoxLayout(widget)
|
44
|
+
|
45
|
+
self.icon = QtWidgets.QLabel()
|
46
|
+
self.icon.setPixmap(self.expand_ico)
|
47
|
+
layout.addWidget(self.icon)
|
48
|
+
layout.setContentsMargins(11, 0, 11, 0)
|
49
|
+
|
50
|
+
font = QtGui.QFont()
|
51
|
+
font.setBold(True)
|
52
|
+
label = QtWidgets.QLabel(name)
|
53
|
+
label.setFont(font)
|
54
|
+
|
55
|
+
layout.addWidget(label)
|
56
|
+
layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding))
|
57
|
+
|
58
|
+
stacked.addWidget(widget)
|
59
|
+
stacked.addWidget(background)
|
60
|
+
background.setMinimumHeight(layout.sizeHint().height() * 1.5)
|
61
|
+
|
62
|
+
def mousePressEvent(self, *args):
|
63
|
+
"""Handle mouse events, call the function to toggle groups"""
|
64
|
+
self.expand() if not self.content.isVisible() else self.collapse()
|
65
|
+
|
66
|
+
def expand(self):
|
67
|
+
self.content.setVisible(True)
|
68
|
+
self.icon.setPixmap(self.expand_ico)
|
69
|
+
|
70
|
+
def collapse(self):
|
71
|
+
self.content.setVisible(False)
|
72
|
+
self.icon.setPixmap(self.collapse_ico)
|
73
|
+
|
74
|
+
def _create_arrow_icon(self, direction):
|
75
|
+
"""Create a fallback arrow icon when resource icons are not available.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
direction (str): Direction of the arrow ('down' or 'right')
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
QtGui.QPixmap: Created arrow icon
|
82
|
+
"""
|
83
|
+
# Create a pixmap for the arrow
|
84
|
+
pixmap = QtGui.QPixmap(16, 16)
|
85
|
+
pixmap.fill(QtGui.QColor(0, 0, 0, 0)) # Transparent background
|
86
|
+
|
87
|
+
# Create a painter to draw the arrow
|
88
|
+
painter = QtGui.QPainter(pixmap)
|
89
|
+
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
90
|
+
|
91
|
+
# Set the pen and brush
|
92
|
+
pen = QtGui.QPen(QtGui.QColor(200, 200, 200))
|
93
|
+
painter.setPen(pen)
|
94
|
+
painter.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 200)))
|
95
|
+
|
96
|
+
# Draw the arrow based on direction
|
97
|
+
if direction == "down":
|
98
|
+
points = [QtCore.QPoint(4, 6), QtCore.QPoint(12, 6), QtCore.QPoint(8, 10)]
|
99
|
+
else: # right arrow
|
100
|
+
points = [QtCore.QPoint(6, 4), QtCore.QPoint(6, 12), QtCore.QPoint(10, 8)]
|
101
|
+
|
102
|
+
painter.drawPolygon(points)
|
103
|
+
painter.end()
|
104
|
+
|
105
|
+
return pixmap
|
106
|
+
|
107
|
+
|
108
|
+
class Container(QtWidgets.QWidget):
|
109
|
+
"""Class for creating a collapsible group similar to how it is implement in Maya
|
110
|
+
|
111
|
+
Examples:
|
112
|
+
Simple example of how to add a Container to a QVBoxLayout and attach a QGridLayout
|
113
|
+
|
114
|
+
>>> layout = QtWidgets.QVBoxLayout()
|
115
|
+
>>> container = Container("Group")
|
116
|
+
>>> layout.addWidget(container)
|
117
|
+
>>> content_layout = QtWidgets.QGridLayout(container.contentWidget)
|
118
|
+
>>> content_layout.addWidget(QtWidgets.QPushButton("Button"))
|
119
|
+
"""
|
120
|
+
def __init__(self, name, color_background=True):
|
121
|
+
"""Container Class Constructor to initialize the object
|
122
|
+
|
123
|
+
Args:
|
124
|
+
name (str): Name for the header
|
125
|
+
color_background (bool): whether or not to color the background lighter like in maya
|
126
|
+
"""
|
127
|
+
super(Container, self).__init__()
|
128
|
+
layout = QtWidgets.QVBoxLayout(self)
|
129
|
+
layout.setContentsMargins(0, 2, 0, 0)
|
130
|
+
layout.setSpacing(0)
|
131
|
+
self._content_widget = QtWidgets.QWidget()
|
132
|
+
if color_background:
|
133
|
+
self._content_widget.setStyleSheet('''
|
134
|
+
.QWidget{
|
135
|
+
background-color: rgb(81, 81, 81);
|
136
|
+
}
|
137
|
+
''')
|
138
|
+
header = Header(name, self._content_widget)
|
139
|
+
layout.addWidget(header)
|
140
|
+
layout.addWidget(self._content_widget)
|
141
|
+
|
142
|
+
# assign header methods to instance attributes so they can be called outside of this class
|
143
|
+
self.collapse = header.collapse
|
144
|
+
self.expand = header.expand
|
145
|
+
self.toggle = header.mousePressEvent
|
146
|
+
|
147
|
+
@property
|
148
|
+
def contentWidget(self):
|
149
|
+
"""Getter for the content widget
|
150
|
+
|
151
|
+
Returns: Content widget
|
152
|
+
"""
|
153
|
+
return self._content_widget
|
@@ -0,0 +1,171 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
관절 부피 유지 본(Volume preserve Bone) 모듈 - 3ds Max용 관절의 부피를 유지하기 위해 추가되는 중간본들을 위한 모듈
|
6
|
+
"""
|
7
|
+
|
8
|
+
from pymxs import runtime as rt
|
9
|
+
|
10
|
+
# Import necessary service classes for default initialization
|
11
|
+
from .name import Name
|
12
|
+
from .anim import Anim
|
13
|
+
from .constraint import Constraint
|
14
|
+
from .bone import Bone
|
15
|
+
from .helper import Helper
|
16
|
+
|
17
|
+
class VolumePreserveBone:
|
18
|
+
"""
|
19
|
+
관절 부피 유지 본(Volume preserve Bone) 클래스
|
20
|
+
3ds Max에서 관절의 부피를 유지하기 위해 추가되는 중간본들을 위한 클래스
|
21
|
+
"""
|
22
|
+
def __init__(self, nameService=None, animService=None, constService=None, boneService=None, helperService=None):
|
23
|
+
self.name = nameService if nameService else Name()
|
24
|
+
self.anim = animService if animService else Anim()
|
25
|
+
# Ensure dependent services use the potentially newly created instances
|
26
|
+
self.const = constService if constService else Constraint(nameService=self.name)
|
27
|
+
self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
|
28
|
+
self.helper = helperService if helperService else Helper(nameService=self.name)
|
29
|
+
|
30
|
+
def create_rot_helpers(self, inObj, inRotScale=0.5):
|
31
|
+
if rt.isValidNode(inObj) == False or rt.isValidNode(inObj.parent) == False:
|
32
|
+
return False
|
33
|
+
|
34
|
+
volName = self.name.get_RealName(inObj.name)
|
35
|
+
volName = volName + "Vol"
|
36
|
+
|
37
|
+
parentObj = inObj.parent
|
38
|
+
|
39
|
+
rt.select(inObj)
|
40
|
+
rotHelpers = self.helper.create_helper(make_two=True)
|
41
|
+
rotHelpers[0].parent = inObj
|
42
|
+
rotHelpers[1].parent = parentObj
|
43
|
+
|
44
|
+
rotHelpers[0].name = self.name.replace_RealName(rotHelpers[0].name, volName)
|
45
|
+
rotHelpers[1].name = self.name.replace_RealName(rotHelpers[1].name, volName + self.name.get_name_part_value_by_description("Type", "Target"))
|
46
|
+
|
47
|
+
rotConst = self.const.assign_rot_const_multi(rotHelpers[0], [inObj, rotHelpers[1]])
|
48
|
+
rotConst.setWeight(1, inRotScale * 100.0)
|
49
|
+
rotConst.setWeight(2, (1.0 - inRotScale) * 100.0)
|
50
|
+
|
51
|
+
return rotHelpers
|
52
|
+
|
53
|
+
def create_init_bone(self, inObj, inVolumeSize, inRotHelpers, inRotAxis="Z", inTransAxis="PosY", inTransScale=1.0):
|
54
|
+
if rt.isValidNode(inObj) == False or rt.isValidNode(inObj.parent) == False:
|
55
|
+
return False
|
56
|
+
|
57
|
+
returnVal = {
|
58
|
+
"Bones": [],
|
59
|
+
"Helpers": []
|
60
|
+
}
|
61
|
+
|
62
|
+
volName = self.name.get_RealName(inObj.name)
|
63
|
+
volName = volName + "Vol"
|
64
|
+
|
65
|
+
# ExposeTM을 사용하여 회전값을 가져오는 방법
|
66
|
+
# rt.select(inObj)
|
67
|
+
# expHelper = self.helper.create_exp_tm()[0]
|
68
|
+
# expHelper.parent = parentObj
|
69
|
+
# expHelper.exposeNode = rotHelpers[0]
|
70
|
+
# expHelper.useParent = False
|
71
|
+
# expHelper.localReferenceNode = rotHelpers[1]
|
72
|
+
# expHelper.eulerXOrder = 5
|
73
|
+
# expHelper.eulerYOrder = 5
|
74
|
+
# expHelper.eulerZOrder = 5
|
75
|
+
|
76
|
+
# expHelper.name = self.name.replace_RealName(expHelper.name, volName)
|
77
|
+
|
78
|
+
boneGenHelperA = rt.point()
|
79
|
+
boneGenHelperB = rt.point()
|
80
|
+
boneGenHelperA.transform = inObj.transform
|
81
|
+
boneGenHelperB.transform = inObj.transform
|
82
|
+
|
83
|
+
if inTransAxis == "PosX":
|
84
|
+
self.anim.move_local(boneGenHelperB, inVolumeSize, 0, 0)
|
85
|
+
elif inTransAxis == "NegX":
|
86
|
+
self.anim.move_local(boneGenHelperB, -inVolumeSize, 0, 0)
|
87
|
+
elif inTransAxis == "PosY":
|
88
|
+
self.anim.move_local(boneGenHelperB, 0, inVolumeSize, 0)
|
89
|
+
elif inTransAxis == "NegY":
|
90
|
+
self.anim.move_local(boneGenHelperB, 0, -inVolumeSize, 0)
|
91
|
+
elif inTransAxis == "PosZ":
|
92
|
+
self.anim.move_local(boneGenHelperB, 0, 0, inVolumeSize)
|
93
|
+
elif inTransAxis == "NegZ":
|
94
|
+
self.anim.move_local(boneGenHelperB, 0, 0, -inVolumeSize)
|
95
|
+
|
96
|
+
row = ""
|
97
|
+
sourceUpAxist = 0
|
98
|
+
upAxis = 0
|
99
|
+
if inRotAxis == "X":
|
100
|
+
row = "row1"
|
101
|
+
sourceUpAxist = 1
|
102
|
+
upAxis = 1
|
103
|
+
elif inRotAxis == "Y":
|
104
|
+
row = "row3"
|
105
|
+
sourceUpAxist = 2
|
106
|
+
upAxis = 2
|
107
|
+
elif inRotAxis == "Z":
|
108
|
+
row = "row2"
|
109
|
+
sourceUpAxist = 3
|
110
|
+
upAxis = 3
|
111
|
+
|
112
|
+
volumeBoneName = self.name.replace_RealName(self.name.get_string(inObj.name), volName + inRotAxis + inTransAxis)
|
113
|
+
|
114
|
+
volumeBones = self.bone.create_simple_bone(inVolumeSize, volumeBoneName)
|
115
|
+
volumeBones[0].transform = inObj.transform
|
116
|
+
lookatConst = self.const.assign_lookat(volumeBones[0], boneGenHelperB)
|
117
|
+
lookatConst.pickUpNode = inObj
|
118
|
+
lookatConst.upnode_world = False
|
119
|
+
lookatConst.StoUP_axis = sourceUpAxist
|
120
|
+
lookatConst.upnode_axis = upAxis
|
121
|
+
self.const.collapse(volumeBones[0])
|
122
|
+
|
123
|
+
rt.delete(boneGenHelperA)
|
124
|
+
rt.delete(boneGenHelperB)
|
125
|
+
|
126
|
+
volumeBones[0].parent = inRotHelpers[0]
|
127
|
+
rt.select(volumeBones[0])
|
128
|
+
parentHelper = self.helper.create_parent_helper()[0]
|
129
|
+
|
130
|
+
posConst = self.const.assign_pos_script_controller(volumeBones[0])
|
131
|
+
# posConst.AddNode("rotExp", expHelper)
|
132
|
+
posConst.AddNode("rotObj", inRotHelpers[0])
|
133
|
+
posConst.AddNode("rotParent", inRotHelpers[1])
|
134
|
+
|
135
|
+
posConstCode = f""
|
136
|
+
posConstCode += f"local parentXAxis = (normalize rotParent.objectTransform.{row})\n"
|
137
|
+
posConstCode += f"local rotObjXAxis = (normalize rotObj.objectTransform.{row})\n"
|
138
|
+
posConstCode += f"local rotAmount = (1.0 - (dot parentXAxis rotObjXAxis))/2.0\n"
|
139
|
+
posConstCode += f"local posX = rotAmount * {inVolumeSize*inTransScale}\n"
|
140
|
+
posConstCode += f"[posX, 0.0, 0.0]\n"
|
141
|
+
|
142
|
+
posConst.SetExpression(posConstCode)
|
143
|
+
|
144
|
+
returnVal["Bones"] = volumeBones
|
145
|
+
returnVal["Helpers"] = [parentHelper]
|
146
|
+
|
147
|
+
return returnVal
|
148
|
+
|
149
|
+
def create_bones(self,inObj, inVolumeSize, inRotAxises, inRotScale, inTransAxiese, inTransScales):
|
150
|
+
if rt.isValidNode(inObj) == False or rt.isValidNode(inObj.parent) == False:
|
151
|
+
return False
|
152
|
+
|
153
|
+
if not len(inTransAxiese) == len(inTransScales) == len(inTransAxiese) == len(inRotAxises):
|
154
|
+
return False
|
155
|
+
|
156
|
+
rotHelpers = self.create_rot_helpers(inObj, inRotScale=inRotScale)
|
157
|
+
|
158
|
+
returnVal = {
|
159
|
+
"Bones": [],
|
160
|
+
"Helpers": []
|
161
|
+
}
|
162
|
+
|
163
|
+
for i in range(len(inRotAxises)):
|
164
|
+
genResult = self.create_init_bone(inObj, inVolumeSize, rotHelpers, inRotAxises[i], inTransAxiese[i], inTransScales[i])
|
165
|
+
returnVal["Bones"].extend(genResult["Bones"])
|
166
|
+
returnVal["Helpers"].extend(genResult["Helpers"])
|
167
|
+
|
168
|
+
returnVal["Helpers"].insert(0, rotHelpers[0])
|
169
|
+
returnVal["Helpers"].insert(1, rotHelpers[1])
|
170
|
+
|
171
|
+
return returnVal
|
@@ -1,4 +1,4 @@
|
|
1
|
-
pyjallib/__init__.py,sha256=
|
1
|
+
pyjallib/__init__.py,sha256=FD41BluMZ_gDt-gXhEAzIwNyWn_tokcUoZMKTpn_fN8,460
|
2
2
|
pyjallib/namePart.py,sha256=D1hnFNnZbrNicAiW2ZUaIT0LU5pro3uFYrFYOEjt7_Y,24001
|
3
3
|
pyjallib/nameToPath.py,sha256=61EWrc0Wc1K1Qsc4G6jewIccI0IHbiZWroRcU_lX1Wc,4664
|
4
4
|
pyjallib/naming.py,sha256=jJ6w0n_nnUE2uZy_I4KFsMx95Ij3_KvSjhVvQQplxpw,36621
|
@@ -7,30 +7,33 @@ pyjallib/perforce.py,sha256=YnKAKlbBss4cl66R4nQbr-bsYjTneLzGYaZ187kOSpA,29872
|
|
7
7
|
pyjallib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
pyjallib/reloadModules.py,sha256=RAEG3IxzJ0TlsjvnZwJt56JOkc2j8voqAnRbfQuZ44g,1151
|
9
9
|
pyjallib/ConfigFiles/namingConfig.json,sha256=Ov4bbVJb6qodPaooU63e11YUMGXXPWFAA4AQq1sLBYU,1486
|
10
|
-
pyjallib/max/__init__.py,sha256=
|
10
|
+
pyjallib/max/__init__.py,sha256=1058Mvp_Ebf3DG_DVkAC4o_frQPq7F6-m-C-Ccme64k,1064
|
11
11
|
pyjallib/max/align.py,sha256=HKjCViQCuicGmtvHB6xxVv4BEGEBGtV2gO3NvR_6R2A,5183
|
12
12
|
pyjallib/max/anim.py,sha256=-shQeE0WeAeCefc8FoI63dNDLHhz0uzOJ4shp5AL_Cs,25135
|
13
13
|
pyjallib/max/autoClavicle.py,sha256=FQGFkucRu670TpkEUs8lQRJyNUykvtxF9rZiPjHoH3s,7517
|
14
14
|
pyjallib/max/bip.py,sha256=m6eA-rg-MghYSxbzj-YXa0KJFPm1wiOsOqyJu76_hlY,16967
|
15
15
|
pyjallib/max/bone.py,sha256=GYs3Uohc0AMkLWZAqZTc1DET-WDgZlvlkhSUaBeszvk,33161
|
16
|
-
pyjallib/max/constraint.py,sha256=
|
16
|
+
pyjallib/max/constraint.py,sha256=gVdGezkRmzWRtUZeAdLnWfmvhgy8HAhD_dXmyI4xvUw,41282
|
17
17
|
pyjallib/max/groinBone.py,sha256=rEZQ7tAXgcZ3NedFacIyvfG07nI8rgF_pVufy-hnKuo,3830
|
18
|
-
pyjallib/max/header.py,sha256=
|
19
|
-
pyjallib/max/helper.py,sha256=
|
18
|
+
pyjallib/max/header.py,sha256=s2no3jRenY4b-xjofKwV9TGbO9doE4KH8paeBQA7qko,3071
|
19
|
+
pyjallib/max/helper.py,sha256=WPoxSoHAFXmAPx_CRJFWBNXn4S2k04UK-1J9x6nIv84,18674
|
20
20
|
pyjallib/max/layer.py,sha256=e9Mn8h7xf0oBYST3QIpyBpLMl8qpWTExO9Y6yH6rKc0,8795
|
21
21
|
pyjallib/max/link.py,sha256=J3z9nkP8ZxAh9yYhR16tjQFCJTCYZMSB0MGbSHfA7uI,2592
|
22
22
|
pyjallib/max/mirror.py,sha256=j8LnsXowyTINzvtWsvCNaDsQ6v7u2RjlY50R8v5JCuc,14517
|
23
|
+
pyjallib/max/morph.py,sha256=I8HRYx4NznL6GZL4CbT9iTv05SeaBW_mJJ4PzMxCBkw,12664
|
23
24
|
pyjallib/max/name.py,sha256=DcJt2td-N7vfUGyWazdGTD4-0JW-noa7z5nwc6SHm6I,15337
|
24
25
|
pyjallib/max/select.py,sha256=HMJD2WNX3zVBEeYrj0UX2YXM3fHNItfw6UtQSItNsoU,9487
|
25
26
|
pyjallib/max/skin.py,sha256=5mBzG2wSUxoGlkFeb9Ys8uUxOwuZRGeqUMTI9LiWWZU,41937
|
26
27
|
pyjallib/max/twistBone.py,sha256=0Z-Z-OiTPM0fXAnzgol0tiY7Kms6-qrEuVCHsCVVbUc,20338
|
27
|
-
pyjallib/max/
|
28
|
+
pyjallib/max/volumePreserveBone.py,sha256=mTtbAOKRjkbVO-PQQMt6FZzAawtUMbwGgj8XO1n9SV8,7193
|
29
|
+
pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json,sha256=PBUYawCELG0aLNRdTh6j-Yka4eNdmpF4P8iRyp0Ngpg,3717
|
28
30
|
pyjallib/max/macro/jal_macro_align.py,sha256=t0gQjybkHYAvPtjT5uWjUQd1vsXIsxOkdYfhFWduxJU,4270
|
29
31
|
pyjallib/max/macro/jal_macro_bone.py,sha256=QS492tskLrARGIFDmbKEwJw7122xJga0ZP5z8WVVRn8,12422
|
30
32
|
pyjallib/max/macro/jal_macro_constraint.py,sha256=0gpfan7hXES9jhnpZVlxWtCbqUCyGxjtSmVMbp7nUh8,4160
|
31
33
|
pyjallib/max/macro/jal_macro_helper.py,sha256=ODwAl4C6WK2UE45dqpfw90TgN5vDjLJvODvYeO8tj4o,12906
|
32
34
|
pyjallib/max/macro/jal_macro_link.py,sha256=xkgcCX0fJw4vLfMYybtfUklT3dgcO0tHfpt2X9BfWLw,1193
|
33
35
|
pyjallib/max/macro/jal_macro_select.py,sha256=-r24l84XmDEM4W6H0r1jOBErp3q0UxNrr0m9oAHSgkE,2289
|
34
|
-
pyjallib
|
35
|
-
pyjallib-0.1.
|
36
|
-
pyjallib-0.1.
|
36
|
+
pyjallib/max/ui/Container.py,sha256=QSk3oCqhfiR4aglSSkherRGAyPFqMRUt83L-0ENBz-s,5571
|
37
|
+
pyjallib-0.1.8.dist-info/METADATA,sha256=7dGxma4w0wrSzOlLcLnI1dN0ha3emWufRpVDWxkCaXY,869
|
38
|
+
pyjallib-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
39
|
+
pyjallib-0.1.8.dist-info/RECORD,,
|
File without changes
|