pyjallib 0.1.0__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/mirror.py ADDED
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 미러 모듈 - 3ds Max용 객체 미러링 관련 기능 제공
6
+ 원본 MAXScript의 mirror.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 .bone import Bone
14
+
15
+
16
+ class Mirror:
17
+ """
18
+ 객체 미러링 관련 기능을 제공하는 클래스.
19
+ MAXScript의 _Mirror 구조체 개념을 Python으로 재구현한 클래스이며,
20
+ 3ds Max의 기능들을 pymxs API를 통해 제어합니다.
21
+ """
22
+
23
+ def __init__(self, nameService=None, boneService=None):
24
+ """
25
+ 클래스 초기화
26
+
27
+ Args:
28
+ nameService: Name 서비스 인스턴스 (제공되지 않으면 새로 생성)
29
+ boneService: Bone 서비스 인스턴스 (제공되지 않으면 새로 생성)
30
+ """
31
+ self.name = nameService if nameService else Name()
32
+ self.bone = boneService if boneService else Bone(nameService=self.name) # Pass the potentially newly created nameService
33
+
34
+ def mirror_matrix(self, mAxis="x", mFlip="x", tm=None, pivotTM=None):
35
+ """
36
+ 미러링 행렬 생성
37
+
38
+ Args:
39
+ mAxis: 미러링 축 (기본값: "x")
40
+ mFlip: 뒤집는 축 (기본값: "x")
41
+ tm: 변환 행렬 (기본값: 단위 행렬)
42
+ pivotTM: 피벗 변환 행렬 (기본값: 단위 행렬)
43
+
44
+ Returns:
45
+ 미러링된 변환 행렬
46
+ """
47
+ def fetch_reflection(a):
48
+ """
49
+ 반사 벡터 값 반환
50
+
51
+ Args:
52
+ a: 축 식별자 ("x", "y", "z")
53
+
54
+ Returns:
55
+ 해당 축에 대한 반사 벡터
56
+ """
57
+ if a == "x":
58
+ return [-1, 1, 1] # YZ 평면에 대한 반사
59
+ elif a == "y":
60
+ return [1, -1, 1] # ZX 평면에 대한 반사
61
+ elif a == "z":
62
+ return [1, 1, -1] # XY 평면에 대한 반사
63
+ else:
64
+ return [1, 1, 1] # 반사 없음
65
+
66
+ # 기본값 설정
67
+ if tm is None:
68
+ tm = rt.matrix3(1)
69
+ if pivotTM is None:
70
+ pivotTM = rt.matrix3(1)
71
+
72
+ # 반사 행렬 생성
73
+ a_reflection = rt.scalematrix(rt.Point3(*fetch_reflection(mAxis)))
74
+ f_reflection = rt.scalematrix(rt.Point3(*fetch_reflection(mFlip)))
75
+
76
+ # 미러링된 변환 행렬 계산: fReflection * tm * aReflection * pivotTm
77
+ return f_reflection * tm * a_reflection * pivotTM
78
+
79
+ def apply_mirror(self, inObj, axis=1, flip=2, pivotObj=None, cloneStatus=2, negative=False):
80
+ """
81
+ 객체에 미러링 적용
82
+
83
+ Args:
84
+ inObj: 미러링할 객체
85
+ axis: 미러링 축 인덱스 (1=x, 2=y, 3=z, 기본값: 1)
86
+ flip: 뒤집기 축 인덱스 (1=x, 2=y, 3=z, 4=none, 기본값: 2)
87
+ pivotObj: 피벗 객체 (기본값: None)
88
+ cloneStatus: 복제 상태 (1=원본 변경, 2=복제본 생성, 3=스냅샷, 기본값: 2)
89
+ negative: 음수 좌표계 사용 여부 (기본값: False)
90
+
91
+ Returns:
92
+ 미러링된 객체 (복제본 또는 원본)
93
+ """
94
+ axisArray = ["x", "y", "z", "none"]
95
+ copyObj = rt.copy(inObj)
96
+ objTM = inObj.transform
97
+ pivotTM = rt.matrix3(1)
98
+ mirrorIndexAxis = axis
99
+ flipAxisIndex = flip
100
+ copyObjName = self.name.gen_mirroring_name(inObj.name)
101
+
102
+ # 피벗 객체가 지정된 경우 피벗 변환 행렬 사용
103
+ if pivotObj is not None:
104
+ pivotTM = pivotObj.transform
105
+
106
+ # negative가 True인 경우 뒤집기 없음으로 설정
107
+ if negative:
108
+ flipAxisIndex = 4
109
+
110
+ # 복제본 초기 설정
111
+ copyObj.name = copyObjName
112
+ copyObj.parent = None
113
+ copyObj.wirecolor = inObj.wirecolor
114
+
115
+ # 복제 상태에 따른 처리
116
+ if cloneStatus == 1: # 원본 변경
117
+ rt.delete(copyObj)
118
+ copyObj = None
119
+ inObj.transform = self.mirror_matrix(
120
+ mAxis=axisArray[mirrorIndexAxis-1],
121
+ mFlip=axisArray[flipAxisIndex-1],
122
+ tm=objTM,
123
+ pivotTM=pivotTM
124
+ )
125
+ copyObj = inObj
126
+ elif cloneStatus == 2: # 복제본 생성
127
+ copyObj.transform = self.mirror_matrix(
128
+ mAxis=axisArray[mirrorIndexAxis-1],
129
+ mFlip=axisArray[flipAxisIndex-1],
130
+ tm=objTM,
131
+ pivotTM=pivotTM
132
+ )
133
+ elif cloneStatus == 3: # 스냅샷 생성
134
+ rt.delete(copyObj)
135
+ copyObj = None
136
+ copyObj = rt.snapShot(inObj)
137
+ copyObj.transform = self.mirror_matrix(
138
+ mAxis=axisArray[mirrorIndexAxis-1],
139
+ mFlip=axisArray[flipAxisIndex-1],
140
+ tm=objTM,
141
+ pivotTM=pivotTM
142
+ )
143
+
144
+ return copyObj
145
+
146
+ def mirror_object(self, inObjArray, mAxis=1, pivotObj=None, cloneStatus=2):
147
+ """
148
+ 객체 배열을 음수 좌표계를 사용하여 미러링
149
+
150
+ Args:
151
+ inObjArray: 미러링할 객체 배열
152
+ mAxis: 미러링 축 (기본값: 1)
153
+ pivotObj: 피벗 객체 (기본값: None)
154
+ cloneStatus: 복제 상태 (기본값: 2)
155
+
156
+ Returns:
157
+ 미러링된 객체 배열
158
+ """
159
+ returnArray = []
160
+
161
+ for item in inObjArray:
162
+ mirroredObj = self.apply_mirror(
163
+ item,
164
+ axis=mAxis,
165
+ pivotObj=pivotObj,
166
+ cloneStatus=cloneStatus,
167
+ negative=True
168
+ )
169
+ returnArray.append(mirroredObj)
170
+
171
+ return returnArray
172
+
173
+ def mirror_without_negative(self, inMirrorObjArray, mAxis=1, pivotObj=None, cloneStatus=2):
174
+ """
175
+ 객체 배열을 양수 좌표계를 사용하여 미러링
176
+
177
+ Args:
178
+ inMirrorObjArray: 미러링할 객체 배열
179
+ mAxis: 미러링 축 인덱스 (1-6, 기본값: 1)
180
+ pivotObj: 피벗 객체 (기본값: None)
181
+ cloneStatus: 복제 상태 (기본값: 2)
182
+
183
+ Returns:
184
+ 미러링된 객체 배열
185
+ """
186
+ # 미러링 축과 뒤집기 축 매핑
187
+ # 1=XY, 2=XZ, 3=YX, 4=YZ, 5=ZX, 6=ZY
188
+ axisIndex = 1
189
+ flipIndex = 1
190
+
191
+ # 미러링 축 인덱스에 따른 매핑
192
+ if mAxis == 1:
193
+ axisIndex = 1 # x
194
+ flipIndex = 2 # y
195
+ elif mAxis == 2:
196
+ axisIndex = 1 # x
197
+ flipIndex = 3 # z
198
+ elif mAxis == 3:
199
+ axisIndex = 2 # y
200
+ flipIndex = 1 # x
201
+ elif mAxis == 4:
202
+ axisIndex = 2 # y
203
+ flipIndex = 3 # z
204
+ elif mAxis == 5:
205
+ axisIndex = 3 # z
206
+ flipIndex = 1 # x
207
+ elif mAxis == 6:
208
+ axisIndex = 3 # z
209
+ flipIndex = 2 # y
210
+ else:
211
+ axisIndex = 1 # x
212
+ flipIndex = 1 # x
213
+
214
+ # 미러링 적용
215
+ returnArray = []
216
+ for item in inMirrorObjArray:
217
+ mirroredObj = self.apply_mirror(
218
+ item,
219
+ axis=axisIndex,
220
+ flip=flipIndex,
221
+ pivotObj=pivotObj,
222
+ cloneStatus=cloneStatus,
223
+ negative=False
224
+ )
225
+ returnArray.append(mirroredObj)
226
+
227
+ return returnArray
228
+
229
+ def mirror_bone(self, inBoneArray, mAxis=1, flipZ=False, offset=0.0):
230
+ """
231
+ 뼈대 객체를 미러링
232
+
233
+ Args:
234
+ inBoneArray: 미러링할 뼈대 배열
235
+ mAxis: 미러링 축 (1=x, 2=y, 3=z, 기본값: 1)
236
+ flipZ: Z축 뒤집기 여부 (기본값: False)
237
+ offset: 미러링 오프셋 (기본값: 0.0)
238
+
239
+ Returns:
240
+ 미러링된 뼈대 배열
241
+ """
242
+ # 계층 구조에 따라 뼈대 정렬
243
+ bones = self.bone.sort_bones_as_hierarchy(inBoneArray)
244
+
245
+ # 미러링 축 팩터 설정
246
+ axisFactor = [1, 1, 1]
247
+ if mAxis == 1:
248
+ axisFactor = [-1, 1, 1] # x축 미러링
249
+ elif mAxis == 2:
250
+ axisFactor = [1, -1, 1] # y축 미러링
251
+ elif mAxis == 3:
252
+ axisFactor = [1, 1, -1] # z축 미러링
253
+
254
+ # 새 뼈대와 부모 정보 저장 배열 준비
255
+ parents = []
256
+ created = []
257
+
258
+ # 시작점 위치 (미러링 중심) 설정
259
+ root = bones[0].transform.translation
260
+
261
+ # 정렬된 뼈대 순서대로 처리
262
+ for i in range(len(bones)):
263
+ original = bones[i]
264
+ if rt.classOf(original) != rt.BoneGeometry: # 실제 뼈대가 아닌 경우
265
+ parents.append(None) # 부모 없음 표시
266
+ continue
267
+
268
+ # 원본 뼈대의 시작점, 끝점, Z축 방향 가져오기
269
+ boneStart = original.pos
270
+ boneEnd = self.bone.get_bone_end_position(original)
271
+ boneZ = original.dir
272
+
273
+ # 미러링 적용
274
+ for k in range(3): # x, y, z 좌표
275
+ if axisFactor[k] < 0:
276
+ boneStart[k] = 2.0 * root[k] - boneStart[k] + offset
277
+ boneEnd[k] = 2.0 * root[k] - boneEnd[k] + offset
278
+ boneZ[k] = -boneZ[k]
279
+
280
+ # Z축 뒤집기 옵션 적용
281
+ if flipZ:
282
+ boneZ = -boneZ
283
+
284
+ # 새 뼈대 생성
285
+ reflection = rt.bonesys.createbone(boneStart, boneEnd, boneZ)
286
+
287
+ # 원본 뼈대의 속성을 복사
288
+ reflection.backfin = original.backfin
289
+ reflection.backfinendtaper = original.backfinendtaper
290
+ reflection.backfinsize = original.backfinsize
291
+ reflection.backfinstarttaper = original.backfinstarttaper
292
+ reflection.frontfin = original.frontfin
293
+ reflection.frontfinendtaper = original.frontfinendtaper
294
+ reflection.frontfinsize = original.frontfinsize
295
+ reflection.frontfinstarttaper = original.frontfinstarttaper
296
+ reflection.height = original.height
297
+
298
+ # 이름 생성 (좌우/앞뒤 방향이 있는 경우 미러링된 이름 생성)
299
+ if self.name.has_Side(original.name) or self.name.has_FrontBack(original.name):
300
+ reflection.name = self.name.gen_mirroring_name(original.name, axis=mAxis)
301
+ else:
302
+ reflection.name = self.name.add_suffix_to_real_name(original.name, "Mirrored")
303
+
304
+ reflection.sidefins = original.sidefins
305
+ reflection.sidefinsendtaper = original.sidefinsendtaper
306
+ reflection.sidefinssize = original.sidefinssize
307
+ reflection.sidefinsstarttaper = original.sidefinsstarttaper
308
+ reflection.taper = original.taper
309
+ reflection.width = original.width
310
+ reflection.wirecolor = original.wirecolor
311
+
312
+ created.append(reflection)
313
+ parents.append(reflection)
314
+
315
+ # 계층 구조 연결 (자식부터 상위로)
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
323
+
324
+ # 부모가 없는 뼈대는 위치 조정
325
+ for i in range(len(created)):
326
+ if created[i].parent is None:
327
+ created[i].position = rt.Point3(
328
+ bones[i].position.x * axisFactor[0],
329
+ bones[i].position.y * axisFactor[1],
330
+ bones[i].position.z * axisFactor[2]
331
+ )
332
+
333
+ return created
334
+
335
+ def mirror_geo(self, inMirrorObjArray, mAxis=1, pivotObj=None, cloneStatus=2):
336
+ """
337
+ 지오메트리 객체 미러링 (폴리곤 노멀 방향 조정 포함)
338
+
339
+ Args:
340
+ inMirrorObjArray: 미러링할 객체 배열
341
+ mAxis: 미러링 축 (기본값: 1)
342
+ pivotObj: 피벗 객체 (기본값: None)
343
+ cloneStatus: 복제 상태 (기본값: 2)
344
+
345
+ Returns:
346
+ 미러링된 객체 배열
347
+ """
348
+ # 객체 미러링
349
+ mirroredArray = self.mirror_object(
350
+ inMirrorObjArray,
351
+ mAxis=mAxis,
352
+ pivotObj=pivotObj,
353
+ cloneStatus=cloneStatus
354
+ )
355
+
356
+ # 리셋 대상, 비리셋 대상 분류
357
+ resetXformArray = []
358
+ nonResetXformArray = []
359
+ returnArray = []
360
+
361
+ # 객체 타입에 따라 분류
362
+ for item in mirroredArray:
363
+ caseIndex = 0
364
+ if rt.classOf(item) == rt.Editable_Poly:
365
+ caseIndex += 1
366
+ if rt.classOf(item) == rt.Editable_mesh:
367
+ caseIndex += 1
368
+ if item.modifiers.count > 0:
369
+ caseIndex += 1
370
+
371
+ if caseIndex == 1: # 폴리곤, 메시 또는 모디파이어가 있는 경우
372
+ resetXformArray.append(item)
373
+ else:
374
+ nonResetXformArray.append(item)
375
+
376
+ # 리셋 대상 객체에 XForm 리셋 및 노멀 방향 뒤집기 적용
377
+ for item in resetXformArray:
378
+ rt.ResetXForm(item)
379
+ tempNormalMod = rt.normalModifier()
380
+ tempNormalMod.flip = True
381
+ rt.addModifier(item, tempNormalMod)
382
+ rt.collapseStack(item)
383
+
384
+ # 처리된 객체들 합치기
385
+ returnArray.extend(resetXformArray)
386
+ returnArray.extend(nonResetXformArray)
387
+
388
+ return returnArray