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.
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):
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ PyJalLib Max UI 모듈
4
+
5
+ 3ds Max용 UI 컴포넌트들을 제공합니다.
6
+ """
7
+
8
+ from .Container import Container
9
+
10
+ __all__ = ['Container']
@@ -123,6 +123,7 @@ class VolumeBone: # Updated class name to match the new file name
123
123
  rotHelper.name = self.name.replace_name_part("Type", rotHelper.name, self.name.get_name_part_value_by_description("Type", "Dummy"))
124
124
  rt.setProperty(rotHelper, "transform", inObj.transform)
125
125
  rotHelper.parent = inParent
126
+ self.const.assign_pos_const(rotHelper, inObj)
126
127
 
127
128
  oriConst = self.const.assign_rot_const_multi(rootBone, [inObj, rotHelper])
128
129
  oriConst.setWeight(1, inRotScale * 100.0)
@@ -152,9 +153,12 @@ class VolumeBone: # Updated class name to match the new file name
152
153
  volBoneName = inObj.name
153
154
  filteringChar = self.name._get_filtering_char(volBoneName)
154
155
  volBoneName = self.name.add_suffix_to_real_name(volBoneName, filteringChar + "Vol" + filteringChar + inRotAxis + filteringChar+ inTransAxis)
156
+ volBoneIndex = self.name.get_name("Index", volBoneName)
155
157
 
156
158
  volBone = self.bone.create_nub_bone(volBoneName, 2)
157
159
  volBone.name = self.name.remove_name_part("Nub", volBone.name)
160
+ volBone.name = self.name.replace_name_part("Index", volBone.name, volBoneIndex)
161
+
158
162
  if volBone.name[0].islower():
159
163
  volBone.name = volBone.name.lower()
160
164
  volBoneName = volBoneName.lower()
@@ -225,6 +229,10 @@ class VolumeBone: # Updated class name to match the new file name
225
229
 
226
230
  rootBone = self.create_root_bone(inObj, inParent, inRotScale=inRotScale)
227
231
 
232
+ # rootBone이 None이면 실패
233
+ if not rootBone:
234
+ return None
235
+
228
236
  # 볼륨 본들 생성
229
237
  bones = []
230
238
  for i in range(len(inRotAxises)):
@@ -240,7 +248,8 @@ class VolumeBone: # Updated class name to match the new file name
240
248
  if volBoneName[0].islower():
241
249
  volBoneName = volBoneName.lower()
242
250
 
243
- volBone = rt.getNodeByName(self.name.remove_name_part("Nub", volBoneName))
251
+ final_volBoneName = self.name.remove_name_part("Nub", volBoneName)
252
+ volBone = rt.getNodeByName(final_volBoneName)
244
253
  if rt.isValidNode(volBone):
245
254
  bones.append(volBone)
246
255
 
@@ -259,6 +268,8 @@ class VolumeBone: # Updated class name to match the new file name
259
268
  # 메소드 호출 후 데이터 초기화
260
269
  self.reset()
261
270
 
271
+ rt.redrawViews()
272
+
262
273
  return BoneChain.from_result(result)
263
274
 
264
275
  def create_bones_from_chain(self, inBoneChain: BoneChain):
pyjallib/max/wrist.py ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 손목 모듈
6
+ 자동으로 손목 본을 생성.
7
+ """
8
+
9
+ from pymxs import runtime as rt
10
+
11
+ from .volumeBone import VolumeBone
12
+ from .boneChain import BoneChain
13
+
14
+ class Wrist(VolumeBone):
15
+ def __init__(self, nameService=None, animService=None, constraintService=None, boneService=None, helperService=None):
16
+ super().__init__(nameService=nameService, animService=animService, constraintService=constraintService, boneService=boneService, helperService=helperService)
17
+
18
+ def create_bones(self, inHand, inForeArm, inRotScale=0.5, inVolumeSize=4.0, inFBRotAxis="Y", inIORotAxis="Z", inFBTransAxis="Z", inIOTransAxis="Y", inFBTransScale=1.0, inIOTransScale=1.0):
19
+ """손목 볼륨 본들을 생성합니다."""
20
+ if not rt.isValidNode(inHand) or not rt.isValidNode(inForeArm):
21
+ return False
22
+
23
+ # 이름 생성 (로컬 변수로 처리)
24
+ filteringChar = self.name._get_filtering_char(inForeArm.name)
25
+ wristName = self.name.replace_name_part("RealName", inForeArm.name, "Wrist")
26
+ wristRootName = self.name.replace_name_part("RealName", wristName, "Wrist" + filteringChar + "Root")
27
+ wristRootDumName = self.name.replace_name_part("Type", wristRootName, self.name.get_name_part_value_by_description("Type", "Dummy"))
28
+ wristFwdName = self.name.replace_name_part("RealName", wristName, "Wrist" + filteringChar + "Fwd")
29
+ wristBckName = self.name.replace_name_part("RealName", wristName, "Wrist" + filteringChar + "Bck")
30
+ wristInName = self.name.replace_name_part("RealName", wristName, "Wrist" + filteringChar + "In")
31
+ wristOutName = self.name.replace_name_part("RealName", wristName, "Wrist" + filteringChar + "Out")
32
+
33
+ # 소문자 처리
34
+ if inForeArm.name[0].islower():
35
+ wristName = wristName.lower()
36
+ wristRootName = wristRootName.lower()
37
+ wristRootDumName = wristRootDumName.lower()
38
+ wristFwdName = wristFwdName.lower()
39
+ wristBckName = wristBckName.lower()
40
+ wristInName = wristInName.lower()
41
+ wristOutName = wristOutName.lower()
42
+
43
+ # 방향 결정
44
+ facingDirVec = inHand.transform.position - inForeArm.transform.position
45
+ inObjXAxisVec = inHand.objectTransform.row1
46
+ distanceDir = 1.0 if rt.dot(inObjXAxisVec, facingDirVec) > 0 else -1.0
47
+
48
+ # 축과 스케일 설정 - 2개의 볼륨 본: FB(앞/뒤), IO(안/바깥)
49
+ rotAxises = [inFBRotAxis, inFBRotAxis, inIORotAxis, inIORotAxis]
50
+ transScales = [inFBTransScale, inFBTransScale, inIOTransScale, inIOTransScale]
51
+ transAxises = ["Pos" + inFBTransAxis, "Neg" + inFBTransAxis, "Pos" + inIOTransAxis, "Neg" + inIOTransAxis]
52
+ transAxisNames = [transAxises[0], transAxises[1], transAxises[2], transAxises[3]]
53
+
54
+ if distanceDir < 0:
55
+ transAxisNames = [transAxises[1], transAxises[0], transAxises[3], transAxises[2]]
56
+
57
+ # 부모 클래스의 create_bones 호출
58
+ volumeBoneResult = super().create_bones(inHand, inForeArm, inRotScale, inVolumeSize, rotAxises, transAxises, transScales)
59
+
60
+ # volumeBoneResult가 None이면 실패 반환
61
+ if not volumeBoneResult:
62
+ return None
63
+
64
+ # 생성된 본들의 이름 변경
65
+ if hasattr(volumeBoneResult, 'bones') and volumeBoneResult.bones:
66
+ for item in volumeBoneResult.bones:
67
+ if rt.matchPattern(item.name.lower(), pattern="*root*"):
68
+ item.name = wristRootName
69
+ elif rt.matchPattern(item.name.lower(), pattern="*" + transAxisNames[0].lower() + "*"):
70
+ item.name = wristBckName
71
+ elif rt.matchPattern(item.name.lower(), pattern="*" + transAxisNames[1].lower() + "*"):
72
+ item.name = wristFwdName
73
+ elif rt.matchPattern(item.name.lower(), pattern="*" + transAxisNames[2].lower() + "*"):
74
+ item.name = wristInName
75
+ elif rt.matchPattern(item.name.lower(), pattern="*" + transAxisNames[3].lower() + "*"):
76
+ item.name = wristOutName
77
+
78
+ # 생성된 헬퍼들의 이름 변경
79
+ if hasattr(volumeBoneResult, 'helpers') and volumeBoneResult.helpers:
80
+ for item in volumeBoneResult.helpers:
81
+ if rt.matchPattern(item.name.lower(), pattern="*root*"):
82
+ item.name = wristRootDumName
83
+
84
+ rt.redrawViews()
85
+
86
+ return volumeBoneResult
87
+
88
+ def create_bones_from_chain(self, inBoneChain: BoneChain):
89
+ """기존 BoneChain에서 손목 본들을 재생성합니다."""
90
+ if not inBoneChain or inBoneChain.is_empty():
91
+ return None
92
+
93
+ inBoneChain.delete()
94
+
95
+ sourceBones = inBoneChain.sourceBones
96
+ parameters = inBoneChain.parameters
97
+
98
+ if len(sourceBones) < 2 or not rt.isValidNode(sourceBones[0]) or not rt.isValidNode(sourceBones[1]):
99
+ return None
100
+
101
+ # 매개변수 추출
102
+ inHand = sourceBones[0]
103
+ inForeArm = sourceBones[1]
104
+ inRotScale = parameters[0] if len(parameters) > 0 else 0.5
105
+ inVolumeSize = parameters[1] if len(parameters) > 1 else 4.0
106
+ inFBRotAxis = parameters[2] if len(parameters) > 2 else "Y"
107
+ inIORotAxis = parameters[3] if len(parameters) > 3 else "Z"
108
+ inFBTransAxis = parameters[4] if len(parameters) > 4 else "Z"
109
+ inIOTransAxis = parameters[5] if len(parameters) > 5 else "Y"
110
+ inFBTransScale = parameters[6] if len(parameters) > 6 else 1.0
111
+ inIOTransScale = parameters[7] if len(parameters) > 7 else 1.0
112
+
113
+ return self.create_bones(inHand, inForeArm, inRotScale, inVolumeSize, inFBRotAxis, inIORotAxis, inFBTransAxis, inIOTransAxis, inFBTransScale, inIOTransScale)
pyjallib/nameToPath.py CHANGED
@@ -2,37 +2,93 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  """
5
- nameToPath 모듈 - 이름과 경로 변환 관련 기능
6
- 이름 규칙에 따라 경로를 생성하거나 경로에서 이름을 추출하는 기능 제공
5
+ 입력된 네이밍 규칙에 따라 경로를 생성하고, 윈도우즈용 폴더명을 안전하게 변환하는 기능을 제공하는 클래스.
7
6
  """
8
7
 
8
+ from pathlib import PureWindowsPath
9
9
  import os
10
- import json
10
+ import re
11
11
  from typing import Optional, Dict, Any, List
12
12
 
13
13
  from pyjallib.naming import Naming
14
- from pyjallib.namePart import NamePartType
14
+ from pyjallib.namingConfig import NamingConfig
15
+
15
16
 
16
17
  class NameToPath(Naming):
17
18
  """
18
- NameToPath 클래스는 Naming 클래스를 상속받아 이름을 기반으로 경로를 생성하는 기능을 제공합니다.
19
+ 네이밍 규칙에 따라 경로를 생성하고, 폴더명을 안전하게 변환하는 기능을 제공하는 클래스.
19
20
  """
20
- def __init__(self, configPath: str, rootPath: str = None, sourceNaming: Naming = None):
21
+ def __init__(self, inputConfigPath: str, pathConfigPath: str, rootPath: str = None):
22
+ """
23
+ NameToPath 초기화
24
+
25
+ :param inputConfigPath: 입력 이름 파싱용 설정 파일 경로
26
+ :param pathConfigPath: 경로 생성용 설정 파일 경로
27
+ :param rootPath: 루트 경로 (옵션)
28
+ """
29
+ super().__init__()
30
+ self.pathConfig = NamingConfig()
31
+ self.pathConfig.load(pathConfigPath)
32
+
33
+ # 루트 경로 설정 (pathAndFiles 의존성 제거)
34
+ if rootPath is not None:
35
+ self.rootPath = self._normalize_path(rootPath)
36
+ else:
37
+ self.rootPath = None
38
+
39
+ self.load_from_config_file(inputConfigPath)
40
+
41
+ @staticmethod
42
+ def _normalize_path(path: str) -> str:
43
+ """경로를 윈도우즈 형식으로 정규화"""
44
+ if not path:
45
+ return path
46
+ return str(PureWindowsPath(path))
47
+
48
+ @staticmethod
49
+ def _sanitize_folder_name(name: str) -> str:
50
+ """폴더명에서 유효하지 않은 문자를 제거"""
51
+ invalidChars = r'[<>:"/\\|?*]'
52
+ return re.sub(invalidChars, '_', name).strip()
53
+
54
+ @property
55
+ def rootPathProp(self) -> str:
56
+ """루트 경로 속성 getter"""
57
+ return self.rootPath
58
+
59
+ @rootPathProp.setter
60
+ def rootPathProp(self, path: str) -> None:
61
+ """루트 경로 속성 setter"""
62
+ self.rootPath = self._normalize_path(path)
63
+
64
+ def generate_path(self, inputName: str, inIncludeRealName: bool = False) -> str:
21
65
  """
22
- 생성자 메서드입니다.
23
- :param configPath: 설정 파일의 경로
24
- :param rootPath: 루트 경로 (기본값: None)
25
- :param sourceNaming: 소스 이름을 처리하기 위한 Naming 객체 (기본값: None)
66
+ 입력된 이름을 기반으로 경로를 생성
67
+
68
+ :param inputName: 파싱할 입력 이름
69
+ :param inIncludeRealName: 실제 이름을 경로에 포함할지 여부
70
+ :return: 생성된 경로
26
71
  """
27
- # 부모 클래스(Naming) 생성자 호출
28
- super().__init__(configPath)
29
- self.rootPath = None
30
- if rootPath:
31
- self.set_root_path(rootPath)
32
- # 소스 네이밍 객체 설정
33
- self.sourceNaming = sourceNaming
72
+ parsedDict = self.parse_name(inputName)
73
+ nameParts = self.pathConfig.get_part_order()
74
+ folders = [
75
+ self._sanitize_folder_name(self.pathConfig.get_part(part).get_description_by_value(parsedDict[part]))
76
+ for part in nameParts if part in parsedDict and self.pathConfig.get_part(part).get_description_by_value(parsedDict[part])
77
+ ]
78
+ if inIncludeRealName:
79
+ folders.append(self._sanitize_folder_name(parsedDict["RealName"]))
80
+
81
+ if self.rootPath:
82
+ fullPath = os.path.join(self.rootPath, *folders)
83
+ else:
84
+ fullPath = os.path.join(*folders) if folders else ""
85
+ return self._normalize_path(fullPath)
86
+
87
+ def parse_name(self, inName: str):
88
+ """이름을 파싱하여 딕셔너리로 변환"""
89
+ return self.convert_to_dictionary(inName)
34
90
 
35
- def set_root_path(self, inRootPath: str):
91
+ def set_root_path(self, inRootPath: str) -> str:
36
92
  """
37
93
  루트 경로를 설정합니다.
38
94
  입력된 경로를 정규화하고 유효성을 검증합니다.
@@ -42,25 +98,21 @@ class NameToPath(Naming):
42
98
  :raises ValueError: 경로가 존재하지 않는 경우
43
99
  """
44
100
  if inRootPath:
45
- # 경로 정규화 (상대 경로를 절대 경로로 변환, '/' 대신 '\' 사용 등)
46
- normalized_path = os.path.normpath(os.path.abspath(inRootPath))
47
-
48
- # 경로 존재 여부 확인 (선택적)
101
+ normalized_path = self._normalize_path(inRootPath)
49
102
  if not os.path.exists(normalized_path):
50
103
  raise ValueError(f"경로가 존재하지 않습니다: {normalized_path}")
51
-
52
104
  self.rootPath = normalized_path
53
105
  return self.rootPath
54
106
  else:
55
107
  self.rootPath = None
56
108
  return None
57
-
109
+
58
110
  def combine(self, inPartsDict={}, inFilChar=os.sep) -> str:
59
111
  """
60
112
  딕셔너리의 값들을 설정된 순서에 따라 문자열로 결합합니다. (인덱스 제외)
61
113
 
62
114
  :param inPartsDict: 결합할 키-값 쌍을 포함하는 딕셔너리
63
- :param inFilChar: 값들을 구분할 구분자 (기본값: "_")
115
+ :param inFilChar: 값들을 구분할 구분자 (기본값: os.sep)
64
116
  :return: 결합된 문자열
65
117
  """
66
118
  # 결과 배열 초기화 (빈 문자열로)
@@ -75,39 +127,4 @@ class NameToPath(Naming):
75
127
 
76
128
  # 배열을 문자열로 결합
77
129
  newName = self._combine(combinedNameArray, inFilChar)
78
- return newName
79
-
80
-
81
- def gen_path(self, inStr):
82
- """
83
- 입력된 문자열을 기반으로 경로를 생성합니다.
84
-
85
- :param inStr: 경로를 생성할 문자열 (이름)
86
- :return: 생성된 경로 (문자열)
87
- :raises ValueError: 루트 경로가 설정되지 않았거나 이름을 변환할 수 없는 경우
88
- """
89
- if not self.rootPath:
90
- raise ValueError("루트 경로가 설정되지 않았습니다.")
91
-
92
- # 이름을 딕셔너리로 변환
93
- nameDict = self.sourceNaming.convert_to_dictionary(inStr)
94
- if not nameDict:
95
- raise ValueError(f"이름을 변환할 수 없습니다: {inStr}")
96
- print(f"Name Dictionary: {nameDict}")
97
-
98
- pathDict = {}
99
-
100
- # 선택된 NamePart 값들을 설명으로 변환하여 폴더 이름으로 사용
101
- for key, value in nameDict.items():
102
- namePart = self.sourceNaming.get_name_part(key)
103
- if self.get_name_part(namePart.get_name()):
104
- if namePart.get_type().value == NamePartType.REALNAME.value:
105
- # 실제 이름인 경우, 해당 이름을 사용
106
- pathDict[key] = value
107
- else:
108
- pathDict[key] = namePart.get_description_by_value(value)
109
-
110
- combinedPath = self.combine(pathDict)
111
- finalPath = os.path.join(self.rootPath, combinedPath)
112
-
113
- return os.path.normpath(finalPath)
130
+ return newName