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.
Files changed (46) hide show
  1. pyjallib/__init__.py +4 -1
  2. pyjallib/exceptions.py +75 -0
  3. pyjallib/logger.py +288 -0
  4. pyjallib/max/__init__.py +8 -0
  5. pyjallib/max/autoClavicle.py +17 -5
  6. pyjallib/max/bip.py +0 -21
  7. pyjallib/max/bone.py +21 -1
  8. pyjallib/max/boneChain.py +2 -0
  9. pyjallib/max/constraint.py +27 -2
  10. pyjallib/max/elbow.py +105 -0
  11. pyjallib/max/groinBone.py +2 -0
  12. pyjallib/max/header.py +121 -113
  13. pyjallib/max/hip.py +2 -0
  14. pyjallib/max/inguinal.py +117 -0
  15. pyjallib/max/kneeBone.py +2 -0
  16. pyjallib/max/macro/jal_macro_bone.py +221 -8
  17. pyjallib/max/macro/jal_macro_constraint.py +30 -0
  18. pyjallib/max/mirror.py +20 -13
  19. pyjallib/max/shoulder.py +173 -0
  20. pyjallib/max/twistBone.py +22 -19
  21. pyjallib/max/volumeBone.py +12 -1
  22. pyjallib/max/wrist.py +113 -0
  23. pyjallib/nameToPath.py +78 -61
  24. pyjallib/naming.py +4 -1
  25. pyjallib/perforce.py +196 -347
  26. pyjallib/progressEvent.py +75 -0
  27. pyjallib/ue5/ConfigFiles/UE5NamingConfig.json +487 -0
  28. pyjallib/ue5/__init__.py +170 -0
  29. pyjallib/ue5/disableInterchangeFrameWork.py +82 -0
  30. pyjallib/ue5/inUnreal/__init__.py +95 -0
  31. pyjallib/ue5/inUnreal/animationImporter.py +206 -0
  32. pyjallib/ue5/inUnreal/baseImporter.py +187 -0
  33. pyjallib/ue5/inUnreal/importerSettings.py +179 -0
  34. pyjallib/ue5/inUnreal/skeletalMeshImporter.py +112 -0
  35. pyjallib/ue5/inUnreal/skeletonImporter.py +105 -0
  36. pyjallib/ue5/logger.py +241 -0
  37. pyjallib/ue5/templateProcessor.py +287 -0
  38. pyjallib/ue5/templates/__init__.py +109 -0
  39. pyjallib/ue5/templates/animImportTemplate.py +22 -0
  40. pyjallib/ue5/templates/batchAnimImportTemplate.py +21 -0
  41. pyjallib/ue5/templates/skeletalMeshImportTemplate.py +22 -0
  42. pyjallib/ue5/templates/skeletonImportTemplate.py +21 -0
  43. {pyjallib-0.1.16.dist-info → pyjallib-0.1.19.dist-info}/METADATA +1 -1
  44. pyjallib-0.1.19.dist-info/RECORD +72 -0
  45. pyjallib-0.1.16.dist-info/RECORD +0 -49
  46. {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("L")
51
- self.side_r_radio = QtWidgets.QRadioButton("R")
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("F")
63
- self.front_b_radio = QtWidgets.QRadioButton("B")
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 = "L"
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 = "R"
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 = "F"
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 = "B"
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
- parents.append(reflection)
312
+ bone_mapping[original] = reflection
314
313
 
315
314
  # 계층 구조 연결 (자식부터 상위로)
316
- for i in range(len(created)-1, 0, -1):
317
- pIndex = bones.index(bones[i].parent) if bones[i].parent in bones else 0
318
- if pIndex != 0:
319
- created[i].parent = parents[pIndex]
320
-
321
- # 루트 뼈대의 부모 설정
322
- created[0].parent = bones[0].parent
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)):
@@ -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.anim.move_local(lastBone, offssetAmount*(twistNum-1), 0, 0)
170
-
171
- weightVal = 100.0 / (twistNum-1)
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.anim.move_local(twistExtraBone, offssetAmount*i, 0, 0)
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
- distance = rt.distance(inObj, inChild)
229
- facingDirVec = inChild.transform.position - inObj.transform.position
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.anim.move_local(twistBone, offssetAmount*(twistNum-1), 0, 0)
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.anim.move_local(twistExtraBone, offssetAmount*(twistNum-1-i), 0, 0)
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):