pyjallib 0.1.16__py3-none-any.whl → 0.1.19__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 +4 -1
- pyjallib/exceptions.py +75 -0
- pyjallib/logger.py +288 -0
- pyjallib/max/__init__.py +8 -0
- pyjallib/max/autoClavicle.py +17 -5
- pyjallib/max/bip.py +0 -21
- pyjallib/max/bone.py +21 -1
- pyjallib/max/boneChain.py +2 -0
- pyjallib/max/constraint.py +27 -2
- pyjallib/max/elbow.py +105 -0
- pyjallib/max/groinBone.py +2 -0
- pyjallib/max/header.py +121 -113
- pyjallib/max/hip.py +2 -0
- pyjallib/max/inguinal.py +117 -0
- pyjallib/max/kneeBone.py +2 -0
- pyjallib/max/macro/jal_macro_bone.py +221 -8
- pyjallib/max/macro/jal_macro_constraint.py +30 -0
- pyjallib/max/mirror.py +20 -13
- pyjallib/max/shoulder.py +173 -0
- pyjallib/max/twistBone.py +22 -19
- pyjallib/max/volumeBone.py +12 -1
- pyjallib/max/wrist.py +113 -0
- pyjallib/nameToPath.py +78 -61
- pyjallib/naming.py +4 -1
- pyjallib/perforce.py +196 -347
- pyjallib/progressEvent.py +75 -0
- pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +487 -0
- pyjallib/ue5/__init__.py +170 -0
- pyjallib/ue5/disableInterchangeFrameWork.py +82 -0
- pyjallib/ue5/inUnreal/__init__.py +95 -0
- pyjallib/ue5/inUnreal/animationImporter.py +206 -0
- pyjallib/ue5/inUnreal/baseImporter.py +187 -0
- pyjallib/ue5/inUnreal/importerSettings.py +179 -0
- pyjallib/ue5/inUnreal/skeletalMeshImporter.py +112 -0
- pyjallib/ue5/inUnreal/skeletonImporter.py +105 -0
- pyjallib/ue5/logger.py +241 -0
- pyjallib/ue5/templateProcessor.py +287 -0
- pyjallib/ue5/templates/__init__.py +109 -0
- pyjallib/ue5/templates/animImportTemplate.py +22 -0
- pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
- pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
- pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/METADATA +1 -1
- pyjallib-0.1.19.dist-info/RECORD +72 -0
- pyjallib-0.1.16.dist-info/RECORD +0 -49
- {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/WHEEL +0 -0
@@ -47,8 +47,8 @@ class BoneNameDialog(QtWidgets.QDialog):
|
|
47
47
|
side_group = QtWidgets.QGroupBox("Side:")
|
48
48
|
side_layout = QtWidgets.QVBoxLayout()
|
49
49
|
self.side_none_radio = QtWidgets.QRadioButton("(None)")
|
50
|
-
self.side_l_radio = QtWidgets.QRadioButton("
|
51
|
-
self.side_r_radio = QtWidgets.QRadioButton("
|
50
|
+
self.side_l_radio = QtWidgets.QRadioButton(jal.name.get_name_part("Side").get_value_by_description("Left"))
|
51
|
+
self.side_r_radio = QtWidgets.QRadioButton(jal.name.get_name_part("Side").get_value_by_description("Right"))
|
52
52
|
self.side_none_radio.setChecked(True)
|
53
53
|
side_layout.addWidget(self.side_none_radio)
|
54
54
|
side_layout.addWidget(self.side_l_radio)
|
@@ -59,8 +59,8 @@ class BoneNameDialog(QtWidgets.QDialog):
|
|
59
59
|
front_group = QtWidgets.QGroupBox("Front:")
|
60
60
|
front_layout = QtWidgets.QVBoxLayout()
|
61
61
|
self.front_none_radio = QtWidgets.QRadioButton("(None)")
|
62
|
-
self.front_f_radio = QtWidgets.QRadioButton("
|
63
|
-
self.front_b_radio = QtWidgets.QRadioButton("
|
62
|
+
self.front_f_radio = QtWidgets.QRadioButton(jal.name.get_name_part("FrontBack").get_value_by_description("Forward"))
|
63
|
+
self.front_b_radio = QtWidgets.QRadioButton(jal.name.get_name_part("FrontBack").get_value_by_description("Backward"))
|
64
64
|
self.front_none_radio.setChecked(True)
|
65
65
|
front_layout.addWidget(self.front_none_radio)
|
66
66
|
front_layout.addWidget(self.front_f_radio)
|
@@ -136,16 +136,16 @@ class BoneNameDialog(QtWidgets.QDialog):
|
|
136
136
|
if self.side_none_radio.isChecked():
|
137
137
|
self.sideName = ""
|
138
138
|
elif self.side_l_radio.isChecked():
|
139
|
-
self.sideName = "
|
139
|
+
self.sideName = jal.name.get_name_part("Side").get_value_by_description("Left")
|
140
140
|
elif self.side_r_radio.isChecked():
|
141
|
-
self.sideName = "
|
141
|
+
self.sideName = jal.name.get_name_part("Side").get_value_by_description("Right")
|
142
142
|
|
143
143
|
if self.front_none_radio.isChecked():
|
144
144
|
self.frontBackName = ""
|
145
145
|
elif self.front_f_radio.isChecked():
|
146
|
-
self.frontBackName = "
|
146
|
+
self.frontBackName = jal.name.get_name_part("FrontBack").get_value_by_description("Forward")
|
147
147
|
elif self.front_b_radio.isChecked():
|
148
|
-
self.frontBackName = "
|
148
|
+
self.frontBackName = jal.name.get_name_part("FrontBack").get_value_by_description("Backward")
|
149
149
|
|
150
150
|
RealName = self.name_edit.text() if self.name_edit.text() != "" else "TempBone"
|
151
151
|
|
@@ -189,6 +189,190 @@ class BoneNameDialog(QtWidgets.QDialog):
|
|
189
189
|
self.boneNameSetted = False
|
190
190
|
self.reject()
|
191
191
|
|
192
|
+
class BoneSizeDialog(QtWidgets.QDialog):
|
193
|
+
def __init__(self, parent=QtWidgets.QWidget.find(rt.windows.getMAXHWND())):
|
194
|
+
super().__init__(parent)
|
195
|
+
self.setWindowTitle("Bone Size")
|
196
|
+
self.setMinimumWidth(300)
|
197
|
+
|
198
|
+
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
|
199
|
+
|
200
|
+
# UI 변수들 초기화
|
201
|
+
self.fin_on = True
|
202
|
+
self.fin_size = 2.0
|
203
|
+
self.bone_size = 4.0
|
204
|
+
self.bone_taper = 90.0
|
205
|
+
|
206
|
+
# 원래 본 모양 저장용 변수들
|
207
|
+
self.original_bone_shapes = {} # {본객체: 원래모양배열}
|
208
|
+
self.original_selection = [] # 원래 선택된 본들
|
209
|
+
|
210
|
+
# Layouts
|
211
|
+
main_layout = QtWidgets.QVBoxLayout(self)
|
212
|
+
fin_layout = QtWidgets.QHBoxLayout()
|
213
|
+
bone_layout = QtWidgets.QHBoxLayout()
|
214
|
+
button_layout = QtWidgets.QHBoxLayout()
|
215
|
+
|
216
|
+
# Fin On 체크박스와 Fin Size 스피너 (가로로 배치)
|
217
|
+
self.fin_on_checkbox = QtWidgets.QCheckBox("Fin On")
|
218
|
+
self.fin_on_checkbox.setChecked(self.fin_on)
|
219
|
+
fin_layout.addWidget(self.fin_on_checkbox)
|
220
|
+
|
221
|
+
fin_layout.addWidget(QtWidgets.QLabel("Fin Size:"))
|
222
|
+
|
223
|
+
self.fin_size_spinner = QtWidgets.QDoubleSpinBox()
|
224
|
+
self.fin_size_spinner.setRange(0.0, 100.0)
|
225
|
+
self.fin_size_spinner.setValue(self.fin_size)
|
226
|
+
self.fin_size_spinner.setDecimals(1)
|
227
|
+
self.fin_size_spinner.setSingleStep(0.1)
|
228
|
+
fin_layout.addWidget(self.fin_size_spinner)
|
229
|
+
|
230
|
+
# Bone Size와 Bone Taper 스피너 (가로로 배치)
|
231
|
+
bone_layout.addWidget(QtWidgets.QLabel("Bone Size:"))
|
232
|
+
|
233
|
+
self.bone_size_spinner = QtWidgets.QDoubleSpinBox()
|
234
|
+
self.bone_size_spinner.setRange(0.0, 100.0)
|
235
|
+
self.bone_size_spinner.setValue(self.bone_size)
|
236
|
+
self.bone_size_spinner.setDecimals(1)
|
237
|
+
self.bone_size_spinner.setSingleStep(0.1)
|
238
|
+
bone_layout.addWidget(self.bone_size_spinner)
|
239
|
+
|
240
|
+
bone_layout.addWidget(QtWidgets.QLabel("Bone Taper:"))
|
241
|
+
|
242
|
+
self.bone_taper_spinner = QtWidgets.QDoubleSpinBox()
|
243
|
+
self.bone_taper_spinner.setRange(0.0, 100.0)
|
244
|
+
self.bone_taper_spinner.setValue(self.bone_taper)
|
245
|
+
self.bone_taper_spinner.setDecimals(1)
|
246
|
+
self.bone_taper_spinner.setSingleStep(0.1)
|
247
|
+
bone_layout.addWidget(self.bone_taper_spinner)
|
248
|
+
|
249
|
+
# OK/Cancel Buttons
|
250
|
+
self.ok_button = QtWidgets.QPushButton("OK")
|
251
|
+
self.cancel_button = QtWidgets.QPushButton("Cancel")
|
252
|
+
|
253
|
+
# Arrange Buttons
|
254
|
+
button_layout.addStretch()
|
255
|
+
button_layout.addWidget(self.ok_button)
|
256
|
+
button_layout.addWidget(self.cancel_button)
|
257
|
+
|
258
|
+
# Add layouts to main layout
|
259
|
+
main_layout.addLayout(fin_layout)
|
260
|
+
main_layout.addLayout(bone_layout)
|
261
|
+
main_layout.addStretch()
|
262
|
+
main_layout.addLayout(button_layout)
|
263
|
+
|
264
|
+
# Connect signals
|
265
|
+
self.ok_button.clicked.connect(self.ok_clicked)
|
266
|
+
self.cancel_button.clicked.connect(self.cancel_clicked)
|
267
|
+
|
268
|
+
# Connect all relevant UI changes to the update method
|
269
|
+
self.fin_on_checkbox.toggled.connect(self.update_ui)
|
270
|
+
self.fin_size_spinner.valueChanged.connect(self.update_ui)
|
271
|
+
self.bone_size_spinner.valueChanged.connect(self.update_ui)
|
272
|
+
self.bone_taper_spinner.valueChanged.connect(self.update_ui)
|
273
|
+
|
274
|
+
# 초기 선택된 본들의 원래 모양 저장
|
275
|
+
self.save_original_bone_shapes()
|
276
|
+
|
277
|
+
self.update_ui() # Initial update
|
278
|
+
|
279
|
+
def save_original_bone_shapes(self):
|
280
|
+
"""선택된 본들의 원래 모양을 저장합니다."""
|
281
|
+
selObjs = rt.getCurrentSelection()
|
282
|
+
for item in selObjs:
|
283
|
+
if rt.classOf(item) == rt.BoneGeometry:
|
284
|
+
self.original_selection.append(item)
|
285
|
+
self.original_bone_shapes[item] = jal.bone.get_bone_shape(item)
|
286
|
+
|
287
|
+
def restore_original_bone_shapes(self):
|
288
|
+
"""저장된 원래 모양으로 본들을 되돌립니다."""
|
289
|
+
for item, shape_array in self.original_bone_shapes.items():
|
290
|
+
if rt.isValidObj(item):
|
291
|
+
jal.bone.pasete_bone_shape(item, shape_array)
|
292
|
+
rt.redrawViews()
|
293
|
+
|
294
|
+
def update_ui(self):
|
295
|
+
"""UI 값들을 업데이트합니다."""
|
296
|
+
self.fin_on = self.fin_on_checkbox.isChecked()
|
297
|
+
self.fin_size = self.fin_size_spinner.value()
|
298
|
+
self.bone_size = self.bone_size_spinner.value()
|
299
|
+
self.bone_taper = self.bone_taper_spinner.value()
|
300
|
+
|
301
|
+
# Fin On 상태에 따라 Fin Size 스피너 활성화/비활성화
|
302
|
+
self.fin_size_spinner.setEnabled(self.fin_on)
|
303
|
+
|
304
|
+
selObjs = rt.getCurrentSelection()
|
305
|
+
for item in selObjs:
|
306
|
+
if self.fin_on:
|
307
|
+
jal.bone.set_fin_on(item, inSize=self.fin_size)
|
308
|
+
else:
|
309
|
+
jal.bone.set_fin_off(item)
|
310
|
+
jal.bone.set_bone_size(item, self.bone_size)
|
311
|
+
jal.bone.set_bone_taper(item, self.bone_taper)
|
312
|
+
|
313
|
+
rt.redrawViews()
|
314
|
+
|
315
|
+
def ok_clicked(self):
|
316
|
+
"""OK 버튼 클릭 처리"""
|
317
|
+
self.update_ui()
|
318
|
+
self.accept()
|
319
|
+
|
320
|
+
def cancel_clicked(self):
|
321
|
+
"""Cancel 버튼 클릭 처리"""
|
322
|
+
# 원래 모양으로 되돌리기
|
323
|
+
self.restore_original_bone_shapes()
|
324
|
+
self.reject()
|
325
|
+
|
326
|
+
class TurnBoneDialog(QtWidgets.QDialog):
|
327
|
+
def __init__(self, parent=QtWidgets.QWidget.find(rt.windows.getMAXHWND())):
|
328
|
+
super().__init__(parent)
|
329
|
+
self.setWindowTitle("Turn Bone")
|
330
|
+
self.setMinimumWidth(300)
|
331
|
+
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowContextHelpButtonHint)
|
332
|
+
|
333
|
+
# 다이얼로그 크기 고정 (예: 220x80)
|
334
|
+
self.setFixedSize(220, 80)
|
335
|
+
|
336
|
+
# 메인 레이아웃 생성
|
337
|
+
main_layout = QtWidgets.QVBoxLayout(self)
|
338
|
+
|
339
|
+
# 버튼 레이아웃 (가로)
|
340
|
+
button_layout = QtWidgets.QHBoxLayout()
|
341
|
+
|
342
|
+
# 90, 45, 5 버튼 생성 및 크기 설정
|
343
|
+
self.btn_90 = QtWidgets.QPushButton("90")
|
344
|
+
self.btn_45 = QtWidgets.QPushButton("45")
|
345
|
+
self.btn_5 = QtWidgets.QPushButton("5")
|
346
|
+
for btn in [self.btn_90, self.btn_45, self.btn_5]:
|
347
|
+
btn.setMinimumWidth(50)
|
348
|
+
btn.setMaximumWidth(50)
|
349
|
+
btn.setMinimumHeight(20)
|
350
|
+
btn.setMaximumHeight(20)
|
351
|
+
button_layout.addWidget(btn)
|
352
|
+
|
353
|
+
# 버튼 레이아웃을 메인 레이아웃에 추가
|
354
|
+
main_layout.addLayout(button_layout)
|
355
|
+
|
356
|
+
# 버튼 클릭 이벤트 연결
|
357
|
+
self.btn_90.clicked.connect(lambda: self.turn_bone_by_angle(90))
|
358
|
+
self.btn_45.clicked.connect(lambda: self.turn_bone_by_angle(45))
|
359
|
+
self.btn_5.clicked.connect(lambda: self.turn_bone_by_angle(5))
|
360
|
+
|
361
|
+
def turn_bone_by_angle(self, inAngle):
|
362
|
+
# Ctrl 키가 눌려 있으면 각도를 음수로 변환
|
363
|
+
modifiers = QtWidgets.QApplication.keyboardModifiers()
|
364
|
+
if modifiers & QtCore.Qt.ControlModifier:
|
365
|
+
angle = -inAngle
|
366
|
+
else:
|
367
|
+
angle = inAngle
|
368
|
+
selObjs = rt.getCurrentSelection()
|
369
|
+
for item in selObjs:
|
370
|
+
if rt.classOf(item) == rt.BoneGeometry:
|
371
|
+
jal.bone.turn_bone(item, angle)
|
372
|
+
|
373
|
+
rt.redrawViews()
|
374
|
+
|
375
|
+
|
192
376
|
def jal_bone_on():
|
193
377
|
jal.bone.set_bone_on_selection()
|
194
378
|
|
@@ -275,6 +459,17 @@ def jal_bone_nub_create():
|
|
275
459
|
else:
|
276
460
|
jal.bone.create_nub_bone("Temp", 2)
|
277
461
|
|
462
|
+
def jal_bone_size_modify():
|
463
|
+
dialog = BoneSizeDialog()
|
464
|
+
dialog.show()
|
465
|
+
|
466
|
+
# 다이얼로그가 닫힐 때까지 대기하지 않고 즉시 반환
|
467
|
+
# 사용자가 다이얼로그를 닫으면 자동으로 정리됨
|
468
|
+
|
469
|
+
def jal_bone_turn():
|
470
|
+
dialog = TurnBoneDialog()
|
471
|
+
dialog.show()
|
472
|
+
|
278
473
|
# Register macroscripts
|
279
474
|
macroScript_Category = "jalTools"
|
280
475
|
|
@@ -358,3 +553,21 @@ rt.macros.new(
|
|
358
553
|
"Bone Nub Create",
|
359
554
|
"jal_bone_nub_create()"
|
360
555
|
)
|
556
|
+
|
557
|
+
rt.jal_bone_size_modify = jal_bone_size_modify
|
558
|
+
rt.macros.new(
|
559
|
+
macroScript_Category,
|
560
|
+
"jal_boneSizeModify",
|
561
|
+
"Bone Size Modify",
|
562
|
+
"Bone Size Modify",
|
563
|
+
"jal_bone_size_modify()"
|
564
|
+
)
|
565
|
+
|
566
|
+
rt.jal_bone_turn = jal_bone_turn
|
567
|
+
rt.macros.new(
|
568
|
+
macroScript_Category,
|
569
|
+
"jal_boneTurn",
|
570
|
+
"Bone Turn",
|
571
|
+
"Bone Turn",
|
572
|
+
"jal_bone_turn()"
|
573
|
+
)
|
@@ -11,6 +11,12 @@ def jal_collapse_const():
|
|
11
11
|
for selObj in selArray:
|
12
12
|
jal.constraint.collapse(selObj)
|
13
13
|
|
14
|
+
def jal_collapse_const_with_tcb_rot():
|
15
|
+
if rt.selection.count > 0:
|
16
|
+
selArray = rt.getCurrentSelection()
|
17
|
+
for selObj in selArray:
|
18
|
+
jal.constraint.collapse(selObj, inUseTCBRot=True)
|
19
|
+
|
14
20
|
def jal_pos_const():
|
15
21
|
if rt.selection.count > 1:
|
16
22
|
selArray = rt.getCurrentSelection()
|
@@ -35,6 +41,12 @@ def jal_ori_const():
|
|
35
41
|
|
36
42
|
jal.constraint.assign_rot_const_multi(oriObj, targetObjArray)
|
37
43
|
|
44
|
+
def jal_tcb_rot():
|
45
|
+
if rt.selection.count > 1:
|
46
|
+
selArray = rt.getCurrentSelection()
|
47
|
+
for item in selArray:
|
48
|
+
jal.constraint.assign_tcb_rot(item)
|
49
|
+
|
38
50
|
def jal_rot_script_const():
|
39
51
|
if rt.selection.count == 2:
|
40
52
|
selArray = rt.getCurrentSelection()
|
@@ -87,6 +99,15 @@ rt.macros.new(
|
|
87
99
|
"jal_collapse_const()"
|
88
100
|
)
|
89
101
|
|
102
|
+
rt.jal_collapse_const_with_tcb_rot = jal_collapse_const_with_tcb_rot
|
103
|
+
rt.macros.new(
|
104
|
+
macroScript_Category,
|
105
|
+
"jal_collapse_const_with_tcb_rot",
|
106
|
+
"Collapse Constraints with TCB Rot",
|
107
|
+
"Collapse Constraints with TCB Rot",
|
108
|
+
"jal_collapse_const_with_tcb_rot()"
|
109
|
+
)
|
110
|
+
|
90
111
|
rt.jal_pos_const = jal_pos_const
|
91
112
|
rt.macros.new(
|
92
113
|
macroScript_Category,
|
@@ -105,6 +126,15 @@ rt.macros.new(
|
|
105
126
|
"jal_ori_const()"
|
106
127
|
)
|
107
128
|
|
129
|
+
rt.jal_tcb_rot = jal_tcb_rot
|
130
|
+
rt.macros.new(
|
131
|
+
macroScript_Category,
|
132
|
+
"jal_tcb_rot",
|
133
|
+
"Constraint TCB Rot",
|
134
|
+
"Constraint TCB Rot",
|
135
|
+
"jal_tcb_rot()"
|
136
|
+
)
|
137
|
+
|
108
138
|
rt.jal_rot_script_const = jal_rot_script_const
|
109
139
|
rt.macros.new(
|
110
140
|
macroScript_Category,
|
pyjallib/max/mirror.py
CHANGED
@@ -251,18 +251,17 @@ class Mirror:
|
|
251
251
|
elif mAxis == 3:
|
252
252
|
axisFactor = [1, 1, -1] # z축 미러링
|
253
253
|
|
254
|
-
# 새 뼈대와
|
255
|
-
parents = []
|
254
|
+
# 새 뼈대와 원본-미러 매핑 정보 저장
|
256
255
|
created = []
|
256
|
+
bone_mapping = {} # 원본 뼈대 -> 미러 뼈대 매핑
|
257
257
|
|
258
258
|
# 시작점 위치 (미러링 중심) 설정
|
259
259
|
root = bones[0].transform.translation
|
260
260
|
|
261
|
-
# 정렬된 뼈대 순서대로 처리
|
261
|
+
# 정렬된 뼈대 순서대로 처리 (실제 뼈대만)
|
262
262
|
for i in range(len(bones)):
|
263
263
|
original = bones[i]
|
264
|
-
if rt.classOf(original) != rt.BoneGeometry: # 실제 뼈대가 아닌 경우
|
265
|
-
parents.append(None) # 부모 없음 표시
|
264
|
+
if rt.classOf(original) != rt.BoneGeometry: # 실제 뼈대가 아닌 경우 건너뛰기
|
266
265
|
continue
|
267
266
|
|
268
267
|
# 원본 뼈대의 시작점, 끝점, Z축 방향 가져오기
|
@@ -310,16 +309,24 @@ class Mirror:
|
|
310
309
|
reflection.wirecolor = original.wirecolor
|
311
310
|
|
312
311
|
created.append(reflection)
|
313
|
-
|
312
|
+
bone_mapping[original] = reflection
|
314
313
|
|
315
314
|
# 계층 구조 연결 (자식부터 상위로)
|
316
|
-
for i in range(len(created)-1,
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
315
|
+
for i in range(len(created)-1, -1, -1): # 인덱스 len(created)-1부터 0까지 역순으로 처리
|
316
|
+
original_bone = None
|
317
|
+
# 원본 뼈대 찾기
|
318
|
+
for orig, mirror in bone_mapping.items():
|
319
|
+
if mirror == created[i]:
|
320
|
+
original_bone = orig
|
321
|
+
break
|
322
|
+
|
323
|
+
if original_bone and original_bone.parent:
|
324
|
+
# 부모가 매핑에 있는지 확인
|
325
|
+
if original_bone.parent in bone_mapping:
|
326
|
+
created[i].parent = bone_mapping[original_bone.parent]
|
327
|
+
else:
|
328
|
+
# 부모가 미러링된 뼈대 중에 없으면 원본 부모 사용
|
329
|
+
created[i].parent = original_bone.parent
|
323
330
|
|
324
331
|
# 부모가 없는 뼈대는 위치 조정
|
325
332
|
for i in range(len(created)):
|
pyjallib/max/shoulder.py
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
어깨 모듈
|
6
|
+
자동으로 어깨 본을 생성.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from pymxs import runtime as rt
|
10
|
+
|
11
|
+
# Import necessary service classes for default initialization
|
12
|
+
from .name import Name
|
13
|
+
from .anim import Anim
|
14
|
+
from .helper import Helper
|
15
|
+
from .bone import Bone
|
16
|
+
from .constraint import Constraint
|
17
|
+
from .bip import Bip
|
18
|
+
|
19
|
+
from .boneChain import BoneChain
|
20
|
+
|
21
|
+
class Shoulder:
|
22
|
+
def __init__(self, nameService=None, animService=None, helperService=None, boneService=None, constraintService=None, bipService=None):
|
23
|
+
self.name = nameService if nameService else Name()
|
24
|
+
self.anim = animService if animService else Anim()
|
25
|
+
self.helper = helperService if helperService else Helper(nameService=self.name)
|
26
|
+
self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
|
27
|
+
self.const = constraintService if constraintService else Constraint(nameService=self.name)
|
28
|
+
self.bip = bipService if bipService else Bip(nameService=self.name, animService=self.anim)
|
29
|
+
|
30
|
+
self.boneSize = 2.0
|
31
|
+
|
32
|
+
self.genBones = []
|
33
|
+
self.genHelpers = []
|
34
|
+
self.clavicle = None
|
35
|
+
self.upperArm = None
|
36
|
+
self.autoClavicle = None
|
37
|
+
self.upperArmTwist = None
|
38
|
+
self.twistWeight = 0.7
|
39
|
+
|
40
|
+
self.shoulderLookAtTargetPosConstExpression = ""
|
41
|
+
self.shoulderLookAtTargetPosConstExpression += "local clavicleDistance = distance clavicle upperArm\n"
|
42
|
+
self.shoulderLookAtTargetPosConstExpression += "local xPos = clavicleDistance * distanceDir\n"
|
43
|
+
self.shoulderLookAtTargetPosConstExpression += "[xPos, 0.0, 0.0]\n"
|
44
|
+
|
45
|
+
def reset(self):
|
46
|
+
self.genBones = []
|
47
|
+
self.genHelpers = []
|
48
|
+
self.clavicle = None
|
49
|
+
self.upperArm = None
|
50
|
+
self.autoClavicle = None
|
51
|
+
self.upperArmTwist = None
|
52
|
+
self.twistWeight = 0.7
|
53
|
+
|
54
|
+
return self
|
55
|
+
|
56
|
+
def create_bones(self, inClavicle, inUpperArm, inAutoClavicle, inUpperArmTwist, inTwistWeight=0.7):
|
57
|
+
if not rt.isValidNode(inClavicle) or not rt.isValidNode(inUpperArm) or not rt.isValidNode(inAutoClavicle) or not rt.isValidNode(inUpperArmTwist):
|
58
|
+
return False
|
59
|
+
|
60
|
+
clavicleLength = rt.distance(inClavicle, inUpperArm)
|
61
|
+
facingDirVec = inUpperArm.transform.position - inClavicle.transform.position
|
62
|
+
inObjXAxisVec = inClavicle.objectTransform.row1
|
63
|
+
distanceDir = 1.0 if rt.dot(inObjXAxisVec, facingDirVec) > 0 else -1.0
|
64
|
+
clavicleLength *= distanceDir
|
65
|
+
|
66
|
+
genBones = []
|
67
|
+
genHelpers = []
|
68
|
+
|
69
|
+
# 자동 쇄골 이름 생성 및 뼈대 생성
|
70
|
+
shoulderName = self.name.replace_name_part("RealName", inClavicle.name, "Shoulder")
|
71
|
+
if inClavicle.name[0].islower():
|
72
|
+
shoulderName = shoulderName.lower()
|
73
|
+
|
74
|
+
shoulderBone = self.bone.create_nub_bone(shoulderName, self.boneSize)
|
75
|
+
shoulderBone.name = self.name.remove_name_part("Nub", shoulderBone.name)
|
76
|
+
shoulderBone.transform = inUpperArm.transform
|
77
|
+
shoulderBone.parent = inAutoClavicle
|
78
|
+
genBones.append(shoulderBone)
|
79
|
+
|
80
|
+
# 어깨 타겟 포인트 생성
|
81
|
+
shoulderLookAtTarget = self.helper.create_point(self.name.replace_name_part("Type", shoulderName, self.name.get_name_part_value_by_description("Type", "Target")))
|
82
|
+
shoulderLookAtTarget.transform = inUpperArm.transform
|
83
|
+
shoulderLookAtTarget.parent = inUpperArm
|
84
|
+
shoulderLookAtTargetPosConst = self.const.assign_pos_script_controller(shoulderLookAtTarget)
|
85
|
+
shoulderLookAtTargetPosConst.addConstant("distanceDir", distanceDir)
|
86
|
+
shoulderLookAtTargetPosConst.addNode("clavicle", inClavicle)
|
87
|
+
shoulderLookAtTargetPosConst.addNode("upperArm", inUpperArm)
|
88
|
+
shoulderLookAtTargetPosConst.setExpression(self.shoulderLookAtTargetPosConstExpression)
|
89
|
+
genHelpers.append(shoulderLookAtTarget)
|
90
|
+
|
91
|
+
# 어깨 회전 헬퍼 포인트 생성
|
92
|
+
shoulderLookAtHelper = self.helper.create_point(self.name.replace_name_part("Type", shoulderName, self.name.get_name_part_value_by_description("Type", "LookAt")))
|
93
|
+
shoulderLookAtHelper.transform = inClavicle.transform
|
94
|
+
shoulderLookAtHelper.parent = inClavicle
|
95
|
+
shoulderLookAtHelperPosConst = self.const.assign_pos_const(shoulderLookAtHelper, inClavicle)
|
96
|
+
|
97
|
+
lookAtConst = self.const.assign_lookat(shoulderLookAtHelper, shoulderLookAtTarget)
|
98
|
+
lookAtConst.upnode_world = False
|
99
|
+
lookAtConst.pickUpNode = inClavicle
|
100
|
+
lookAtConst.lookat_vector_length = 0.0
|
101
|
+
if distanceDir < 0:
|
102
|
+
lookAtConst.target_axisFlip = True
|
103
|
+
genHelpers.append(shoulderLookAtHelper)
|
104
|
+
|
105
|
+
# 어깨 최종 회전 헬퍼 포인트 생성
|
106
|
+
shoulderRotHelper = self.helper.create_point(self.name.replace_name_part("Type", shoulderName, self.name.get_name_part_value_by_description("Type", "Rotation")))
|
107
|
+
shoulderRotHelper.transform = inUpperArm.transform
|
108
|
+
shoulderRotHelper.parent = inUpperArm
|
109
|
+
shoulderRotHelperPosConst = self.const.assign_pos_const(shoulderRotHelper, inUpperArm)
|
110
|
+
|
111
|
+
shoulderRotHelperRotConst = self.const.assign_rot_const_multi(shoulderRotHelper, [shoulderLookAtHelper, inUpperArmTwist])
|
112
|
+
shoulderRotHelperRotConst.setWeight(1, 100.0-inTwistWeight)
|
113
|
+
shoulderRotHelperRotConst.setWeight(2, inTwistWeight)
|
114
|
+
genHelpers.append(shoulderRotHelper)
|
115
|
+
|
116
|
+
# 어깨 최종 회전 오프셋 헬퍼 포인트 생성
|
117
|
+
shoulderRotOffsetHelepr = self.helper.create_point(self.name.replace_name_part("Type", shoulderName, self.name.get_name_part_value_by_description("Type", "Dummy")))
|
118
|
+
shoulderRotOffsetHelepr.transform = inUpperArm.transform
|
119
|
+
shoulderRotOffsetHelepr.parent = shoulderRotHelper
|
120
|
+
genHelpers.append(shoulderRotOffsetHelepr)
|
121
|
+
|
122
|
+
# 어깨 본 회전 설정
|
123
|
+
shoulderBoneRotConst = self.const.assign_rot_const(shoulderBone, shoulderRotOffsetHelepr)
|
124
|
+
|
125
|
+
self.genBones = genBones
|
126
|
+
self.genHelpers = genHelpers
|
127
|
+
self.clavicle = inClavicle
|
128
|
+
self.upperArm = inUpperArm
|
129
|
+
self.autoClavicle = inAutoClavicle
|
130
|
+
self.upperArmTwist = inUpperArmTwist
|
131
|
+
self.twistWeight = inTwistWeight
|
132
|
+
|
133
|
+
result = {
|
134
|
+
"Bones": genBones,
|
135
|
+
"Helpers": genHelpers,
|
136
|
+
"SourceBones": [inClavicle, inUpperArm, inAutoClavicle, inUpperArmTwist],
|
137
|
+
"Parameters": [inTwistWeight]
|
138
|
+
}
|
139
|
+
|
140
|
+
self.reset()
|
141
|
+
|
142
|
+
rt.redrawViews()
|
143
|
+
|
144
|
+
return BoneChain.from_result(result)
|
145
|
+
|
146
|
+
def create_bones_from_chain(self, inBoneChain: BoneChain):
|
147
|
+
if not inBoneChain or inBoneChain.is_empty():
|
148
|
+
return None
|
149
|
+
|
150
|
+
inBoneChain.delete()
|
151
|
+
|
152
|
+
# BoneChain에서 필요한 정보 추출
|
153
|
+
sourceBones = inBoneChain.sourceBones
|
154
|
+
parameters = inBoneChain.parameters
|
155
|
+
|
156
|
+
# 필수 소스 본 확인
|
157
|
+
if len(sourceBones) < 4 or not rt.isValidNode(sourceBones[0]) or not rt.isValidNode(sourceBones[1]) or not rt.isValidNode(sourceBones[2]) or not rt.isValidNode(sourceBones[3]):
|
158
|
+
return None
|
159
|
+
|
160
|
+
# 파라미터 가져오기 (또는 기본값 사용)
|
161
|
+
twistWeight = parameters[0] if len(parameters) > 0 else 0.7
|
162
|
+
|
163
|
+
# 쇄골 생성
|
164
|
+
inClavicle = sourceBones[0]
|
165
|
+
inUpperArm = sourceBones[1]
|
166
|
+
inAutoClavicle = sourceBones[2]
|
167
|
+
inUpperArmTwist = sourceBones[3]
|
168
|
+
|
169
|
+
# 새로운 쇄골 생성
|
170
|
+
return self.create_bones(inClavicle, inUpperArm, inAutoClavicle, inUpperArmTwist, twistWeight)
|
171
|
+
|
172
|
+
|
173
|
+
|
pyjallib/max/twistBone.py
CHANGED
@@ -126,11 +126,6 @@ class TwistBone:
|
|
126
126
|
BoneChain: 생성된 트위스트 뼈대 BoneChain 객체
|
127
127
|
"""
|
128
128
|
limb = inObj
|
129
|
-
distance = rt.distance(limb, inChild)
|
130
|
-
facingDirVec = inChild.transform.position - inObj.transform.position
|
131
|
-
inObjXAxisVec = inObj.objectTransform.row1
|
132
|
-
distanceDir = 1.0 if rt.dot(inObjXAxisVec, facingDirVec) > 0 else -1.0
|
133
|
-
offssetAmount = (distance / twistNum) * distanceDir
|
134
129
|
|
135
130
|
boneChainArray = []
|
136
131
|
|
@@ -161,14 +156,17 @@ class TwistBone:
|
|
161
156
|
boneChainArray.append(twistBone)
|
162
157
|
|
163
158
|
if twistNum > 1:
|
159
|
+
weightVal = 100.0 / (twistNum-1)
|
160
|
+
posWeightVal = 100.0 / twistNum
|
161
|
+
|
164
162
|
lastBone = self.bone.create_nub_bone(boneName, 2)
|
165
163
|
lastBone.name = self.name.replace_name_part("Index", boneName, str(twistNum))
|
166
164
|
lastBone.name = self.name.remove_name_part("Nub", lastBone.name)
|
167
165
|
lastBone.transform = limb.transform
|
168
166
|
lastBone.parent = limb
|
169
|
-
self.
|
170
|
-
|
171
|
-
|
167
|
+
lastBonePosConst = self.const.assign_pos_const_multi(lastBone, [limb, inChild])
|
168
|
+
lastBonePosConst.setWeight(1, 100.0 - (posWeightVal*(twistNum-1)))
|
169
|
+
lastBonePosConst.setWeight(2, posWeightVal*(twistNum-1))
|
172
170
|
|
173
171
|
if twistNum > 2:
|
174
172
|
for i in range(1, twistNum-1):
|
@@ -177,7 +175,9 @@ class TwistBone:
|
|
177
175
|
twistExtraBone.name = self.name.remove_name_part("Nub", twistExtraBone.name)
|
178
176
|
twistExtraBone.transform = limb.transform
|
179
177
|
twistExtraBone.parent = limb
|
180
|
-
self.
|
178
|
+
twistExtraBonePosConst = self.const.assign_pos_const_multi(twistExtraBone, [limb, inChild])
|
179
|
+
twistExtraBonePosConst.setWeight(1, 100.0 - (posWeightVal*i))
|
180
|
+
twistExtraBonePosConst.setWeight(2, posWeightVal*i)
|
181
181
|
|
182
182
|
twistExtraBoneRotListController = self.const.assign_rot_list(twistExtraBone)
|
183
183
|
twistExtraBoneController = rt.Rotation_Script()
|
@@ -225,11 +225,8 @@ class TwistBone:
|
|
225
225
|
BoneChain: 생성된 트위스트 뼈대 BoneChain 객체
|
226
226
|
"""
|
227
227
|
limb = inChild
|
228
|
-
|
229
|
-
|
230
|
-
inObjXAxisVec = inObj.objectTransform.row1
|
231
|
-
distanceDir = 1.0 if rt.dot(inObjXAxisVec, facingDirVec) > 0 else -1.0
|
232
|
-
offssetAmount = (distance / twistNum) * distanceDir
|
228
|
+
|
229
|
+
posWeightVal = 100.0 / twistNum
|
233
230
|
|
234
231
|
boneChainArray = []
|
235
232
|
|
@@ -242,7 +239,10 @@ class TwistBone:
|
|
242
239
|
twistBone.name = self.name.remove_name_part("Nub", twistBone.name)
|
243
240
|
twistBone.transform = inObj.transform
|
244
241
|
twistBone.parent = inObj
|
245
|
-
self.
|
242
|
+
twistBonePosConst = self.const.assign_pos_const_multi(twistBone, [limb, inObj])
|
243
|
+
twistBonePosConst.setWeight(1, posWeightVal*(twistNum-1))
|
244
|
+
twistBonePosConst.setWeight(2, 100.0 - (posWeightVal*(twistNum-1)))
|
245
|
+
|
246
246
|
twistBoneLocalRefTM = limb.transform * rt.inverse(limb.parent.transform)
|
247
247
|
|
248
248
|
twistBoneRotListController = self.const.assign_rot_list(twistBone)
|
@@ -262,14 +262,13 @@ class TwistBone:
|
|
262
262
|
boneChainArray.append(twistBone)
|
263
263
|
|
264
264
|
if twistNum > 1:
|
265
|
+
weightVal = 100.0 / (twistNum-1)
|
266
|
+
|
265
267
|
lastBone = self.bone.create_nub_bone(boneName, 2)
|
266
268
|
lastBone.name = self.name.replace_name_part("Index", boneName, str(twistNum))
|
267
269
|
lastBone.name = self.name.remove_name_part("Nub", lastBone.name)
|
268
270
|
lastBone.transform = inObj.transform
|
269
271
|
lastBone.parent = inObj
|
270
|
-
self.anim.move_local(lastBone, 0, 0, 0)
|
271
|
-
|
272
|
-
weightVal = 100.0 / (twistNum-1)
|
273
272
|
|
274
273
|
if twistNum > 2:
|
275
274
|
for i in range(1, twistNum-1):
|
@@ -278,7 +277,9 @@ class TwistBone:
|
|
278
277
|
twistExtraBone.name = self.name.remove_name_part("Nub", twistExtraBone.name)
|
279
278
|
twistExtraBone.transform = inObj.transform
|
280
279
|
twistExtraBone.parent = inObj
|
281
|
-
self.
|
280
|
+
twistExtraBonePosConst = self.const.assign_pos_const_multi(twistExtraBone, [limb, inObj])
|
281
|
+
twistExtraBonePosConst.setWeight(1, 100.0 - (posWeightVal*(i+1)))
|
282
|
+
twistExtraBonePosConst.setWeight(2, posWeightVal*(i+1))
|
282
283
|
|
283
284
|
twistExtraBoneRotListController = self.const.assign_rot_list(twistExtraBone)
|
284
285
|
twistExtraBoneController = rt.Rotation_Script()
|
@@ -307,6 +308,8 @@ class TwistBone:
|
|
307
308
|
# 메소드 호출 후 데이터 초기화
|
308
309
|
self.reset()
|
309
310
|
|
311
|
+
rt.redrawViews()
|
312
|
+
|
310
313
|
return BoneChain.from_result(result)
|
311
314
|
|
312
315
|
def create_bones_from_chain(self, inBoneChain: BoneChain):
|