pyjallib 0.1.17__py3-none-any.whl → 0.1.20__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.
@@ -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
+
@@ -10,11 +10,23 @@ from PySide2 import QtWidgets, QtCore
10
10
  import gc
11
11
 
12
12
  class ToolManager:
13
+ """
14
+ 3DS Max에서 실행되는 도구(툴) 인스턴스를 통합 관리하는 클래스입니다.
15
+ 도구의 등록, 닫기, 표시 등 라이프사이클을 관리합니다.
16
+ """
13
17
  def __init__(self):
18
+ """
19
+ ToolManager 인스턴스를 초기화합니다.
20
+ """
14
21
  self.tools = {} # {tool_class_name: [instances]} 형태로 관리
15
22
 
16
23
  def register_tool(self, tool_instance):
17
- """도구 인스턴스를 등록합니다"""
24
+ """
25
+ 도구 인스턴스를 등록합니다.
26
+
27
+ Args:
28
+ tool_instance: 등록할 도구 인스턴스
29
+ """
18
30
  class_name = tool_instance.__class__.__name__
19
31
 
20
32
  if class_name not in self.tools:
@@ -23,7 +35,12 @@ class ToolManager:
23
35
  self.tools[class_name].append(tool_instance)
24
36
 
25
37
  def close_tool_by_type(self, tool_class):
26
- """특정 유형의 도구를 모두 닫습니다"""
38
+ """
39
+ 특정 클래스의 모든 도구 인스턴스를 닫고 정리합니다.
40
+
41
+ Args:
42
+ tool_class: 닫을 도구의 클래스
43
+ """
27
44
  class_name = tool_class.__name__
28
45
 
29
46
  if class_name not in self.tools:
@@ -66,13 +83,12 @@ class ToolManager:
66
83
  def show_tool(self, tool_class, **kwargs):
67
84
  """
68
85
  도구를 표시합니다. 중복 실행을 방지하고 항상 새 인스턴스를 생성합니다.
69
-
86
+
70
87
  Args:
71
- tool_class: 도구 클래스
88
+ tool_class: 표시할 도구 클래스
72
89
  **kwargs: 도구 클래스 생성자에 전달할 인자들
73
-
74
90
  Returns:
75
- 새로 생성된 도구 인스턴스
91
+ object: 새로 생성된 도구 인스턴스
76
92
  """
77
93
  # 기존 인스턴스 모두 정리
78
94
  self.close_tool_by_type(tool_class)