pyjallib 0.1.4__tar.gz → 0.1.6__tar.gz

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 (53) hide show
  1. {pyjallib-0.1.4 → pyjallib-0.1.6}/PKG-INFO +1 -1
  2. {pyjallib-0.1.4 → pyjallib-0.1.6}/pyproject.toml +1 -1
  3. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/__init__.py +1 -1
  4. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/ConfigFiles/3DSMaxNamingConfig.json +43 -3
  5. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/__init__.py +5 -1
  6. pyjallib-0.1.6/src/pyjallib/max/autoClavicle.py +122 -0
  7. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/bip.py +2 -4
  8. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/constraint.py +4 -1
  9. pyjallib-0.1.6/src/pyjallib/max/groinBone.py +90 -0
  10. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/header.py +5 -1
  11. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/helper.py +54 -0
  12. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_helper.py +33 -5
  13. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/twistBone.py +128 -2
  14. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/naming.py +5 -4
  15. pyjallib-0.1.6/tests/autoclavicle.ms +47 -0
  16. pyjallib-0.1.6/tests/autoclavicleTest.py +24 -0
  17. {pyjallib-0.1.4 → pyjallib-0.1.6}/.gitignore +0 -0
  18. {pyjallib-0.1.4 → pyjallib-0.1.6}/.python-version +0 -0
  19. {pyjallib-0.1.4 → pyjallib-0.1.6}/README.md +0 -0
  20. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/.nojekyll +0 -0
  21. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/index.html +0 -0
  22. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/max.html +0 -0
  23. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/namePart.html +0 -0
  24. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/nameToPath.html +0 -0
  25. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/naming.html +0 -0
  26. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/namingConfig.html +0 -0
  27. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/perforce.html +0 -0
  28. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/pymxs.html +0 -0
  29. {pyjallib-0.1.4 → pyjallib-0.1.6}/docs/reloadModules.html +0 -0
  30. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/ConfigFiles/namingConfig.json +0 -0
  31. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/align.py +0 -0
  32. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/anim.py +0 -0
  33. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/bone.py +0 -0
  34. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/layer.py +0 -0
  35. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/link.py +0 -0
  36. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_align.py +0 -0
  37. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_bone.py +0 -0
  38. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_constraint.py +0 -0
  39. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_link.py +0 -0
  40. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/macro/jal_macro_select.py +0 -0
  41. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/mirror.py +0 -0
  42. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/name.py +0 -0
  43. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/select.py +0 -0
  44. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/max/skin.py +0 -0
  45. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/namePart.py +0 -0
  46. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/nameToPath.py +0 -0
  47. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/namingConfig.py +0 -0
  48. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/perforce.py +0 -0
  49. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/py.typed +0 -0
  50. {pyjallib-0.1.4 → pyjallib-0.1.6}/src/pyjallib/reloadModules.py +0 -0
  51. {pyjallib-0.1.4 → pyjallib-0.1.6}/tests/globalVarTest.py +0 -0
  52. {pyjallib-0.1.4 → pyjallib-0.1.6}/tests/moduleImportTest.py +0 -0
  53. {pyjallib-0.1.4 → pyjallib-0.1.6}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyjallib
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: A utility library for 3D game character development pipelines.
5
5
  Author-email: Dongseok Kim <jalnagakds@gmail.com>
6
6
  Requires-Python: >=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyjallib"
3
- version = "0.1.4"
3
+ version = "0.1.6"
4
4
  description = "A utility library for 3D game character development pipelines."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -6,7 +6,7 @@ pyjallib Package
6
6
  Python library for game character development pipeline.
7
7
  """
8
8
 
9
- __version__ = '0.1.2'
9
+ __version__ = '0.1.6'
10
10
 
11
11
  # reload_modules 함수를 패키지 레벨에서 사용 가능하게 함
12
12
  from .namePart import NamePart, NamePartType
@@ -25,6 +25,10 @@
25
25
  "SkinBone",
26
26
  "Biped"
27
27
  ],
28
+ "koreanDescriptions": [
29
+ "",
30
+ ""
31
+ ],
28
32
  "isDirection": false
29
33
  },
30
34
  {
@@ -34,14 +38,22 @@
34
38
  "Dum",
35
39
  "Exp",
36
40
  "IK",
37
- "T"
41
+ "T",
42
+ "Rot",
43
+ "Pos",
44
+ "Lat",
45
+ "UpN"
38
46
  ],
39
47
  "weights": [
40
48
  5,
41
49
  10,
42
50
  15,
43
51
  20,
44
- 25
52
+ 25,
53
+ 30,
54
+ 35,
55
+ 40,
56
+ 45
45
57
  ],
46
58
  "type": "PREFIX",
47
59
  "descriptions": [
@@ -49,7 +61,22 @@
49
61
  "Dummy",
50
62
  "ExposeTM",
51
63
  "IK",
52
- "Target"
64
+ "Target",
65
+ "Rotation",
66
+ "Position",
67
+ "LookAt",
68
+ "UpNode"
69
+ ],
70
+ "koreanDescriptions": [
71
+ "",
72
+ "",
73
+ "",
74
+ "",
75
+ "",
76
+ "",
77
+ "",
78
+ "",
79
+ ""
53
80
  ],
54
81
  "isDirection": false
55
82
  },
@@ -68,6 +95,10 @@
68
95
  "Left",
69
96
  "Right"
70
97
  ],
98
+ "koreanDescriptions": [
99
+ "",
100
+ ""
101
+ ],
71
102
  "isDirection": true
72
103
  },
73
104
  {
@@ -85,6 +116,10 @@
85
116
  "Front",
86
117
  "Back"
87
118
  ],
119
+ "koreanDescriptions": [
120
+ "",
121
+ ""
122
+ ],
88
123
  "isDirection": true
89
124
  },
90
125
  {
@@ -93,6 +128,7 @@
93
128
  "weights": [],
94
129
  "type": "REALNAME",
95
130
  "descriptions": [],
131
+ "koreanDescriptions": [],
96
132
  "isDirection": false
97
133
  },
98
134
  {
@@ -101,6 +137,7 @@
101
137
  "weights": [],
102
138
  "type": "INDEX",
103
139
  "descriptions": [],
140
+ "koreanDescriptions": [],
104
141
  "isDirection": false
105
142
  },
106
143
  {
@@ -115,6 +152,9 @@
115
152
  "descriptions": [
116
153
  "Nub"
117
154
  ],
155
+ "koreanDescriptions": [
156
+ ""
157
+ ],
118
158
  "isDirection": false
119
159
  }
120
160
  ]
@@ -26,6 +26,8 @@ from .bip import Bip
26
26
  from .skin import Skin
27
27
 
28
28
  from .twistBone import TwistBone
29
+ from .groinBone import GroinBone
30
+ from .autoClavicle import AutoClavicle
29
31
 
30
32
  # 모듈 내보내기
31
33
  __all__ = [
@@ -42,5 +44,7 @@ __all__ = [
42
44
  'Link',
43
45
  'Bip',
44
46
  'Skin',
45
- 'TwistBone'
47
+ 'TwistBone',
48
+ 'GroinBone',
49
+ 'AutoClavicle'
46
50
  ]
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 자동 쇄골(AutoClavicle) 모듈 - 3ds Max용 자동화된 쇄골 기능 제공
6
+ 원본 MAXScript의 autoclavicle.ms를 Python으로 변환하였으며, pymxs 모듈 기반으로 구현됨
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
+
20
+ class AutoClavicle:
21
+ """
22
+ 자동 쇄골(AutoClavicle) 관련 기능을 제공하는 클래스.
23
+ MAXScript의 _AutoClavicleBone 구조체 개념을 Python으로 재구현한 클래스이며,
24
+ 3ds Max의 기능들을 pymxs API를 통해 제어합니다.
25
+ """
26
+
27
+ def __init__(self, nameService=None, animService=None, helperService=None, boneService=None, constraintService=None, bipService=None):
28
+ """
29
+ 클래스 초기화
30
+
31
+ Args:
32
+ nameService: 이름 처리 서비스 (제공되지 않으면 새로 생성)
33
+ animService: 애니메이션 서비스 (제공되지 않으면 새로 생성)
34
+ helperService: 헬퍼 객체 서비스 (제공되지 않으면 새로 생성)
35
+ boneService: 뼈대 서비스 (제공되지 않으면 새로 생성)
36
+ constraintService: 제약 서비스 (제공되지 않으면 새로 생성)
37
+ bipService: Biped 서비스 (제공되지 않으면 새로 생성)
38
+ """
39
+ self.name = nameService if nameService else Name()
40
+ self.anim = animService if animService else Anim()
41
+ self.helper = helperService if helperService else Helper(nameService=self.name)
42
+ self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim, helperService=self.helper)
43
+ self.const = constraintService if constraintService else Constraint(nameService=self.name, helperService=self.helper)
44
+ self.bip = bipService if bipService else Bip(animService=self.anim, nameService=self.name, boneService=self.bone)
45
+
46
+ self.bone_size = 2.0
47
+
48
+ def create_bones(self, inClavicle, inUpperArm, liftScale=0.8):
49
+ """
50
+ 자동 쇄골 뼈를 생성하고 설정합니다.
51
+
52
+ Args:
53
+ inClavicle: 쇄골 뼈 객체
54
+ inUpperArm: 상완 뼈 객체
55
+ liftScale: 들어올림 스케일 (기본값: 0.8)
56
+
57
+ Returns:
58
+ 생성된 자동 쇄골 뼈대 배열
59
+ """
60
+ # 쇄골과 상완 사이의 거리 계산
61
+ clavicleLength = rt.distance(inClavicle, inUpperArm)
62
+
63
+ # 임시 헬퍼 포인트 생성
64
+ tempHelperA = rt.Point()
65
+ tempHelperB = rt.Point()
66
+ tempHelperA.transform = inClavicle.transform
67
+ tempHelperB.transform = inClavicle.transform
68
+ self.anim.move_local(tempHelperB, clavicleLength/2.0, 0.0, 0.0)
69
+
70
+ # 자동 쇄골 이름 생성 및 뼈대 생성
71
+ autoClavicleName = self.name.replace_name_part("RealName", inClavicle.name, "AutoClavicle")
72
+ autoClavicleBones = self.bone.create_bone(
73
+ [tempHelperA, tempHelperB],
74
+ autoClavicleName,
75
+ end=True,
76
+ delPoint=True,
77
+ parent=False,
78
+ size=self.bone_size
79
+ )
80
+ autoClavicleBones[0].transform = inClavicle.transform
81
+ self.anim.move_local(autoClavicleBones[0], clavicleLength/2.0, 0.0, 0.0)
82
+ autoClavicleBones[0].parent = inClavicle
83
+
84
+ # LookAt 설정
85
+ ikGoal = self.helper.create_point(autoClavicleName, boxToggle=False, crossToggle=True)
86
+ ikGoal.transform = autoClavicleBones[1].transform
87
+ ikGoal.name = self.name.replace_name_part("Type", autoClavicleName, "T")
88
+ autClavicleLookAtConst = self.const.assign_lookat(autoClavicleBones[0], ikGoal)
89
+ autClavicleLookAtConst.upnode_world = False
90
+ autClavicleLookAtConst.pickUpNode = inClavicle
91
+ autClavicleLookAtConst.lookat_vector_length = 0.0
92
+
93
+ # 회전 헬퍼 포인트 생성
94
+ autoClavicleRotHelper = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, "Rot"))
95
+ autoClavicleRotHelper.transform = autoClavicleBones[0].transform
96
+ autoClavicleRotHelper.parent = inClavicle
97
+
98
+ # 타겟 헬퍼 포인트 생성 (쇄골과 상완용)
99
+ rotTargetClavicle = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, "T"))
100
+ rotTargetClavicle.transform = inClavicle.transform
101
+ self.anim.move_local(rotTargetClavicle, clavicleLength, 0.0, 0.0)
102
+
103
+ rotTargetUpperArm = self.helper.create_point(self.name.replace_name_part("Type", autoClavicleName, "T"))
104
+ rotTargetUpperArm.name = self.name.add_suffix_to_real_name(rotTargetUpperArm.name, "UArm")
105
+ rotTargetUpperArm.transform = inUpperArm.transform
106
+ self.anim.move_local(rotTargetUpperArm, (clavicleLength/2.0)*liftScale, 0.0, 0.0)
107
+
108
+ # 부모 설정
109
+ rotTargetClavicle.parent = inClavicle
110
+ rotTargetUpperArm.parent = inUpperArm
111
+
112
+ # LookAt 제약 설정
113
+ # self.const.assign_lookat_multi(autoClavicleRotHelper, [rotTargetClavicle, rotTargetUpperArm])
114
+ lookAtConst = self.const.assign_scripted_lookat(autoClavicleRotHelper, [rotTargetClavicle, rotTargetUpperArm])["lookAt"]
115
+
116
+ lookAtConst.upnode_world = False
117
+ lookAtConst.pickUpNode = inClavicle
118
+ lookAtConst.lookat_vector_length = 0.0
119
+
120
+ ikGoal.parent = autoClavicleRotHelper
121
+
122
+ return autoClavicleBones
@@ -436,12 +436,11 @@ class Bip:
436
436
  if colNum > 0:
437
437
  rt.biped.deleteAllCopyCollections(inBipRoot.controller)
438
438
 
439
- def link_base_skeleton(self):
439
+ def link_base_skeleton(self, skinBoneBaseName="b"):
440
440
  """
441
441
  기본 스켈레톤 링크 (Biped와 일반 뼈대 연결)
442
442
  """
443
443
  rt.setWaitCursor()
444
- skinBoneBaseName = "b"
445
444
 
446
445
  bipSkel = self.get_bips()
447
446
  baseSkel = [None] * len(bipSkel)
@@ -473,12 +472,11 @@ class Bip:
473
472
 
474
473
  rt.setArrowCursor()
475
474
 
476
- def unlink_base_skeleton(self):
475
+ def unlink_base_skeleton(self, skinBoneBaseName="b"):
477
476
  """
478
477
  기본 스켈레톤 링크 해제
479
478
  """
480
479
  rt.setWaitCursor()
481
- skinBoneBaseName = "b"
482
480
 
483
481
  bipSkel = self.get_bips()
484
482
  baseSkel = [None] * len(bipSkel)
@@ -675,7 +675,8 @@ class Constraint:
675
675
  # 객체 이름 생성
676
676
  if self.name:
677
677
  objName = self.name.get_string(oriObj.name)
678
- indexNum = self.name.get_index_as_digit(oriObj.name)
678
+ indexVal = self.name.get_index_as_digit(oriObj.name)
679
+ indexNum = 0 if indexVal is False else indexVal
679
680
  dummyName = self.name.add_prefix_to_real_name(objName, self.name.get_dummy_value())
680
681
 
681
682
  lookAtPointName = self.name.replace_Index(dummyName, str(indexNum))
@@ -752,6 +753,8 @@ class Constraint:
752
753
  x_controller.Update()
753
754
  y_controller.Update()
754
755
  z_controller.Update()
756
+
757
+ return {"lookAt":lookAtPoint_rot_controller, "x":x_controller, "y":y_controller, "z":z_controller}
755
758
 
756
759
  def assign_attachment(self, inPlacedObj, inSurfObj, bAlign=False, shiftAxis=(0, 0, 1), shiftAmount=3.0):
757
760
  """
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 고간 부 본 모듈 - 3ds Max용 트위스트 뼈대 생성 관련 기능 제공
6
+ """
7
+
8
+ from pymxs import runtime as rt
9
+
10
+ # Import necessary service classes for default initialization
11
+ from .name import Name
12
+ from .anim import Anim
13
+ from .constraint import Constraint
14
+ from .bip import Bip
15
+ from .bone import Bone
16
+ from .helper import Helper
17
+ from .twistBone import TwistBone
18
+
19
+ class GroinBone:
20
+ """
21
+ 고간 부 본 관련 기능을 위한 클래스
22
+ 3DS Max에서 고간 부 본을 생성하고 관리하는 기능을 제공합니다.
23
+ """
24
+
25
+ def __init__(self, nameService=None, animService=None, helperService=None, constService=None, bipService=None, boneService=None, twistBoneService=None):
26
+ """
27
+ 클래스 초기화.
28
+
29
+ Args:
30
+ nameService: 이름 처리 서비스 (제공되지 않으면 새로 생성)
31
+ animService: 애니메이션 서비스 (제공되지 않으면 새로 생성)
32
+ constService: 제약 서비스 (제공되지 않으면 새로 생성)
33
+ bipService: 바이페드 서비스 (제공되지 않으면 새로 생성)
34
+ """
35
+ self.name = nameService if nameService else Name()
36
+ self.anim = animService if animService else Anim()
37
+ # Ensure dependent services use the potentially newly created instances
38
+ self.const = constService if constService else Constraint(nameService=self.name)
39
+ self.bip = bipService if bipService else Bip(animService=self.anim, nameService=self.name)
40
+ self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
41
+ self.twistBone = twistBoneService if twistBoneService else TwistBone(nameService=self.name, animService=self.anim, constService=self.const, bipService=self.bip, boneService=self.bone)
42
+ self.helper = helperService if helperService else Helper(nameService=self.name)
43
+
44
+ def create_bone(self, inObj):
45
+ """
46
+ 고간 부 본을 생성하는 메소드.
47
+
48
+ Args:
49
+ name: 생성할 본의 이름
50
+ parent: 부모 본 (제공되지 않으면 None)
51
+
52
+ Returns:
53
+ 생성된 본 객체
54
+ """
55
+ if self.bip.is_biped_object(inObj) == False:
56
+ rt.messageBox("This is not a biped object.")
57
+ return False
58
+
59
+ bipObj = self.bip.get_com(inObj)
60
+
61
+ lThigh = self.bip.get_grouped_nodes(inObj, "lLeg")[0]
62
+ rThigh = self.bip.get_grouped_nodes(inObj, "rLeg")[0]
63
+ pelvis = self.bip.get_grouped_nodes(inObj, "pelvis")[0]
64
+
65
+ lThighTwists = self.twistBone.get_thigh_type(lThigh)
66
+ rThighTwists = self.twistBone.get_thigh_type(rThigh)
67
+
68
+ if len(lThighTwists) == 0 or len(rThighTwists) == 0:
69
+ rt.messageBox("There is no twist bone.")
70
+ return False
71
+
72
+ pelvisHelper = self.helper.create_point(bipObj.name + " Dum Groin 00")
73
+ pelvisHelper.transform = bipObj.transform
74
+ self.anim.rotate_local(pelvisHelper, 90, 0, 0)
75
+ self.anim.rotate_local(pelvisHelper, 0, 0, -90)
76
+ pelvisHelper.parent = pelvis
77
+ self.helper.set_shape_to_box(pelvisHelper)
78
+
79
+ groinBones = self.bone.create_simple_bone(3.0, bipObj.name +" Groin 00", size=2)
80
+ groinBones[0].transform = pelvisHelper.transform
81
+ groinBones[0].parent = pelvis
82
+
83
+ self.const.assign_rot_const_multi(groinBones[0], [pelvisHelper, lThigh, rThigh])
84
+ rotConst = self.const.get_rot_list_controller(groinBones[0])[1]
85
+ rotConst.setWeight(1, 40.0)
86
+ rotConst.setWeight(2, 30.0)
87
+ rotConst.setWeight(3, 30.0)
88
+
89
+
90
+
@@ -25,6 +25,8 @@ from .bip import Bip
25
25
  from .skin import Skin
26
26
 
27
27
  from .twistBone import TwistBone
28
+ from .groinBone import GroinBone
29
+ from .autoClavicle import AutoClavicle
28
30
 
29
31
  class Header:
30
32
  """
@@ -63,7 +65,9 @@ class Header:
63
65
  self.bip = Bip(animService=self.anim, nameService=self.name, boneService=self.bone)
64
66
  self.skin = Skin()
65
67
 
66
- self.twistBone = TwistBone(nameService=self.name, animService=self.anim, constService=self.constraint, bipService=self.bip)
68
+ self.twistBone = TwistBone(nameService=self.name, animService=self.anim, constService=self.constraint, bipService=self.bip, boneService=self.bone)
69
+ self.groinBone = GroinBone(nameService=self.name, animService=self.anim, helperService=self.helper, constService=self.constraint, bipService=self.bip, boneService=self.bone, twistBoneService=self.twistBone)
70
+ self.autoClavicle = AutoClavicle(nameService=self.name, animService=self.anim, helperService=self.helper, boneService=self.bone, constraintService=self.constraint, bipService=self.bip)
67
71
 
68
72
  # 모듈 레벨에서 전역 인스턴스 생성
69
73
  jal = Header.get_instance()
@@ -431,3 +431,57 @@ class Helper:
431
431
  inObj.centermarker = False
432
432
  inObj.axistripod = False
433
433
  inObj.cross = False
434
+
435
+ def get_shape(self, inObj):
436
+ """
437
+ 헬퍼 객체의 시각적 형태 속성을 가져옵니다.
438
+ inObj (object): 형태 정보를 가져올 대상 3ds Max 헬퍼 객체.
439
+ dict: 헬퍼의 형태 속성을 나타내는 딕셔너리.
440
+ - "size" (float): 크기
441
+ - "centermarker" (bool): 센터 마커 활성화 여부
442
+ - "axistripod" (bool): 축 삼각대 활성화 여부
443
+ - "cross" (bool): 십자 표시 활성화 여부
444
+ - "box" (bool): 박스 표시 활성화 여부
445
+ `inObj`가 `rt.ExposeTm` 또는 `rt.Point` 타입의 객체인 경우 해당 객체의
446
+ 속성값을 반영하며, 그렇지 않은 경우 미리 정의된 기본값을 반환합니다.
447
+ """
448
+ returnDict = {
449
+ "size": 2.0,
450
+ "centermarker": False,
451
+ "axistripod": False,
452
+ "cross": True,
453
+ "box": False
454
+ }
455
+ if rt.classOf(inObj) == rt.ExposeTm or rt.classOf(inObj) == rt.Point:
456
+ returnDict["size"] = inObj.size
457
+ returnDict["centermarker"] = inObj.centermarker
458
+ returnDict["axistripod"] = inObj.axistripod
459
+ returnDict["cross"] = inObj.cross
460
+ returnDict["box"] = inObj.box
461
+
462
+ return returnDict
463
+
464
+ def set_shape(self, inObj, inShapeDict):
465
+ """
466
+ 헬퍼 객체의 표시 형태를 설정합니다.
467
+ `rt.ExposeTm` 또는 `rt.Point` 타입의 헬퍼 객체에 대해 크기, 센터 마커, 축 삼각대, 십자, 박스 표시 여부를 설정합니다.
468
+ inObj (rt.ExposeTm | rt.Point): 설정을 적용할 헬퍼 객체입니다.
469
+ inShapeDict (dict): 헬퍼의 형태를 정의하는 딕셔너리입니다.
470
+ 다음 키와 값을 포함해야 합니다:
471
+ - "size" (float | int): 헬퍼의 크기.
472
+ - "centermarker" (bool): 센터 마커 표시 여부 (True/False).
473
+ - "axistripod" (bool): 축 삼각대(axis tripod) 표시 여부 (True/False).
474
+ - "cross" (bool): 십자(cross) 표시 여부 (True/False).
475
+ - "box" (bool): 박스(box) 표시 여부 (True/False).
476
+ rt.ExposeTm | rt.Point | None: 형태가 설정된 객체를 반환합니다.
477
+ 만약 `inObj`가 `rt.ExposeTm` 또는 `rt.Point` 타입이 아닐 경우,
478
+ 아무 작업도 수행하지 않고 `None`을 반환합니다.
479
+ """
480
+ if rt.classOf(inObj) == rt.ExposeTm or rt.classOf(inObj) == rt.Point:
481
+ inObj.size = inShapeDict["size"]
482
+ inObj.centermarker = inShapeDict["centermarker"]
483
+ inObj.axistripod = inShapeDict["axistripod"]
484
+ inObj.cross = inShapeDict["cross"]
485
+ inObj.box = inShapeDict["box"]
486
+
487
+ return inObj
@@ -15,7 +15,7 @@ class HelperTypeSelDialog(QtWidgets.QDialog):
15
15
  self.changeHelperType = False
16
16
 
17
17
  self.setWindowTitle("Helper Type")
18
- self.setMinimumWidth(100)
18
+ self.setMinimumWidth(150)
19
19
 
20
20
  self.layout = QtWidgets.QVBoxLayout(self)
21
21
 
@@ -41,6 +41,7 @@ class HelperTypeSelDialog(QtWidgets.QDialog):
41
41
  class ModifyHelperShapeDialog(QtWidgets.QDialog):
42
42
  def __init__(self, parent=QtWidgets.QWidget.find(rt.windows.getMAXHWND())):
43
43
  super(ModifyHelperShapeDialog, self).__init__(parent)
44
+ self.defaultHelperShapes = []
44
45
  self.helperArray = []
45
46
 
46
47
  self.setWindowTitle("Modify Helper Shape")
@@ -55,6 +56,8 @@ class ModifyHelperShapeDialog(QtWidgets.QDialog):
55
56
  self.size_spinbox = QtWidgets.QDoubleSpinBox()
56
57
  self.size_spinbox.setValue(1.0) # Default value
57
58
  self.size_spinbox.setSingleStep(0.1)
59
+ # Install event filter for mouse tracking
60
+ self.size_spinbox.installEventFilter(self)
58
61
  sizeLayout.addWidget(sizeLabel)
59
62
  sizeLayout.addWidget(self.size_spinbox)
60
63
 
@@ -62,6 +65,8 @@ class ModifyHelperShapeDialog(QtWidgets.QDialog):
62
65
  self.add_spinbox = QtWidgets.QDoubleSpinBox()
63
66
  self.add_spinbox.setValue(0.0) # Default value
64
67
  self.add_spinbox.setSingleStep(0.1)
68
+ # Install event filter for mouse tracking
69
+ self.add_spinbox.installEventFilter(self)
65
70
  addLayout.addWidget(addLabel)
66
71
  addLayout.addWidget(self.add_spinbox)
67
72
 
@@ -91,6 +96,7 @@ class ModifyHelperShapeDialog(QtWidgets.QDialog):
91
96
  self.layout.addWidget(shapeGroup) # Add the group box to the layout instead of the raw radioLayout
92
97
  self.layout.addLayout(buttonLayout)
93
98
 
99
+ # Replace incorrect stepUp/stepDown connections with proper valueChanged connections
94
100
  self.size_spinbox.valueChanged.connect(self.change_helper_size)
95
101
  self.add_spinbox.valueChanged.connect(self.add_helper_size)
96
102
  self.add_spinbox.editingFinished.connect(self.reset_add_spinbox)
@@ -100,8 +106,10 @@ class ModifyHelperShapeDialog(QtWidgets.QDialog):
100
106
  self.radio_axis.toggled.connect(self.change_helper_shape)
101
107
  self.radio_center.toggled.connect(self.change_helper_shape)
102
108
 
103
- self.ok_button.clicked.connect(self.accept)
104
- self.cancel_button.clicked.connect(self.reject)
109
+ # Make the dialog apply changes immediately without needing to click OK
110
+ # by removing the requirement for explicit acceptance
111
+ self.ok_button.clicked.connect(self.close) # Just close instead of accept
112
+ self.cancel_button.clicked.connect(self.restore_helper_shape)
105
113
 
106
114
  def change_helper_size(self):
107
115
  if len(self.helperArray) == 0:
@@ -131,6 +139,21 @@ class ModifyHelperShapeDialog(QtWidgets.QDialog):
131
139
  elif self.radio_center.isChecked():
132
140
  jal.helper.set_shape_to_center(obj)
133
141
 
142
+ def save_helper_shape(self):
143
+ if len(self.helperArray) == 0:
144
+ return
145
+ for obj in self.helperArray:
146
+ shape = jal.helper.get_shape(obj)
147
+ self.defaultHelperShapes.append(shape)
148
+
149
+ def restore_helper_shape(self):
150
+ if len(self.helperArray) == 0 or len(self.defaultHelperShapes) == 0:
151
+ return
152
+ for i, obj in enumerate(self.helperArray):
153
+ jal.helper.set_shape(obj, self.defaultHelperShapes[i])
154
+
155
+ self.reject() # Close the dialog without accepting
156
+
134
157
  def jal_create_parentHelper():
135
158
  jal.helper.create_parent_helper()
136
159
 
@@ -229,17 +252,22 @@ def jal_modify_helperShape():
229
252
 
230
253
  # Assuming the first selected object is the one to modify
231
254
  helperObj = helperArray[0]
255
+ helperObjShape = jal.helper.get_shape(helperObj)
232
256
 
233
257
  modDialog = ModifyHelperShapeDialog()
234
258
 
235
259
  # Set initial values from the selected helper (if possible)
236
260
  modDialog.size_spinbox.setValue(helperObj.size)
261
+ modDialog.radio_box.setChecked(helperObjShape["box"])
262
+ modDialog.radio_cross.setChecked(helperObjShape["cross"])
263
+ modDialog.radio_axis.setChecked(helperObjShape["axistripod"])
264
+ modDialog.radio_center.setChecked(helperObjShape["centermarker"])
237
265
 
238
266
  modDialog.helperArray = helperArray
267
+ modDialog.save_helper_shape()
239
268
 
240
- result = modDialog.exec_()
269
+ modDialog.show()
241
270
 
242
- modDialog.deleteLater()
243
271
  modDialog = None
244
272
  gc.collect()
245
273
 
@@ -13,6 +13,7 @@ from .name import Name
13
13
  from .anim import Anim
14
14
  from .constraint import Constraint
15
15
  from .bip import Bip
16
+ from .bone import Bone
16
17
 
17
18
 
18
19
  class TwistBone:
@@ -22,7 +23,7 @@ class TwistBone:
22
23
  3ds Max의 기능들을 pymxs API를 통해 제어합니다.
23
24
  """
24
25
 
25
- def __init__(self, nameService=None, animService=None, constService=None, bipService=None):
26
+ def __init__(self, nameService=None, animService=None, constService=None, bipService=None, boneService=None):
26
27
  """
27
28
  클래스 초기화.
28
29
 
@@ -37,6 +38,7 @@ class TwistBone:
37
38
  # Ensure dependent services use the potentially newly created instances
38
39
  self.const = constService if constService else Constraint(nameService=self.name)
39
40
  self.bip = bipService if bipService else Bip(animService=self.anim, nameService=self.name) # Pass potentially new instances
41
+ self.bone = boneService if boneService else Bone(nameService=self.name, animService=self.anim)
40
42
 
41
43
  # 표현식 초기화
42
44
  self._init_expressions()
@@ -415,4 +417,128 @@ class TwistBone:
415
417
  굽힘(Bend) 타입의 트위스트 뼈대 생성
416
418
  (아직 구현되지 않음)
417
419
  """
418
- pass
420
+ pass
421
+
422
+ def get_upperArm_type(self, inObj):
423
+ """
424
+ 상완(Upper Arm) 타입의 트위스트 뼈대 가져오기
425
+
426
+ Args:
427
+ inObj: 상완 뼈대 객체
428
+
429
+ Returns:
430
+ 상완 타입의 트위스트 뼈대 또는 False(실패 시)
431
+ """
432
+ returnVal = []
433
+
434
+ parentBipObj = None
435
+
436
+ if not self.bip.is_biped_object(inObj):
437
+ return returnVal
438
+
439
+ if self.bip.is_left_node(inObj):
440
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("lArm"), link=2)
441
+ else:
442
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("rArm"), link=2)
443
+
444
+ children = self.bone.get_every_children(parentBipObj)
445
+ for child in children:
446
+ if rt.matchPattern(child.name, pattern="*Twist*") and rt.classOf(child) == rt.BoneGeometry:
447
+ returnVal.append(child)
448
+
449
+ returnVal = self.name.sort_by_name(returnVal)
450
+
451
+ return returnVal
452
+
453
+ def get_foreArm_type(self, inObj):
454
+ """
455
+ 전완(Forearm) 타입의 트위스트 뼈대 가져오기
456
+
457
+ Args:
458
+ inObj: 전완 뼈대 객체
459
+
460
+ Returns:
461
+ 전완 타입의 트위스트 뼈대 또는 False(실패 시)
462
+ """
463
+ returnVal = []
464
+
465
+ parentBipObj = None
466
+
467
+ if not self.bip.is_biped_object(inObj):
468
+ return returnVal
469
+
470
+ if self.bip.is_left_node(inObj):
471
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("lArm"), link=3)
472
+ else:
473
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("rArm"), link=3)
474
+
475
+ children = self.bone.get_every_children(parentBipObj)
476
+ for child in children:
477
+ if rt.matchPattern(child.name, pattern="*Twist*") and rt.classOf(child) == rt.BoneGeometry:
478
+ returnVal.append(child)
479
+
480
+ returnVal = self.name.sort_by_name(returnVal)
481
+
482
+ return returnVal
483
+
484
+ def get_thigh_type(self, inObj):
485
+ """
486
+ 허벅지(Thigh) 타입의 트위스트 뼈대 가져오기
487
+
488
+ Args:
489
+ inObj: 허벅지 뼈대 객체
490
+
491
+ Returns:
492
+ 허벅지 타입의 트위스트 뼈대 또는 False(실패 시)
493
+ """
494
+ returnVal = []
495
+
496
+ parentBipObj = None
497
+
498
+ if not self.bip.is_biped_object(inObj):
499
+ return returnVal
500
+
501
+ if self.bip.is_left_node(inObj):
502
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("lLeg"), link=1)
503
+ else:
504
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("rLeg"), link=1)
505
+
506
+ children = self.bone.get_every_children(parentBipObj)
507
+ for child in children:
508
+ if rt.matchPattern(child.name, pattern="*Twist*") and rt.classOf(child) == rt.BoneGeometry:
509
+ returnVal.append(child)
510
+
511
+ returnVal = self.name.sort_by_name(returnVal)
512
+
513
+ return returnVal
514
+
515
+ def get_calf_type(self, inObj):
516
+ """
517
+ 종아리(Calf) 타입의 트위스트 뼈대 가져오기
518
+
519
+ Args:
520
+ inObj: 종아리 뼈대 객체
521
+
522
+ Returns:
523
+ 종아리 타입의 트위스트 뼈대 또는 False(실패 시)
524
+ """
525
+ returnVal = []
526
+
527
+ parentBipObj = None
528
+
529
+ if not self.bip.is_biped_object(inObj):
530
+ return returnVal
531
+
532
+ if self.bip.is_left_node(inObj):
533
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("lLeg"), link=2)
534
+ else:
535
+ parentBipObj = rt.biped.getNode(inObj.controller.rootNode, rt.Name("rLeg"), link=2)
536
+
537
+ children = self.bone.get_every_children(parentBipObj)
538
+ for child in children:
539
+ if rt.matchPattern(child.name, pattern="*Twist*") and rt.classOf(child) == rt.BoneGeometry:
540
+ returnVal.append(child)
541
+
542
+ returnVal = self.name.sort_by_name(returnVal)
543
+
544
+ return returnVal
@@ -352,11 +352,12 @@ class Naming:
352
352
  partValues = partObj.get_predefined_values()
353
353
 
354
354
  if partType == NamePartType.PREFIX or partType == NamePartType.SUFFIX:
355
- foundIndex = partObj._descriptions.index(inDescription)
356
- if foundIndex >= 0:
355
+ try:
356
+ foundIndex = partObj._descriptions.index(inDescription)
357
357
  return partValues[foundIndex]
358
-
359
- return ""
358
+ except ValueError:
359
+ # Description not found in the list
360
+ return ""
360
361
 
361
362
  def pick_name(self, inNamePartName, inStr):
362
363
  nameArray = self._split_to_array(inStr)
@@ -0,0 +1,47 @@
1
+ struct _AutoClavicleBone (
2
+ name, anim, helper, bone, const, bip,
3
+
4
+ boneSize = 2.0,
5
+
6
+ fn create_bones inClavicle inUpperArm liftScale:0.8 = (
7
+ local clavicleLength = distance inClavicle inUpperArm
8
+ local tempHelperA = point()
9
+ local tempHelperB = point()
10
+ tempHelperA.transform = inClavicle.transform
11
+ tempHelperB.transform = inClavicle.transform
12
+ anim.move_local tempHelperB (clavicleLength/2.0) 0.0 0.0
13
+
14
+ local autoClavicleName = name.replace_realName inClavicle.name "AutoClavicle"
15
+ local autoClavicleBones = bone.create_bone #(tempHelperA, tempHelperB) autoClavicleName end:true delPoint:true parent:false size:boneSize
16
+ autoClavicleBones[1].transform = inClavicle.transform
17
+ anim.move_local autoClavicleBones[1] (clavicleLength/2.0) 0.0 0.0
18
+ autoClavicleBones[1].parent = inClavicle
19
+
20
+ local ikGoal = IKSys.ikChain autoClavicleBones[1] autoClavicleBones[2] "IKHISolver"
21
+ ikGoal.name = name.replace_type autoClavicleName "IK"
22
+ ikGoal.transform = autoClavicleBones[2].transform
23
+
24
+ local autoClavicleRotHelper = helper.create_point (name.replace_type autoClavicleName "Rot")
25
+ autoClavicleRotHelper.transform = autoClavicleBones[1].transform
26
+ autoClavicleRotHelper.parent = inClavicle
27
+
28
+ local rotTargetClavicle = helper.create_point (name.replace_type autoClavicleName "T")
29
+ rotTargetClavicle.transform = inClavicle.transform
30
+ anim.move_local rotTargetClavicle clavicleLength 0.0 0.0
31
+ local rotTargetUpperArm = helper.create_point (name.replace_type autoClavicleName "T")
32
+ rotTargetUpperArm.name = name.add_sufix_to_realName rotTargetUpperArm.name "UArm"
33
+ rotTargetUpperArm.transform = inUpperArm.transform
34
+ anim.move_local rotTargetUpperArm ((clavicleLength/2.0)*liftScale) 0.0 0.0
35
+
36
+ rotTargetClavicle.parent = inClavicle
37
+ rotTargetUpperArm.parent = inUpperArm
38
+
39
+ const.assign_lookAt_multi autoClavicleRotHelper #(rotTargetClavicle, rotTargetUpperArm)
40
+ autoClavicleRotHelper.rotation.controller[2].upnode_world = false
41
+ autoClavicleRotHelper.rotation.controller[2].pickUpNode = inClavicle
42
+ autoClavicleRotHelper.rotation.controller[2].lookat_vector_length = 0.0
43
+ ikGoal.parent = autoClavicleRotHelper
44
+
45
+ autoClavicleBones
46
+ )
47
+ )
@@ -0,0 +1,24 @@
1
+ import sys
2
+ import os
3
+
4
+ # 현재 스크립트의 디렉토리 path 가져오기
5
+ current_dir = os.path.dirname(os.path.abspath(__file__))
6
+ # 프로젝트 루트 디렉토리 추가 (PyJalLib 디렉토리)
7
+ project_root = os.path.abspath(os.path.join(current_dir, "..", "src"))
8
+
9
+ if project_root not in sys.path:
10
+ sys.path.insert(0, project_root)
11
+
12
+ import pyjallib
13
+ pyjallib.reload_modules()
14
+
15
+ tempJal = pyjallib.max.header.Header()
16
+
17
+ selObjs = rt.getCurrentSelection()
18
+
19
+ clavicle = tempJal.bip.get_grouped_nodes(selObjs[0], "lArm")[0]
20
+ upperArm = tempJal.bip.get_grouped_nodes(selObjs[0], "lArm")[1]
21
+
22
+ print(clavicle, upperArm)
23
+
24
+ tempJal.autoClavicle.create_bones(clavicle, upperArm, liftScale=0.8)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes