pyjallib 0.1.7__py3-none-any.whl → 0.1.9__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/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
pyjallib/max/twistBone.py CHANGED
@@ -37,7 +37,7 @@ class TwistBone:
37
37
  self.anim = animService if animService else Anim()
38
38
  # Ensure dependent services use the potentially newly created instances
39
39
  self.const = constService if constService else Constraint(nameService=self.name)
40
- self.bip = bipService if bipService else Bip(animService=self.anim, nameService=self.name) # Pass potentially new instances
40
+ self.bip = bipService if bipService else Bip(animService=self.anim, nameService=self.name)
41
41
  self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
42
42
 
43
43
  # 표현식 초기화
@@ -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