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/skin.py ADDED
@@ -0,0 +1,996 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ 스킨 모듈 - 3ds Max용 고급 스킨 관련 기능 제공
6
+ 원본 MAXScript의 skin2.ms를 Python으로 변환하였으며, pymxs 모듈 기반으로 구현됨
7
+ """
8
+
9
+ import os
10
+ from enum import IntEnum
11
+ import textwrap
12
+ from pymxs import runtime as rt
13
+
14
+ class VertexMode(IntEnum):
15
+ """
16
+ 버텍스 모드 열거형
17
+ """
18
+ Edges = 1
19
+ Attach = 2
20
+ All = 3
21
+ Stiff = 4
22
+
23
+ class Skin:
24
+ """
25
+ 고급 스킨 관련 기능을 제공하는 클래스.
26
+ MAXScript의 ODC_Char_Skin 구조체 개념을 Python으로 재구현한 클래스이며,
27
+ 3ds Max의 기능들을 pymxs API를 통해 제어합니다.
28
+ """
29
+
30
+ def __init__(self):
31
+ """
32
+ 클래스 초기화
33
+ """
34
+ self.skin_match_list = []
35
+
36
+ def has_skin(self, obj=None):
37
+ """
38
+ 객체에 스킨 모디파이어가 있는지 확인
39
+
40
+ Args:
41
+ obj: 확인할 객체 (기본값: 현재 선택된 객체)
42
+
43
+ Returns:
44
+ True: 스킨 모디파이어가 있는 경우
45
+ False: 없는 경우
46
+ """
47
+ if obj is None:
48
+ if len(rt.selection) > 0:
49
+ obj = rt.selection[0]
50
+ else:
51
+ return False
52
+
53
+ # 객체의 모든 모디파이어를 검사하여 Skin 모디파이어가 있는지 확인
54
+ for mod in obj.modifiers:
55
+ if rt.classOf(mod) == rt.Skin:
56
+ return True
57
+ return False
58
+
59
+ def is_valid_bone(self, inNode):
60
+ """
61
+ 노드가 유효한 스킨 본인지 확인
62
+
63
+ Args:
64
+ inNode: 확인할 노드
65
+
66
+ Returns:
67
+ True: 유효한 본인 경우
68
+ False: 아닌 경우
69
+ """
70
+ return (rt.superClassOf(inNode) == rt.GeometryClass or
71
+ rt.classOf(inNode) == rt.BoneGeometry or
72
+ rt.superClassOf(inNode) == rt.Helper)
73
+
74
+ def get_skin_mod(self, obj=None):
75
+ """
76
+ 객체의 스킨 모디파이어 배열 반환
77
+
78
+ Args:
79
+ obj: 모디파이어를 가져올 객체 (기본값: 현재 선택된 객체)
80
+
81
+ Returns:
82
+ 스킨 모디파이어 배열
83
+ """
84
+ if obj is None:
85
+ if len(rt.selection) > 0:
86
+ obj = rt.selection[0]
87
+ else:
88
+ return []
89
+
90
+ return [mod for mod in obj.modifiers if rt.classOf(mod) == rt.Skin]
91
+
92
+ def bind_skin(self, obj, bone_array):
93
+ """
94
+ 객체에 스킨 모디파이어 바인딩
95
+
96
+ Args:
97
+ obj: 바인딩할 객체
98
+ bone_array: 바인딩할 본 배열
99
+
100
+ Returns:
101
+ True: 성공한 경우
102
+ False: 실패한 경우
103
+ """
104
+ if obj is None or len(bone_array) < 1:
105
+ print("Select at least 1 influence and an object.")
106
+ return False
107
+
108
+ # Switch to modify mode
109
+ rt.execute("max modify mode")
110
+
111
+ # Check if the object is valid for skinning
112
+ if rt.superClassOf(obj) != rt.GeometryClass:
113
+ print(f"{obj.name} must be 'Edit_Mesh' or 'Edit_Poly'.")
114
+ return False
115
+
116
+ # Add skin modifier
117
+ objmod = rt.Skin()
118
+ rt.addModifier(obj, objmod)
119
+ rt.select(obj)
120
+
121
+ # Add bones to skin modifier
122
+ wgt = 1.0
123
+ for each in bone_array:
124
+ rt.skinOps.addBone(objmod, each, wgt)
125
+
126
+ # Set skin modifier options
127
+ objmod.filter_vertices = True
128
+ objmod.filter_envelopes = False
129
+ objmod.filter_cross_sections = True
130
+ objmod.enableDQ = False
131
+ objmod.bone_Limit = 8
132
+ objmod.colorAllWeights = True
133
+ objmod.showNoEnvelopes = True
134
+
135
+ return True
136
+
137
+ def optimize_skin(self, skin_mod, bone_limit=8, skin_tolerance=0.01):
138
+ """
139
+ 스킨 모디파이어 최적화
140
+
141
+ Args:
142
+ skin_mod: 스킨 모디파이어
143
+ bone_limit: 본 제한 수 (기본값: 8)
144
+ skin_tolerance: 스킨 가중치 허용 오차 (기본값: 0.01)
145
+ """
146
+ # 스킨 모디파이어 설정
147
+ skin_mod.enableDQ = False
148
+ skin_mod.bone_Limit = bone_limit
149
+ skin_mod.clearZeroLimit = skin_tolerance
150
+ rt.skinOps.RemoveZeroWeights(skin_mod)
151
+ skin_mod.clearZeroLimit = 0
152
+
153
+ skin_mod.filter_vertices = True
154
+ skin_mod.showNoEnvelopes = True
155
+
156
+ rt.skinOps.closeWeightTable(skin_mod)
157
+ rt.skinOps.closeWeightTool(skin_mod)
158
+
159
+ if rt.skinOps.getNumberBones(skin_mod) > 1:
160
+ list_of_bones = [i for i in range(1, rt.skinOps.GetNumberBones(skin_mod) + 1)]
161
+
162
+ for v in range(1, rt.skinOps.GetNumberVertices(skin_mod) + 1):
163
+ for b in range(1, rt.skinOps.GetVertexWeightCount(skin_mod, v) + 1):
164
+ bone_id = rt.skinOps.GetVertexWeightBoneID(skin_mod, v, b)
165
+ if bone_id in list_of_bones:
166
+ list_of_bones.remove(bone_id)
167
+
168
+ # 역순으로 본 제거 (인덱스 변경 문제 방지)
169
+ for i in range(len(list_of_bones) - 1, -1, -1):
170
+ bone_id = list_of_bones[i]
171
+ rt.skinOps.SelectBone(skin_mod, bone_id)
172
+ rt.skinOps.removebone(skin_mod, bone_id)
173
+
174
+ if rt.skinOps.getNumberBones(skin_mod) > 1:
175
+ rt.skinOps.SelectBone(skin_mod, 1)
176
+
177
+ skin_mod_obj = rt.getCurrentSelection()[0]
178
+
179
+ print(f"Obj:{skin_mod_obj.name} Removed:{len(list_of_bones)} Left:{rt.skinOps.GetNumberBones(skin_mod)}")
180
+
181
+ def optimize_skin_process(self, objs=None, optim_all_skin_mod=False, bone_limit=8, skin_tolerance=0.01):
182
+ """
183
+ 여러 객체의 스킨 최적화 프로세스
184
+
185
+ Args:
186
+ objs: 최적화할 객체 배열 (기본값: 현재 선택된 객체들)
187
+ optim_all_skin_mod: 모든 스킨 모디파이어 최적화 여부 (기본값: False)
188
+ bone_limit: 본 제한 수 (기본값: 8)
189
+ skin_tolerance: 스킨 가중치 허용 오차 (기본값: 0.01)
190
+ """
191
+ if objs is None:
192
+ objs = rt.selection
193
+
194
+ if not objs:
195
+ return
196
+
197
+ rt.execute("max modify mode")
198
+
199
+ for obj in objs:
200
+ if self.has_skin(obj):
201
+ mod_id = [i+1 for i in range(len(obj.modifiers)) if rt.classOf(obj.modifiers[i]) == rt.Skin]
202
+
203
+ if not optim_all_skin_mod:
204
+ mod_id = [mod_id[0]]
205
+
206
+ for each in mod_id:
207
+ rt.modPanel.setCurrentObject(obj.modifiers[each-1])
208
+ self.optimize_skin(obj.modifiers[each-1], bone_limit=bone_limit, skin_tolerance=skin_tolerance)
209
+
210
+ rt.select(objs)
211
+
212
+ def load_skin(self, obj, file_path, load_bind_pose=False, keep_skin=False):
213
+ """
214
+ 스킨 데이터 로드
215
+
216
+ Args:
217
+ obj: 로드할 객체
218
+ file_path: 스킨 파일 경로
219
+ load_bind_pose: 바인드 포즈 로드 여부
220
+ keep_skin: 기존 스킨 유지 여부
221
+
222
+ Returns:
223
+ 누락된 본 배열
224
+ """
225
+ # 기본값 설정
226
+ if keep_skin != True:
227
+ keep_skin = False
228
+
229
+ # 객체 선택
230
+ rt.select(obj)
231
+ data = []
232
+ missing_bones = []
233
+
234
+ # 파일 열기
235
+ try:
236
+ with open(file_path, 'r') as f:
237
+ for line in f:
238
+ data.append(line.strip())
239
+ except:
240
+ return []
241
+
242
+ # 버텍스 수 확인
243
+ if len(data) - 1 != obj.verts.count or obj.verts.count == 0:
244
+ print("Bad number of verts")
245
+ return []
246
+
247
+ # 기존 스킨 모디파이어 처리
248
+ if not keep_skin:
249
+ for i in range(len(obj.modifiers) - 1, -1, -1):
250
+ if rt.classOf(obj.modifiers[i]) == rt.Skin:
251
+ rt.deleteModifier(obj, i+1)
252
+
253
+ # 모디파이 모드 설정
254
+ rt.setCommandPanelTaskMode(rt.Name('modify'))
255
+
256
+ # 새 스킨 모디파이어 생성
257
+ new_skin = rt.Skin()
258
+ rt.addModifier(obj, new_skin, before=1 if keep_skin else 0)
259
+
260
+ # 스킨 이름 설정
261
+ if keep_skin:
262
+ new_skin.name = "Skin_" + os.path.splitext(os.path.basename(file_path))[0]
263
+
264
+ # 현재 모디파이어 설정
265
+ rt.modPanel.setCurrentObject(new_skin)
266
+
267
+ tempData = [rt.execute(item) for item in data]
268
+
269
+ # 본 데이터 처리
270
+ bones_data = rt.execute(tempData[0])
271
+ hierarchy = []
272
+
273
+ for i in range(len(bones_data)):
274
+ # 본 이름으로 노드 찾기
275
+ my_bone = [node for node in rt.objects if node.name == bones_data[i]]
276
+
277
+ # 없는 본인 경우 더미 생성
278
+ if len(my_bone) == 0:
279
+ print(f"Missing bone: {bones_data[i]}")
280
+ tmp = rt.Dummy(name=bones_data[i])
281
+ my_bone = [tmp]
282
+ missing_bones.append(tmp)
283
+
284
+ # 계층 구조 확인
285
+ if len(my_bone) > 1 and len(hierarchy) != 0:
286
+ print(f"Multiple bones are named: {my_bone[0].name} ({len(my_bone)})")
287
+ good_bone = None
288
+ for o in my_bone:
289
+ if o in hierarchy:
290
+ good_bone = o
291
+ break
292
+ if good_bone is not None:
293
+ my_bone = [good_bone]
294
+
295
+ # 사용할 본 결정
296
+ my_bone = my_bone[0]
297
+
298
+ # 계층에 추가
299
+ if my_bone not in hierarchy:
300
+ hierarchy.append(my_bone)
301
+ all_nodes = list(hierarchy)
302
+
303
+ for node in all_nodes:
304
+ # 자식 노드 추가
305
+ for child in node.children:
306
+ if child not in all_nodes:
307
+ all_nodes.append(child)
308
+ # 부모 노드 추가
309
+ if node.parent is not None and node.parent not in all_nodes:
310
+ all_nodes.append(node.parent)
311
+
312
+ # 계층에 추가
313
+ for node in all_nodes:
314
+ if self.is_valid_bone(node) and node not in hierarchy:
315
+ hierarchy.append(node)
316
+
317
+ # 본 추가
318
+ rt.skinOps.addBone(new_skin, my_bone, 1.0)
319
+
320
+ # 바인드 포즈 로드
321
+ if load_bind_pose:
322
+ bind_pose_file = os.path.splitext(file_path)[0] + "bp"
323
+ bind_poses = []
324
+
325
+ if os.path.exists(bind_pose_file):
326
+ try:
327
+ with open(bind_pose_file, 'r') as f:
328
+ for line in f:
329
+ bind_poses.append(rt.execute(line.strip()))
330
+ except:
331
+ pass
332
+
333
+ if i < len(bind_poses) and bind_poses[i] is not None:
334
+ rt.skinUtils.SetBoneBindTM(obj, my_bone, bind_poses[i])
335
+
336
+ # 가중치 데이터 처리
337
+ for i in range(1, obj.verts.count + 1):
338
+ bone_id = []
339
+ bone_weight = []
340
+ good_bones = []
341
+ all_bone_weight = [0] * len(bones_data)
342
+
343
+ # 가중치 합산
344
+ for b in range(len(tempData[i][0])):
345
+ bone_index = tempData[i][0][b]
346
+ weight = tempData[i][1][b]
347
+ all_bone_weight[bone_index-1] += weight
348
+ good_bones.append(bone_index)
349
+
350
+ # 가중치 적용
351
+ for b in good_bones:
352
+ bone_id.append(b)
353
+ bone_weight.append(all_bone_weight[b-1])
354
+
355
+ # 가중치 설정
356
+ if len(bone_id) != 0:
357
+ rt.skinOps.SetVertexWeights(new_skin, i, bone_id[0], 1.0) # Max 2014 sp5 hack
358
+ rt.skinOps.ReplaceVertexWeights(new_skin, i, bone_id, bone_weight)
359
+
360
+ return missing_bones
361
+
362
+ def save_skin(self, obj=None, file_path=None, save_bind_pose=False):
363
+ """
364
+ 스킨 데이터 저장
365
+ MAXScript의 saveskin.ms 를 Python으로 변환한 함수
366
+
367
+ Args:
368
+ obj: 저장할 객체 (기본값: 현재 선택된 객체)
369
+ file_path: 저장할 파일 경로 (기본값: None, 자동 생성)
370
+
371
+ Returns:
372
+ 저장된 파일 경로
373
+ """
374
+ # 현재 선택된 객체가 없는 경우 선택된 객체 사용
375
+ if obj is None:
376
+ if len(rt.selection) > 0:
377
+ obj = rt.selection[0]
378
+ else:
379
+ print("No object selected")
380
+ return None
381
+
382
+ # 현재 스킨 모디파이어 가져오기
383
+ skin_mod = rt.modPanel.getCurrentObject()
384
+
385
+ # 스킨 모디파이어가 아니거나 본이 없는 경우 종료
386
+ if rt.classOf(skin_mod) != rt.Skin or rt.skinOps.GetNumberBones(skin_mod) <= 0:
387
+ print("Current modifier is not a Skin modifier or has no bones")
388
+ return None
389
+
390
+ # 본 리스트 생성
391
+ bones_list = []
392
+ for i in range(1, rt.skinOps.GetNumberBones(skin_mod) + 1):
393
+ bones_list.append(rt.skinOps.GetBoneName(skin_mod, i, 1))
394
+
395
+ # 스킨 데이터 생성
396
+ skin_data = "\"#(\\\"" + "\\\",\\\"".join(str(x) for x in bones_list) + "\\\")\"\n"
397
+
398
+ # 버텍스별 가중치 데이터 수집
399
+ for v in range(1, rt.skinOps.GetNumberVertices(skin_mod) + 1):
400
+ bone_array = []
401
+ weight_array = []
402
+
403
+ for b in range(1, rt.skinOps.GetVertexWeightCount(skin_mod, v) + 1):
404
+ bone_array.append(rt.skinOps.GetVertexWeightBoneID(skin_mod, v, b))
405
+ weight_array.append(rt.skinOps.GetVertexWeight(skin_mod, v, b))
406
+
407
+ stringBoneArray = "#(" + ",".join(str(x) for x in bone_array) + ")"
408
+ stringWeightArray = "#(" + ",".join(str(w) for w in weight_array) + ")"
409
+ skin_data += ("#(" + stringBoneArray + ", " + stringWeightArray + ")\n")
410
+
411
+ # 파일 경로가 지정되지 않은 경우 자동 생성
412
+ if file_path is None:
413
+ # animations 폴더 내 skindata 폴더 생성
414
+ animations_dir = rt.getDir(rt.Name('animations'))
415
+ skin_data_dir = os.path.join(animations_dir, "skindata")
416
+
417
+ if not os.path.exists(skin_data_dir):
418
+ os.makedirs(skin_data_dir)
419
+
420
+ # 파일명 생성 (객체명 + 버텍스수 + 면수)
421
+ file_name = f"{obj.name} [v{obj.mesh.verts.count}] [t{obj.mesh.faces.count}].skin"
422
+ file_path = os.path.join(skin_data_dir, file_name)
423
+
424
+ print(f"Saving to: {file_path}")
425
+
426
+ # 스킨 데이터 파일 저장
427
+ try:
428
+ with open(file_path, 'w') as f:
429
+ for data in skin_data:
430
+ f.write(data)
431
+ except Exception as e:
432
+ print(f"Error saving skin data: {e}")
433
+ return None
434
+
435
+ if save_bind_pose:
436
+ # 바인드 포즈 데이터 수집 및 저장
437
+ bind_poses = []
438
+ for i in range(1, rt.skinOps.GetNumberBones(skin_mod) + 1):
439
+ bone_name = rt.skinOps.GetBoneName(skin_mod, i, 1)
440
+ bone_node = rt.getNodeByName(bone_name)
441
+ bind_pose = rt.skinUtils.GetBoneBindTM(obj, bone_node)
442
+ bind_poses.append(bind_pose)
443
+
444
+ # 바인드 포즈 파일 저장
445
+ bind_pose_file = file_path[:-4] + "bp" # .skin -> .bp
446
+ try:
447
+ with open(bind_pose_file, 'w') as f:
448
+ for pose in bind_poses:
449
+ f.write(str(pose) + '\n')
450
+ except Exception as e:
451
+ print(f"Error saving bind pose data: {e}")
452
+
453
+ return file_path
454
+
455
+ def get_bone_id(self, skin_mod, b_array, type=1, refresh=True):
456
+ """
457
+ 스킨 모디파이어에서 본 ID 가져오기
458
+
459
+ Args:
460
+ skin_mod: 스킨 모디파이어
461
+ b_array: 본 배열
462
+ type: 0=객체, 1=객체 이름
463
+ refresh: 인터페이스 업데이트 여부
464
+
465
+ Returns:
466
+ 본 ID 배열
467
+ """
468
+ bone_id = []
469
+
470
+ if refresh:
471
+ rt.modPanel.setCurrentObject(skin_mod)
472
+
473
+ for i in range(1, rt.skinOps.GetNumberBones(skin_mod) + 1):
474
+ if type == 0:
475
+ bone_name = rt.skinOps.GetBoneName(skin_mod, i, 1)
476
+ id = b_array.index(bone_name) + 1 if bone_name in b_array else 0
477
+ elif type == 1:
478
+ bone = rt.getNodeByName(rt.skinOps.GetBoneName(skin_mod, i, 1))
479
+ id = b_array.index(bone) + 1 if bone in b_array else 0
480
+
481
+ if id != 0:
482
+ bone_id.append(i)
483
+
484
+ return bone_id
485
+
486
+ def get_bone_id_from_name(self, in_skin_mod, bone_name):
487
+ """
488
+ 본 이름으로 본 ID 가져오기
489
+
490
+ Args:
491
+ in_skin_mod: 스킨 모디파이어를 가진 객체
492
+ bone_name: 본 이름
493
+
494
+ Returns:
495
+ 본 ID
496
+ """
497
+ for i in range(1, rt.skinOps.GetNumberBones(in_skin_mod) + 1):
498
+ if rt.skinOps.GetBoneName(in_skin_mod, i, 1) == bone_name:
499
+ return i
500
+ return None
501
+
502
+ def get_bones_from_skin(self, objs, skin_mod_index):
503
+ """
504
+ 스킨 모디파이어에서 사용된 본 배열 가져오기
505
+
506
+ Args:
507
+ objs: 객체 배열
508
+ skin_mod_index: 스킨 모디파이어 인덱스
509
+
510
+ Returns:
511
+ 본 배열
512
+ """
513
+ inf_list = []
514
+
515
+ for obj in objs:
516
+ if rt.isValidNode(obj):
517
+ deps = rt.refs.dependsOn(obj.modifiers[skin_mod_index])
518
+ for n in deps:
519
+ if rt.isValidNode(n) and self.is_valid_bone(n):
520
+ if n not in inf_list:
521
+ inf_list.append(n)
522
+
523
+ return inf_list
524
+
525
+ def find_skin_mod_id(self, obj):
526
+ """
527
+ 객체에서 스킨 모디파이어 인덱스 찾기
528
+
529
+ Args:
530
+ obj: 대상 객체
531
+
532
+ Returns:
533
+ 스킨 모디파이어 인덱스 배열
534
+ """
535
+ return [i+1 for i in range(len(obj.modifiers)) if rt.classOf(obj.modifiers[i]) == rt.Skin]
536
+
537
+ def sel_vert_from_bones(self, skin_mod, threshold=0.01):
538
+ """
539
+ 선택된 본에 영향 받는 버텍스 선택
540
+
541
+ Args:
542
+ skin_mod: 스킨 모디파이어
543
+ threshold: 가중치 임계값 (기본값: 0.01)
544
+
545
+ Returns:
546
+ 선택된 버텍스 배열
547
+ """
548
+ verts_to_sel = []
549
+
550
+ if skin_mod is not None:
551
+ le_bone = rt.skinOps.getSelectedBone(skin_mod)
552
+ svc = rt.skinOps.GetNumberVertices(skin_mod)
553
+
554
+ for o in range(1, svc + 1):
555
+ lv = rt.skinOps.GetVertexWeightCount(skin_mod, o)
556
+
557
+ for k in range(1, lv + 1):
558
+ if rt.skinOps.GetVertexWeightBoneID(skin_mod, o, k) == le_bone:
559
+ if rt.skinOps.GetVertexWeight(skin_mod, o, k) >= threshold:
560
+ if o not in verts_to_sel:
561
+ verts_to_sel.append(o)
562
+
563
+ rt.skinOps.SelectVertices(skin_mod, verts_to_sel)
564
+
565
+ else:
566
+ print("You must have a skinned object selected")
567
+
568
+ return verts_to_sel
569
+
570
+ def sel_all_verts(self, skin_mod):
571
+ """
572
+ 스킨 모디파이어의 모든 버텍스 선택
573
+
574
+ Args:
575
+ skin_mod: 스킨 모디파이어
576
+
577
+ Returns:
578
+ 선택된 버텍스 배열
579
+ """
580
+ verts_to_sel = []
581
+
582
+ if skin_mod is not None:
583
+ svc = rt.skinOps.GetNumberVertices(skin_mod)
584
+
585
+ for o in range(1, svc + 1):
586
+ verts_to_sel.append(o)
587
+
588
+ rt.skinOps.SelectVertices(skin_mod, verts_to_sel)
589
+
590
+ return verts_to_sel
591
+
592
+ def make_rigid_skin(self, skin_mod, vert_list):
593
+ """
594
+ 버텍스 가중치를 경직화(rigid) 처리
595
+
596
+ Args:
597
+ skin_mod: 스킨 모디파이어
598
+ vert_list: 버텍스 리스트
599
+
600
+ Returns:
601
+ [본 ID 배열, 가중치 배열]
602
+ """
603
+ weight_array = {}
604
+ vert_count = 0
605
+ bone_array = []
606
+ final_weight = []
607
+
608
+ # 가중치 수집
609
+ for v in vert_list:
610
+ for cur_bone in range(1, rt.skinOps.GetVertexWeightCount(skin_mod, v) + 1):
611
+ cur_id = rt.skinOps.GetVertexWeightBoneID(skin_mod, v, cur_bone)
612
+
613
+ if cur_id not in weight_array:
614
+ weight_array[cur_id] = 0
615
+
616
+ cur_weight = rt.skinOps.GetVertexWeight(skin_mod, v, cur_bone)
617
+ weight_array[cur_id] += cur_weight
618
+ vert_count += cur_weight
619
+
620
+ # 최종 가중치 계산
621
+ for i in weight_array:
622
+ if weight_array[i] > 0:
623
+ new_val = weight_array[i] / vert_count
624
+ if new_val > 0.01:
625
+ bone_array.append(i)
626
+ final_weight.append(new_val)
627
+
628
+ return [bone_array, final_weight]
629
+
630
+ def transfert_skin_data(self, obj, source_bones, target_bones, vtx_list):
631
+ """
632
+ 스킨 가중치 데이터 이전
633
+
634
+ Args:
635
+ obj: 대상 객체
636
+ source_bones: 원본 본 배열
637
+ target_bones: 대상 본
638
+ vtx_list: 버텍스 리스트
639
+ """
640
+ skin_data = []
641
+ new_skin_data = []
642
+
643
+ # 본 ID 가져오기
644
+ source_bones_id = [self.get_bone_id_from_name(obj, b.name) for b in source_bones]
645
+ target_bone_id = self.get_bone_id_from_name(obj, target_bones.name)
646
+
647
+ bone_list = [n for n in rt.refs.dependsOn(obj.skin) if rt.isValidNode(n) and self.is_valid_bone(n)]
648
+ bone_id_map = {self.get_bone_id_from_name(obj, b.name): i for i, b in enumerate(bone_list)}
649
+
650
+ # 스킨 데이터 수집
651
+ for vtx in vtx_list:
652
+ bone_array = []
653
+ weight_array = []
654
+ bone_weight = [0] * len(bone_list)
655
+
656
+ for b in range(1, rt.skinOps.GetVertexWeightCount(obj.skin, vtx) + 1):
657
+ bone_idx = rt.skinOps.GetVertexWeightBoneID(obj.skin, vtx, b)
658
+ bone_weight[bone_id_map[bone_idx]] += rt.skinOps.GetVertexWeight(obj.skin, vtx, b)
659
+
660
+ for b in range(len(bone_weight)):
661
+ if bone_weight[b] > 0:
662
+ bone_array.append(b+1)
663
+ weight_array.append(bone_weight[b])
664
+
665
+ skin_data.append([bone_array, weight_array])
666
+ new_skin_data.append([bone_array[:], weight_array[:]])
667
+
668
+ # 스킨 데이터 이전
669
+ for b, source_bone_id in enumerate(source_bones_id):
670
+ vtx_id = []
671
+ vtx_weight = []
672
+
673
+ # 원본 본의 가중치 추출
674
+ for vtx in range(len(skin_data)):
675
+ for i in range(len(skin_data[vtx][0])):
676
+ if skin_data[vtx][0][i] == source_bone_id:
677
+ vtx_id.append(vtx)
678
+ vtx_weight.append(skin_data[vtx][1][i])
679
+
680
+ # 원본 본 영향력 제거
681
+ for vtx in range(len(vtx_id)):
682
+ for i in range(len(new_skin_data[vtx_id[vtx]][0])):
683
+ if new_skin_data[vtx_id[vtx]][0][i] == source_bone_id:
684
+ new_skin_data[vtx_id[vtx]][1][i] = 0.0
685
+
686
+ # 타겟 본에 영향력 추가
687
+ for vtx in range(len(vtx_id)):
688
+ id = new_skin_data[vtx_id[vtx]][0].index(target_bone_id) if target_bone_id in new_skin_data[vtx_id[vtx]][0] else -1
689
+
690
+ if id == -1:
691
+ new_skin_data[vtx_id[vtx]][0].append(target_bone_id)
692
+ new_skin_data[vtx_id[vtx]][1].append(vtx_weight[vtx])
693
+ else:
694
+ new_skin_data[vtx_id[vtx]][1][id] += vtx_weight[vtx]
695
+
696
+ # 스킨 데이터 적용
697
+ for i in range(len(vtx_list)):
698
+ rt.skinOps.ReplaceVertexWeights(obj.skin, vtx_list[i],
699
+ skin_data[i][0], new_skin_data[i][1])
700
+
701
+ def smooth_skin(self, inObj, inVertMode=VertexMode.Edges, inRadius=5.0, inIterNum=3, inKeepMax=False):
702
+ """
703
+ 스킨 가중치 부드럽게 하기
704
+
705
+ Args:
706
+ inObj: 대상 객체
707
+ inVertMode: 버텍스 모드 (기본값: 1)
708
+ inRadius: 반경 (기본값: 5.0)
709
+ inIterNum: 반복 횟수 (기본값: 3)
710
+ inKeepMax: 최대 가중치 유지 여부 (기본값: False)
711
+
712
+ Returns:
713
+ None
714
+ """
715
+ maxScriptCode = textwrap.dedent(r'''
716
+ struct _SmoothSkin (
717
+ SmoothSkinMaxUndo = 10,
718
+ UndoWeights = #(),
719
+ SmoothSkinData = #(#(), #(), #(), #(), #(), #(), #()),
720
+ smoothRadius = 5.0,
721
+ iterNum = 1,
722
+ keepMax = false,
723
+
724
+ -- vertGroupMode: Edges, Attach, All, Stiff
725
+ vertGroupMode = 1,
726
+
727
+ fn make_rigid_skin skin_mod vert_list =
728
+ (
729
+ /*
730
+ Rigidify vertices weights in skin modifier
731
+ */
732
+ WeightArray = #()
733
+ VertCount = 0
734
+ BoneArray = #()
735
+ FinalWeight = #()
736
+
737
+ for v in vert_list do
738
+ (
739
+ for CurBone = 1 to (skinOps.GetVertexWeightCount skin_mod v) do
740
+ (
741
+ CurID = (skinOps.GetVertexWeightBoneID skin_mod v CurBone)
742
+ if WeightArray[CurID] == undefined do WeightArray[CurID] = 0
743
+
744
+ CurWeight = (skinOps.GetVertexWeight skin_mod v CurBone)
745
+ WeightArray[CurID] += CurWeight
746
+ VertCount += CurWeight
747
+ )
748
+
749
+ for i = 1 to WeightArray.count where WeightArray[i] != undefined and WeightArray[i] > 0 do
750
+ (
751
+ NewVal = (WeightArray[i] / VertCount)
752
+ if NewVal > 0.01 do (append BoneArray i; append FinalWeight NewVal)
753
+ )
754
+ )
755
+ return #(BoneArray, FinalWeight)
756
+ ),
757
+
758
+ fn smooth_skin =
759
+ (
760
+ if $selection.count != 1 then return false
761
+
762
+ p = 0
763
+ for iter = 1 to iterNum do
764
+ (
765
+ p += 1
766
+ if classOf (modPanel.getCurrentObject()) != Skin then return false
767
+
768
+ obj = $; skinMod = modPanel.getCurrentObject()
769
+ FinalBoneArray = #(); FinalWeightArray = #(); o = 1
770
+
771
+ UseOldData = (obj == SmoothSkinData[1][1]) and (obj.verts.count == SmoothSkinData[1][2])
772
+ if not UseOldData do SmoothSkinData = #(#(), #(), #(), #(), #(), #(), #())
773
+ SmoothSkinData[1][1] = obj; SmoothSkinData[1][2] = obj.verts.count
774
+
775
+ tmpObj = copy Obj
776
+ tmpObj.modifiers[skinMod.name].enabled = false
777
+
778
+ fn DoNormalizeWeight Weight =
779
+ (
780
+ WeightLength = 0; NormalizeWeight = #()
781
+ for w = 1 to Weight.count do WeightLength += Weight[w]
782
+ if WeightLength != 0 then
783
+ for w = 1 to Weight.count do NormalizeWeight[w] = Weight[w] * (1 / WeightLength)
784
+ else
785
+ NormalizeWeight[1] = 1.0
786
+ return NormalizeWeight
787
+ )
788
+
789
+ skinMod.clearZeroLimit = 0.00
790
+ skinOps.RemoveZeroWeights skinMod
791
+
792
+ posarray = for a in tmpObj.verts collect a.pos
793
+
794
+ if (SmoothSkinData[8] != smoothRadius) do (SmoothSkinData[6] = #(); SmoothSkinData[7] = #())
795
+
796
+ for v = 1 to obj.verts.count where (skinOps.IsVertexSelected skinMod v == 1) and (not keepMax or (skinOps.GetVertexWeightCount skinmod v != 1)) do
797
+ (
798
+ VertBros = #{}; VertBrosRatio = #()
799
+ Weightarray = #(); BoneArray = #(); FinalWeight = #()
800
+ WeightArray.count = skinOps.GetNumberBones skinMod
801
+
802
+ if vertGroupMode == 1 and (SmoothSkinData[2][v] == undefined) do
803
+ (
804
+ if (classof tmpObj == Editable_Poly) or (classof tmpObj == PolyMeshObject) then
805
+ (
806
+ CurEdges = polyop.GetEdgesUsingVert tmpObj v
807
+ for CE in CurEdges do VertBros += (polyop.getEdgeVerts tmpObj CE) as bitArray
808
+ )
809
+ else
810
+ (
811
+ CurEdges = meshop.GetEdgesUsingvert tmpObj v
812
+ for i in CurEdges do CurEdges[i] = (getEdgeVis tmpObj (1+(i-1)/3)(1+mod (i-1) 3))
813
+ for CE in CurEdges do VertBros += (meshop.getVertsUsingEdge tmpObj CE) as bitArray
814
+ )
815
+
816
+ VertBros = VertBros as array
817
+ SmoothSkinData[2][v] = #()
818
+ SmoothSkinData[3][v] = #()
819
+
820
+ if VertBros.count > 0 do
821
+ (
822
+ for vb in VertBros do
823
+ (
824
+ CurDist = distance posarray[v] posarray[vb]
825
+ if CurDist == 0 then
826
+ append VertBrosRatio 0
827
+ else
828
+ append VertBrosRatio (1 / CurDist)
829
+ )
830
+
831
+ VertBrosRatio = DoNormalizeWeight VertBrosRatio
832
+ VertBrosRatio[finditem VertBros v] = 1
833
+ SmoothSkinData[2][v] = VertBros
834
+ SmoothSkinData[3][v] = VertBrosRatio
835
+ )
836
+ )
837
+
838
+ if vertGroupMode == 2 do
839
+ (
840
+ SmoothSkinData[4][v] = for vb = 1 to posarray.count where (skinOps.IsVertexSelected skinMod vb == 0) and (distance posarray[v] posarray[vb]) < smoothRadius collect vb
841
+ SmoothSkinData[5][v] = for vb in SmoothSkinData[4][v] collect
842
+ (CurDist = distance posarray[v] posarray[vb]; if CurDist == 0 then 0 else (1 / CurDist))
843
+ SmoothSkinData[5][v] = DoNormalizeWeight SmoothSkinData[5][v]
844
+ for i = 1 to SmoothSkinData[5][v].count do SmoothSkinData[5][v][i] *= 2
845
+ )
846
+
847
+ if vertGroupMode == 3 and (SmoothSkinData[6][v] == undefined) do
848
+ (
849
+ SmoothSkinData[6][v] = for vb = 1 to posarray.count where (distance posarray[v] posarray[vb]) < smoothRadius collect vb
850
+ SmoothSkinData[7][v] = for vb in SmoothSkinData[6][v] collect
851
+ (CurDist = distance posarray[v] posarray[vb]; if CurDist == 0 then 0 else (1 / CurDist))
852
+ SmoothSkinData[7][v] = DoNormalizeWeight SmoothSkinData[7][v]
853
+ for i = 1 to SmoothSkinData[7][v].count do SmoothSkinData[7][v][i] *= 2
854
+ )
855
+
856
+ if vertGroupMode != 4 do
857
+ (
858
+ VertBros = SmoothSkinData[vertGroupMode * 2][v]
859
+ VertBrosRatio = SmoothSkinData[(vertGroupMode * 2) + 1][v]
860
+
861
+ for z = 1 to VertBros.count do
862
+ for CurBone = 1 to (skinOps.GetVertexWeightCount skinMod VertBros[z]) do
863
+ (
864
+ CurID = (skinOps.GetVertexWeightBoneID skinMod VertBros[z] CurBone)
865
+ if WeightArray[CurID] == undefined do WeightArray[CurID] = 0
866
+ WeightArray[CurID] += (skinOps.GetVertexWeight skinMod VertBros[z] CurBone) * VertBrosRatio[z]
867
+ )
868
+
869
+ for i = 1 to WeightArray.count where WeightArray[i] != undefined and WeightArray[i] > 0 do
870
+ (
871
+ NewVal = (WeightArray[i] / 2)
872
+ if NewVal > 0.01 do (append BoneArray i; append FinalWeight NewVal)
873
+ )
874
+ FinalBoneArray[v] = BoneArray
875
+ FinalWeightArray[v] = FinalWeight
876
+ )
877
+ )
878
+
879
+ if vertGroupMode == 4 then
880
+ (
881
+ convertTopoly tmpObj
882
+ polyObj = tmpObj
883
+
884
+ -- Only test selected
885
+ VertSelection = for v = 1 to obj.verts.count where (skinOps.IsVertexSelected skinMod v == 1) collect v
886
+ DoneEdge = (polyobj.edges as bitarray) - polyop.getEdgesUsingVert polyObj VertSelection
887
+ DoneFace = (polyobj.faces as bitarray) - polyop.getFacesUsingVert polyObj VertSelection
888
+
889
+ -- Elements
890
+ SmallElements = #()
891
+ for f = 1 to polyobj.faces.count where not DoneFace[f] do
892
+ (
893
+ CurElement = polyop.getElementsUsingFace polyObj #{f}
894
+
895
+ CurVerts = polyop.getVertsUsingFace polyobj CurElement; MaxDist = 0
896
+ for v1 in CurVerts do
897
+ for v2 in CurVerts where MaxDist < (smoothRadius * 2) do
898
+ (
899
+ dist = distance polyobj.verts[v1].pos polyobj.verts[v2].pos
900
+ if dist > MaxDist do MaxDist = dist
901
+ )
902
+ if MaxDist < (smoothRadius * 2) do append SmallElements CurVerts
903
+ DoneFace += CurElement
904
+ )
905
+
906
+ -- Loops
907
+ EdgeLoops = #()
908
+ for ed in SmallElements do DoneEdge += polyop.getEdgesUsingVert polyobj ed
909
+ for ed = 1 to polyobj.edges.count where not DoneEdge[ed] do
910
+ (
911
+ polyobj.selectedEdges = #{ed}
912
+ polyobj.ButtonOp #SelectEdgeLoop
913
+ CurEdgeLoop = (polyobj.selectedEdges as bitarray)
914
+ if CurEdgeLoop.numberSet > 2 do
915
+ (
916
+ CurVerts = (polyop.getvertsusingedge polyobj CurEdgeLoop); MaxDist = 0
917
+ for v1 in CurVerts do
918
+ for v2 in CurVerts where MaxDist < (smoothRadius * 2) do
919
+ (
920
+ dist = distance polyobj.verts[v1].pos polyobj.verts[v2].pos
921
+ if dist > MaxDist do MaxDist = dist
922
+ )
923
+ if MaxDist < (smoothRadius * 2) do append EdgeLoops CurVerts
924
+ )
925
+ DoneEdge += CurEdgeLoop
926
+ )
927
+
928
+ modPanel.setCurrentObject SkinMod; subobjectLevel = 1
929
+ for z in #(SmallElements, EdgeLoops) do
930
+ for i in z do
931
+ (
932
+ VertList = for v3 in i where (skinOps.IsVertexSelected skinMod v3 == 1) collect v3
933
+ NewWeights = self.make_rigid_skin SkinMod VertList
934
+ for v3 in VertList do (FinalBoneArray[v3] = NewWeights[1]; FinalWeightArray[v3] = NewWeights[2])
935
+ )
936
+ )
937
+
938
+ SmoothSkinData[8] = smoothRadius
939
+
940
+ delete tmpObj
941
+ OldWeightArray = #(); OldBoneArray = #(); LastWeights = #()
942
+ for sv = 1 to FinalBoneArray.count where FinalBonearray[sv] != undefined and FinalBoneArray[sv].count != 0 do
943
+ (
944
+ -- Home-Made undo
945
+ NumItem = skinOps.GetVertexWeightCount skinMod sv
946
+ OldWeightArray.count = OldBoneArray.count = NumItem
947
+ for CurBone = 1 to NumItem do
948
+ (
949
+ OldBoneArray[CurBone] = (skinOps.GetVertexWeightBoneID skinMod sv CurBone)
950
+ OldWeightArray[CurBone] = (skinOps.GetVertexWeight skinMod sv CurBone)
951
+ )
952
+
953
+ append LastWeights #(skinMod, sv, deepcopy OldBoneArray, deepcopy OldWeightArray)
954
+ if UndoWeights.count >= SmoothSkinMaxUndo do deleteItem UndoWeights 1
955
+
956
+ skinOps.ReplaceVertexWeights skinMod sv FinalBoneArray[sv] FinalWeightArray[sv]
957
+ )
958
+
959
+ append UndoWeights LastWeights
960
+
961
+ prog = ((p as float / iterNum as float) * 100.0)
962
+ format "Smoothing Progress:%\n" prog
963
+ )
964
+ ),
965
+
966
+ fn undo_smooth_skin = (
967
+ CurUndo = UndoWeights[UndoWeights.count]
968
+ try(
969
+ if modPanel.GetCurrentObject() != CurUndo[1][1] do (modPanel.setCurrentObject CurUndo[1][1]; subobjectLevel = 1)
970
+ for i in CurUndo do skinOps.ReplaceVertexWeights i[1] i[2] i[3] i[4]
971
+ )
972
+ catch( print "Undo fail")
973
+ deleteitem UndoWeights UndoWeights.count
974
+ if UndoWeights.count == 0 then return false
975
+ ),
976
+
977
+ fn setting inVertMode inRadius inIterNum inKeepMax = (
978
+ vertGroupMode = inVertMode
979
+ smoothRadius = inRadius
980
+ iterNum = inIterNum
981
+ keepMax = inKeepMax
982
+ )
983
+ )
984
+ ''')
985
+
986
+ if rt.isValidNode(inObj):
987
+ rt.select(inObj)
988
+ rt.execute("max modify mode")
989
+
990
+ targetSkinMod = self.get_skin_mod(inObj)
991
+ rt.modPanel.setCurrentObject(targetSkinMod[0])
992
+
993
+ rt.execute(maxScriptCode)
994
+ smooth_skin = rt._SmoothSkin()
995
+ smooth_skin.setting(inVertMode.value, inRadius, inIterNum, inKeepMax)
996
+ smooth_skin.smooth_skin()