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/__init__.py +1 -1
- pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json +2 -2
- pyjallib/max/__init__.py +8 -1
- pyjallib/max/autoClavicle.py +85 -42
- pyjallib/max/bone.py +14 -7
- pyjallib/max/constraint.py +68 -40
- pyjallib/max/groinBone.py +51 -20
- pyjallib/max/header.py +8 -4
- pyjallib/max/helper.py +6 -0
- pyjallib/max/hip.py +366 -0
- pyjallib/max/morph.py +406 -0
- pyjallib/max/twistBone.py +1 -1
- pyjallib/max/ui/Container.py +153 -0
- pyjallib/max/volumePreserveBone.py +209 -0
- pyjallib/p4module.py +488 -0
- pyjallib/perforce.py +446 -653
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.9.dist-info}/METADATA +1 -1
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.9.dist-info}/RECORD +19 -14
- {pyjallib-0.1.7.dist-info → pyjallib-0.1.9.dist-info}/WHEEL +0 -0
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)
|
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
|