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/__init__.py +17 -0
- pyjallib/max/__init__.py +46 -0
- pyjallib/max/align.py +112 -0
- pyjallib/max/anim.py +594 -0
- pyjallib/max/bip.py +508 -0
- pyjallib/max/bone.py +910 -0
- pyjallib/max/constraint.py +973 -0
- pyjallib/max/header.py +57 -0
- pyjallib/max/helper.py +433 -0
- pyjallib/max/layer.py +262 -0
- pyjallib/max/link.py +78 -0
- pyjallib/max/macro/jal_macro_align.py +155 -0
- pyjallib/max/macro/jal_macro_bone.py +358 -0
- pyjallib/max/macro/jal_macro_constraint.py +140 -0
- pyjallib/max/macro/jal_macro_helper.py +321 -0
- pyjallib/max/macro/jal_macro_link.py +55 -0
- pyjallib/max/macro/jal_macro_select.py +91 -0
- pyjallib/max/mirror.py +388 -0
- pyjallib/max/name.py +521 -0
- pyjallib/max/select.py +278 -0
- pyjallib/max/skin.py +996 -0
- pyjallib/max/twistBone.py +418 -0
- pyjallib/namePart.py +633 -0
- pyjallib/nameToPath.py +113 -0
- pyjallib/naming.py +1066 -0
- pyjallib/namingConfig.py +844 -0
- pyjallib/perforce.py +735 -0
- pyjallib/reloadModules.py +33 -0
- pyjallib-0.1.0.dist-info/METADATA +28 -0
- pyjallib-0.1.0.dist-info/RECORD +32 -0
- pyjallib-0.1.0.dist-info/WHEEL +5 -0
- pyjallib-0.1.0.dist-info/top_level.txt +1 -0
pyjallib/namingConfig.py
ADDED
@@ -0,0 +1,844 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
namingConfig 모듈 - Naming 클래스의 설정을 관리하는 기능 제공
|
6
|
+
NamePart 객체를 기반으로 네이밍 설정을 저장하고 불러오는 기능 구현
|
7
|
+
"""
|
8
|
+
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
import copy
|
12
|
+
from typing import List, Dict, Any, Optional, Union
|
13
|
+
import csv # Import the csv module
|
14
|
+
|
15
|
+
|
16
|
+
# NamePart 클래스 임포트
|
17
|
+
from pyjallib.namePart import NamePart, NamePartType
|
18
|
+
|
19
|
+
|
20
|
+
class NamingConfig:
|
21
|
+
"""
|
22
|
+
Naming 클래스의 설정을 관리하는 클래스.
|
23
|
+
NamePart 객체 리스트를 관리하고 JSON 파일로 저장/불러오기 기능 제공.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, padding_num: int = 2, name_parts: Optional[List[NamePart]] = None,
|
27
|
+
config_file_path: str = "", default_file_name: str = "namingConfig.json",
|
28
|
+
required_parts: Optional[List[str]] = None):
|
29
|
+
"""
|
30
|
+
클래스 초기화 및 기본 설정값 정의
|
31
|
+
|
32
|
+
Args:
|
33
|
+
padding_num: 인덱스 패딩 자릿수 (기본값: 2)
|
34
|
+
name_parts: 초기 NamePart 객체 리스트 (기본값: None, 기본 파트로 초기화)
|
35
|
+
config_file_path: 설정 파일 경로 (기본값: 빈 문자열)
|
36
|
+
default_file_name: 기본 파일명 (기본값: "namingConfig.json")
|
37
|
+
required_parts: 필수 namePart 목록 (기본값: ["RealName"])
|
38
|
+
"""
|
39
|
+
# NamePart 객체 리스트
|
40
|
+
self.name_parts = name_parts or []
|
41
|
+
|
42
|
+
# 추가 설정
|
43
|
+
self.padding_num = padding_num
|
44
|
+
|
45
|
+
# NamePart 순서 정보 저장
|
46
|
+
self.part_order = []
|
47
|
+
|
48
|
+
# 필수 namePart 정의 (삭제 불가능)
|
49
|
+
self.required_parts = required_parts or ["RealName"]
|
50
|
+
|
51
|
+
# 설정 파일 경로 및 기본 파일명
|
52
|
+
self.config_file_path = config_file_path
|
53
|
+
self.default_file_name = default_file_name
|
54
|
+
|
55
|
+
# 스크립트 디렉토리 기준 기본 경로 설정
|
56
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
57
|
+
config_dir = os.path.join(script_dir, "ConfigFiles")
|
58
|
+
self.default_file_path = os.path.join(config_dir, self.default_file_name)
|
59
|
+
|
60
|
+
# name_parts가 제공되지 않은 경우에만 기본 NamePart 초기화
|
61
|
+
if not self.name_parts:
|
62
|
+
self._initialize_default_parts()
|
63
|
+
else:
|
64
|
+
# 제공된 name_parts가 있는 경우 순서 업데이트 및 타입 자동 업데이트
|
65
|
+
self._update_part_order()
|
66
|
+
self._update_part_types_based_on_order()
|
67
|
+
|
68
|
+
def _initialize_default_parts(self):
|
69
|
+
"""기본 NamePart 객체들 초기화"""
|
70
|
+
# 기본 순서 정의 (명시적으로 순서를 저장)
|
71
|
+
self.part_order = []
|
72
|
+
|
73
|
+
# Prefix 부분 (PREFIX 타입)
|
74
|
+
prefixPart = NamePart("Prefix", NamePartType.PREFIX, ["Pr"], ["Prefix"], False, ["접두사"]) # Add korean descriptions
|
75
|
+
|
76
|
+
# RealName 부분 (REALNAME 타입)
|
77
|
+
realNamePart = NamePart("RealName", NamePartType.REALNAME, [], [], False, []) # Add korean descriptions
|
78
|
+
|
79
|
+
# Index 부분 (INDEX 타입)
|
80
|
+
indexPart = NamePart("Index", NamePartType.INDEX, [], [], False, []) # Add korean descriptions
|
81
|
+
|
82
|
+
# Suffix 부분 (SUFFIX 타입)
|
83
|
+
suffixPart = NamePart("Suffix", NamePartType.SUFFIX, ["Su"], ["Suffix"], False, ["접미사"]) # Add korean descriptions
|
84
|
+
|
85
|
+
# 기본 순서대로 설정
|
86
|
+
self.name_parts = [prefixPart, realNamePart, indexPart, suffixPart]
|
87
|
+
|
88
|
+
self._update_part_order() # 초기화 후 순서 업데이트
|
89
|
+
|
90
|
+
# 타입 자동 업데이트
|
91
|
+
self._update_part_types_based_on_order()
|
92
|
+
|
93
|
+
def _update_part_order(self):
|
94
|
+
"""
|
95
|
+
NamePart 순서 업데이트 - 기본적으로 NamePart 객체의 순서에 따라 업데이트
|
96
|
+
"""
|
97
|
+
self.part_order = [part.get_name() for part in self.name_parts]
|
98
|
+
|
99
|
+
def _get_real_name_index(self) -> int:
|
100
|
+
"""
|
101
|
+
RealName 파트의 인덱스를 반환합니다.
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
RealName 파트의 인덱스, 없으면 -1
|
105
|
+
"""
|
106
|
+
for i, part in enumerate(self.name_parts):
|
107
|
+
if part.get_type() == NamePartType.REALNAME:
|
108
|
+
return i
|
109
|
+
return -1
|
110
|
+
|
111
|
+
def _update_part_types_based_on_order(self) -> bool:
|
112
|
+
"""
|
113
|
+
NamePart 순서에 따라 파트의 타입을 자동으로 업데이트합니다.
|
114
|
+
RealName을 기준으로 앞에 있는 파트는 PREFIX, 뒤에 있는 파트는 SUFFIX로 설정합니다.
|
115
|
+
(RealName과 Index 파트는 예외)
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
업데이트 성공 여부 (True/False)
|
119
|
+
"""
|
120
|
+
# RealName 파트 인덱스 찾기
|
121
|
+
real_name_index = self._get_real_name_index()
|
122
|
+
if real_name_index == -1:
|
123
|
+
print("경고: RealName 파트를 찾을 수 없어 타입 자동 업데이트를 수행할 수 없습니다.")
|
124
|
+
return False
|
125
|
+
|
126
|
+
# 각 파트의 타입을 순서에 따라 업데이트
|
127
|
+
for i, part in enumerate(self.name_parts):
|
128
|
+
partName = part.get_name()
|
129
|
+
|
130
|
+
# RealName은 항상 REALNAME 타입
|
131
|
+
if partName == "RealName":
|
132
|
+
part.set_type(NamePartType.REALNAME)
|
133
|
+
continue
|
134
|
+
|
135
|
+
# Index는 항상 INDEX 타입
|
136
|
+
if partName == "Index":
|
137
|
+
part.set_type(NamePartType.INDEX)
|
138
|
+
continue
|
139
|
+
|
140
|
+
# RealName 앞의 파트는 PREFIX, 뒤의 파트는 SUFFIX
|
141
|
+
if i < real_name_index:
|
142
|
+
part.set_type(NamePartType.PREFIX)
|
143
|
+
else:
|
144
|
+
part.set_type(NamePartType.SUFFIX)
|
145
|
+
|
146
|
+
return True
|
147
|
+
|
148
|
+
def get_part_names(self) -> List[str]:
|
149
|
+
"""
|
150
|
+
모든 NamePart 이름 목록 반환
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
NamePart 이름 목록
|
154
|
+
"""
|
155
|
+
return [part.get_name() for part in self.name_parts]
|
156
|
+
|
157
|
+
def get_part_order(self) -> List[str]:
|
158
|
+
"""
|
159
|
+
NamePart 순서 목록 반환
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
NamePart 이름 순서 목록
|
163
|
+
"""
|
164
|
+
return self.part_order.copy()
|
165
|
+
|
166
|
+
def get_part(self, name: str) -> Optional[NamePart]:
|
167
|
+
"""
|
168
|
+
이름으로 NamePart 객체 가져오기
|
169
|
+
|
170
|
+
Args:
|
171
|
+
name: NamePart 이름
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
NamePart 객체, 없으면 None
|
175
|
+
"""
|
176
|
+
for part in self.name_parts:
|
177
|
+
if part.get_name() == name:
|
178
|
+
return part
|
179
|
+
return None
|
180
|
+
|
181
|
+
def add_part(self, name: str, part_type: NamePartType = NamePartType.UNDEFINED,
|
182
|
+
values: Optional[List[str]] = None, descriptions: Optional[List[str]] = None,
|
183
|
+
korean_descriptions: Optional[List[str]] = None) -> bool: # Add korean_descriptions parameter
|
184
|
+
"""
|
185
|
+
새 NamePart 객체 추가
|
186
|
+
|
187
|
+
Args:
|
188
|
+
name: 추가할 NamePart 이름
|
189
|
+
part_type: NamePart 타입 (기본값: UNDEFINED)
|
190
|
+
values: 사전 정의된 값 목록 (기본값: None)
|
191
|
+
descriptions: 값에 대한 설명 목록 (기본값: None, 값과 동일하게 설정됨)
|
192
|
+
korean_descriptions: 값에 대한 한국어 설명 목록 (기본값: None, 값과 동일하게 설정됨) # Add korean_descriptions doc
|
193
|
+
|
194
|
+
Returns:
|
195
|
+
추가 성공 여부 (True/False)
|
196
|
+
"""
|
197
|
+
if not name:
|
198
|
+
print("오류: 유효한 NamePart 이름을 입력하세요.")
|
199
|
+
return False
|
200
|
+
|
201
|
+
# 이미 존재하는지 확인
|
202
|
+
if self.get_part(name) is not None:
|
203
|
+
print(f"오류: '{name}' NamePart가 이미 존재합니다.")
|
204
|
+
return False
|
205
|
+
|
206
|
+
# 새 NamePart 객체 생성 - NamePart 클래스의 생성자 활용
|
207
|
+
new_part = NamePart(name, part_type, values or [], descriptions, False, korean_descriptions) # Pass korean_descriptions
|
208
|
+
|
209
|
+
# 리스트에 추가
|
210
|
+
self.name_parts.append(new_part)
|
211
|
+
|
212
|
+
# 순서 목록에 추가
|
213
|
+
if name not in self.part_order:
|
214
|
+
self.part_order.append(name)
|
215
|
+
|
216
|
+
# 순서에 따라 타입 업데이트
|
217
|
+
self._update_part_types_based_on_order()
|
218
|
+
return True
|
219
|
+
|
220
|
+
def remove_part(self, name: str) -> bool:
|
221
|
+
"""
|
222
|
+
NamePart 객체 제거 (필수 부분은 제거 불가)
|
223
|
+
|
224
|
+
Args:
|
225
|
+
name: 제거할 NamePart 이름
|
226
|
+
|
227
|
+
Returns:
|
228
|
+
제거 성공 여부 (True/False)
|
229
|
+
"""
|
230
|
+
# 필수 부분은 제거 불가능
|
231
|
+
if name in self.required_parts:
|
232
|
+
print(f"오류: 필수 NamePart '{name}'는 제거할 수 없습니다.")
|
233
|
+
return False
|
234
|
+
|
235
|
+
# 찾아서 제거
|
236
|
+
for i, part in enumerate(self.name_parts):
|
237
|
+
if part.get_name() == name:
|
238
|
+
del self.name_parts[i]
|
239
|
+
|
240
|
+
# 순서 목록에서도 제거
|
241
|
+
if name in self.part_order:
|
242
|
+
self.part_order.remove(name)
|
243
|
+
|
244
|
+
# 순서에 따라 타입 업데이트
|
245
|
+
self._update_part_types_based_on_order()
|
246
|
+
return True
|
247
|
+
|
248
|
+
print(f"오류: '{name}' NamePart가 존재하지 않습니다.")
|
249
|
+
return False
|
250
|
+
|
251
|
+
def reorder_parts(self, new_order: List[str]) -> bool:
|
252
|
+
"""
|
253
|
+
NamePart 순서 변경
|
254
|
+
|
255
|
+
Args:
|
256
|
+
new_order: 새로운 NamePart 이름 순서 배열
|
257
|
+
|
258
|
+
Returns:
|
259
|
+
변경 성공 여부 (True/False)
|
260
|
+
"""
|
261
|
+
# 배열 길이 확인
|
262
|
+
if len(new_order) != len(self.name_parts):
|
263
|
+
print("오류: 새 순서의 항목 수가 기존 NamePart와 일치하지 않습니다.")
|
264
|
+
return False
|
265
|
+
|
266
|
+
# 모든 필수 부분이 포함되어 있는지 확인
|
267
|
+
for part in self.required_parts:
|
268
|
+
if part not in new_order:
|
269
|
+
print(f"오류: 필수 NamePart '{part}'가 새 순서에 포함되어 있지 않습니다.")
|
270
|
+
return False
|
271
|
+
|
272
|
+
# 모든 이름이 현재 존재하는지 확인
|
273
|
+
current_names = self.get_part_names()
|
274
|
+
for name in new_order:
|
275
|
+
if name not in current_names:
|
276
|
+
print(f"오류: '{name}' NamePart가 존재하지 않습니다.")
|
277
|
+
return False
|
278
|
+
|
279
|
+
# 순서 변경을 위한 새 리스트 생성
|
280
|
+
reordered_parts = []
|
281
|
+
for name in new_order:
|
282
|
+
part = self.get_part(name)
|
283
|
+
if part:
|
284
|
+
reordered_parts.append(part)
|
285
|
+
|
286
|
+
# 새 순서로 업데이트
|
287
|
+
self.name_parts = reordered_parts
|
288
|
+
self.part_order = new_order.copy()
|
289
|
+
|
290
|
+
# 순서에 따라 타입 업데이트
|
291
|
+
self._update_part_types_based_on_order()
|
292
|
+
return True
|
293
|
+
|
294
|
+
def set_padding_num(self, padding_num: int) -> bool:
|
295
|
+
"""
|
296
|
+
인덱스 자릿수 설정
|
297
|
+
|
298
|
+
Args:
|
299
|
+
padding_num: 설정할 패딩 자릿수
|
300
|
+
|
301
|
+
Returns:
|
302
|
+
설정 성공 여부 (True/False)
|
303
|
+
"""
|
304
|
+
if not isinstance(padding_num, int) or padding_num < 1:
|
305
|
+
print("오류: 패딩 자릿수는 1 이상의 정수여야 합니다.")
|
306
|
+
return False
|
307
|
+
|
308
|
+
self.padding_num = padding_num
|
309
|
+
return True
|
310
|
+
|
311
|
+
def set_part_type(self, part_name: str, part_type: NamePartType) -> bool:
|
312
|
+
"""
|
313
|
+
특정 NamePart의 타입 설정
|
314
|
+
|
315
|
+
Args:
|
316
|
+
part_name: NamePart 이름
|
317
|
+
part_type: 설정할 타입 (NamePartType 열거형 값)
|
318
|
+
|
319
|
+
Returns:
|
320
|
+
설정 성공 여부 (True/False)
|
321
|
+
"""
|
322
|
+
part = self.get_part(part_name)
|
323
|
+
if not part:
|
324
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
325
|
+
return False
|
326
|
+
|
327
|
+
# 필수 RealName 부분은 항상 REALNAME 타입이어야 함
|
328
|
+
if part_name == "RealName" and part_type != NamePartType.REALNAME:
|
329
|
+
print("오류: RealName 부분은 반드시 REALNAME 타입이어야 합니다.")
|
330
|
+
return False
|
331
|
+
|
332
|
+
# Index 부분은 항상 INDEX 타입이어야 함
|
333
|
+
if part_name == "Index" and part_type != NamePartType.INDEX:
|
334
|
+
print("오류: Index 부분은 반드시 INDEX 타입이어야 합니다.")
|
335
|
+
return False
|
336
|
+
|
337
|
+
part.set_type(part_type)
|
338
|
+
return True
|
339
|
+
|
340
|
+
def get_part_type(self, part_name: str) -> Optional[NamePartType]:
|
341
|
+
"""
|
342
|
+
특정 NamePart의 타입 가져오기
|
343
|
+
|
344
|
+
Args:
|
345
|
+
part_name: NamePart 이름
|
346
|
+
|
347
|
+
Returns:
|
348
|
+
NamePart 타입, 없으면 None
|
349
|
+
"""
|
350
|
+
part = self.get_part(part_name)
|
351
|
+
if not part:
|
352
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
353
|
+
return None
|
354
|
+
|
355
|
+
return part.get_type()
|
356
|
+
|
357
|
+
def set_part_values(self, part_name: str, values: List[str],
|
358
|
+
descriptions: Optional[List[str]] = None,
|
359
|
+
korean_descriptions: Optional[List[str]] = None) -> bool: # Add korean_descriptions parameter
|
360
|
+
"""
|
361
|
+
특정 NamePart의 사전 정의 값 설정
|
362
|
+
|
363
|
+
Args:
|
364
|
+
part_name: NamePart 이름
|
365
|
+
values: 설정할 사전 정의 값 리스트
|
366
|
+
descriptions: 설정할 설명 목록 (기본값: None, 값과 같은 설명 사용)
|
367
|
+
korean_descriptions: 설정할 한국어 설명 목록 (기본값: None, 값과 같은 설명 사용) # Add korean_descriptions doc
|
368
|
+
|
369
|
+
Returns:
|
370
|
+
설정 성공 여부 (True/False)
|
371
|
+
"""
|
372
|
+
part = self.get_part(part_name)
|
373
|
+
if not part:
|
374
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
375
|
+
return False
|
376
|
+
|
377
|
+
# REALNAME이나 INDEX 타입은 사전 정의 값 설정 불가
|
378
|
+
if part.is_realname() or part.is_index():
|
379
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 사전 정의 값을 설정할 수 없습니다.")
|
380
|
+
return False
|
381
|
+
|
382
|
+
if not values:
|
383
|
+
print(f"오류: {part_name} 부분의 사전 정의 값은 적어도 하나 이상 있어야 합니다.")
|
384
|
+
return False
|
385
|
+
|
386
|
+
# 값 설정
|
387
|
+
part.set_predefined_values(values, descriptions, korean_descriptions) # Pass korean_descriptions
|
388
|
+
|
389
|
+
return True
|
390
|
+
|
391
|
+
def set_part_value_by_csv(self, part_name: str, csv_file_path: str) -> bool:
|
392
|
+
"""
|
393
|
+
특정 NamePart의 사전 정의 값을 CSV 파일로 설정
|
394
|
+
CSV 파일 형식: value,description,koreanDescription (각 줄당)
|
395
|
+
|
396
|
+
Args:
|
397
|
+
part_name: NamePart 이름
|
398
|
+
csv_file_path: CSV 파일 경로
|
399
|
+
|
400
|
+
Returns:
|
401
|
+
설정 성공 여부 (True/False)
|
402
|
+
"""
|
403
|
+
part = self.get_part(part_name)
|
404
|
+
if not part:
|
405
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
406
|
+
return False
|
407
|
+
|
408
|
+
# REALNAME이나 INDEX 타입은 사전 정의 값 설정 불가
|
409
|
+
if part.is_realname() or part.is_index():
|
410
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 사전 정의 값을 설정할 수 없습니다.")
|
411
|
+
return False
|
412
|
+
|
413
|
+
# CSV 파일에서 값, 설명, 한국어 설명 읽기
|
414
|
+
values = []
|
415
|
+
descriptions = []
|
416
|
+
korean_descriptions = []
|
417
|
+
try:
|
418
|
+
with open(csv_file_path, 'r', encoding='utf-8', newline='') as f:
|
419
|
+
reader = csv.reader(f)
|
420
|
+
for row in reader:
|
421
|
+
if len(row) >= 3: # Ensure row has at least 3 columns
|
422
|
+
value = row[0].strip()
|
423
|
+
description = row[1].strip()
|
424
|
+
korean_description = row[2].strip()
|
425
|
+
if value: # Skip empty values
|
426
|
+
values.append(value)
|
427
|
+
descriptions.append(description if description else value) # Use value if description is empty
|
428
|
+
korean_descriptions.append(korean_description if korean_description else value) # Use value if korean_description is empty
|
429
|
+
elif len(row) == 2: # Handle case with value and description only
|
430
|
+
value = row[0].strip()
|
431
|
+
description = row[1].strip()
|
432
|
+
if value:
|
433
|
+
values.append(value)
|
434
|
+
descriptions.append(description if description else value)
|
435
|
+
korean_descriptions.append(value) # Use value as korean description
|
436
|
+
elif len(row) == 1: # Handle case with value only
|
437
|
+
value = row[0].strip()
|
438
|
+
if value:
|
439
|
+
values.append(value)
|
440
|
+
descriptions.append(value)
|
441
|
+
korean_descriptions.append(value)
|
442
|
+
|
443
|
+
if not values:
|
444
|
+
print(f"오류: CSV 파일 '{csv_file_path}'에서 유효한 값을 찾을 수 없습니다.")
|
445
|
+
return False
|
446
|
+
|
447
|
+
# 값, 설명, 한국어 설명 설정
|
448
|
+
return self.set_part_values(part_name, values, descriptions, korean_descriptions)
|
449
|
+
except FileNotFoundError:
|
450
|
+
print(f"오류: CSV 파일을 찾을 수 없습니다: {csv_file_path}")
|
451
|
+
return False
|
452
|
+
except Exception as e:
|
453
|
+
print(f"오류: CSV 파일을 읽는 중 오류 발생: {e}")
|
454
|
+
return False
|
455
|
+
|
456
|
+
def add_part_value(self, part_name: str, value: str,
|
457
|
+
description: Optional[str] = None,
|
458
|
+
korean_description: Optional[str] = None) -> bool: # Add korean_description parameter
|
459
|
+
"""
|
460
|
+
특정 NamePart에 사전 정의 값 추가
|
461
|
+
|
462
|
+
Args:
|
463
|
+
part_name: NamePart 이름
|
464
|
+
value: 추가할 사전 정의 값
|
465
|
+
description: 추가할 값의 설명 (기본값: None, 값과 같은 설명 사용)
|
466
|
+
korean_description: 추가할 값의 한국어 설명 (기본값: None, 값과 같은 설명 사용) # Add korean_description doc
|
467
|
+
|
468
|
+
Returns:
|
469
|
+
추가 성공 여부 (True/False)
|
470
|
+
"""
|
471
|
+
part = self.get_part(part_name)
|
472
|
+
if not part:
|
473
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
474
|
+
return False
|
475
|
+
|
476
|
+
# REALNAME이나 INDEX 타입은 사전 정의 값 추가 불가
|
477
|
+
if part.is_realname() or part.is_index():
|
478
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 사전 정의 값을 추가할 수 없습니다.")
|
479
|
+
return False
|
480
|
+
|
481
|
+
# 값이 이미 존재하는지 확인
|
482
|
+
if part.contains_value(value):
|
483
|
+
print(f"오류: '{value}'가 이미 {part_name} 부분의 사전 정의 값에 존재합니다.")
|
484
|
+
return False
|
485
|
+
|
486
|
+
# description이 없으면 값을 설명으로 사용
|
487
|
+
if description is None:
|
488
|
+
description = value
|
489
|
+
|
490
|
+
# korean_description이 없으면 값을 설명으로 사용
|
491
|
+
if korean_description is None:
|
492
|
+
korean_description = value
|
493
|
+
|
494
|
+
# NamePart 클래스의 add_predefined_value 메소드 직접 활용
|
495
|
+
return part.add_predefined_value(value, description, korean_description) # Pass korean_description
|
496
|
+
|
497
|
+
def remove_part_value(self, part_name: str, value: str) -> bool:
|
498
|
+
"""
|
499
|
+
특정 NamePart에서 사전 정의 값과 해당 설명 제거
|
500
|
+
|
501
|
+
Args:
|
502
|
+
part_name: NamePart 이름
|
503
|
+
value: 제거할 사전 정의 값
|
504
|
+
|
505
|
+
Returns:
|
506
|
+
제거 성공 여부 (True/False)
|
507
|
+
"""
|
508
|
+
part = self.get_part(part_name)
|
509
|
+
if not part:
|
510
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
511
|
+
return False
|
512
|
+
|
513
|
+
# REALNAME이나 INDEX 타입은 사전 정의 값 제거 불가
|
514
|
+
if part.is_realname() or part.is_index():
|
515
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 사전 정의 값을 제거할 수 없습니다.")
|
516
|
+
return False
|
517
|
+
|
518
|
+
# 값이 존재하는지 확인
|
519
|
+
if not part.contains_value(value):
|
520
|
+
print(f"오류: '{value}'가 {part_name} 부분의 사전 정의 값에 존재하지 않습니다.")
|
521
|
+
return False
|
522
|
+
|
523
|
+
# 마지막 값인지 확인
|
524
|
+
if part.get_value_count() <= 1:
|
525
|
+
print(f"오류: {part_name} 부분의 사전 정의 값은 적어도 하나 이상 있어야 합니다.")
|
526
|
+
return False
|
527
|
+
|
528
|
+
# NamePart 클래스의 remove_predefined_value 메소드 직접 활용
|
529
|
+
return part.remove_predefined_value(value)
|
530
|
+
|
531
|
+
def set_part_descriptions(self, part_name: str, descriptions: List[str]) -> bool:
|
532
|
+
"""
|
533
|
+
특정 NamePart의 설명 목록 설정
|
534
|
+
|
535
|
+
Args:
|
536
|
+
part_name: NamePart 이름
|
537
|
+
descriptions: 설정할 설명 목록
|
538
|
+
|
539
|
+
Returns:
|
540
|
+
설정 성공 여부 (True/False)
|
541
|
+
"""
|
542
|
+
part = self.get_part(part_name)
|
543
|
+
if not part:
|
544
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
545
|
+
return False
|
546
|
+
|
547
|
+
# REALNAME이나 INDEX 타입은 설명 설정 불가
|
548
|
+
if part.is_realname() or part.is_index():
|
549
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 설명을 설정할 수 없습니다.")
|
550
|
+
return False
|
551
|
+
|
552
|
+
# NamePart 클래스 메소드 활용하여 설명 설정
|
553
|
+
values = part.get_predefined_values()
|
554
|
+
|
555
|
+
# 길이 맞추기
|
556
|
+
if len(descriptions) < len(values):
|
557
|
+
descriptions.extend([""] * (len(values) - len(descriptions)))
|
558
|
+
elif len(descriptions) > len(values):
|
559
|
+
descriptions = descriptions[:len(values)]
|
560
|
+
|
561
|
+
# 각 값에 대한 설명 설정 (NamePart.set_description 사용)
|
562
|
+
success = True
|
563
|
+
for i, value in enumerate(values):
|
564
|
+
if not part.set_description(value, descriptions[i]):
|
565
|
+
success = False # 실패 시 기록 (이론상 발생하지 않음)
|
566
|
+
|
567
|
+
return success
|
568
|
+
|
569
|
+
def get_part_descriptions(self, part_name: str) -> List[str]:
|
570
|
+
"""
|
571
|
+
특정 NamePart의 설명 목록 가져오기
|
572
|
+
|
573
|
+
Args:
|
574
|
+
part_name: NamePart 이름
|
575
|
+
|
576
|
+
Returns:
|
577
|
+
설명 목록
|
578
|
+
"""
|
579
|
+
part = self.get_part(part_name)
|
580
|
+
if not part:
|
581
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
582
|
+
return []
|
583
|
+
|
584
|
+
return part.get_descriptions()
|
585
|
+
|
586
|
+
def set_part_korean_descriptions(self, part_name: str, korean_descriptions: List[str]) -> bool:
|
587
|
+
"""
|
588
|
+
특정 NamePart의 한국어 설명 목록 설정
|
589
|
+
|
590
|
+
Args:
|
591
|
+
part_name: NamePart 이름
|
592
|
+
korean_descriptions: 설정할 한국어 설명 목록
|
593
|
+
|
594
|
+
Returns:
|
595
|
+
설정 성공 여부 (True/False)
|
596
|
+
"""
|
597
|
+
part = self.get_part(part_name)
|
598
|
+
if not part:
|
599
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
600
|
+
return False
|
601
|
+
|
602
|
+
# REALNAME이나 INDEX 타입은 설명 설정 불가
|
603
|
+
if part.is_realname() or part.is_index():
|
604
|
+
print(f"오류: {part_name} 부분은 {part.get_type().name} 타입이므로 한국어 설명을 설정할 수 없습니다.")
|
605
|
+
return False
|
606
|
+
|
607
|
+
# NamePart 클래스 메소드 활용하여 설명 설정
|
608
|
+
values = part.get_predefined_values()
|
609
|
+
|
610
|
+
# 길이 맞추기
|
611
|
+
if len(korean_descriptions) < len(values):
|
612
|
+
korean_descriptions.extend([""] * (len(values) - len(korean_descriptions)))
|
613
|
+
elif len(korean_descriptions) > len(values):
|
614
|
+
korean_descriptions = korean_descriptions[:len(values)]
|
615
|
+
|
616
|
+
# 각 값에 대한 한국어 설명 설정 (NamePart.set_korean_description 사용)
|
617
|
+
success = True
|
618
|
+
for i, value in enumerate(values):
|
619
|
+
if not part.set_korean_description(value, korean_descriptions[i]):
|
620
|
+
success = False # 실패 시 기록 (이론상 발생하지 않음)
|
621
|
+
|
622
|
+
return success
|
623
|
+
|
624
|
+
def get_part_korean_descriptions(self, part_name: str) -> List[str]:
|
625
|
+
"""
|
626
|
+
특정 NamePart의 한국어 설명 목록 가져오기
|
627
|
+
|
628
|
+
Args:
|
629
|
+
part_name: NamePart 이름
|
630
|
+
|
631
|
+
Returns:
|
632
|
+
한국어 설명 목록
|
633
|
+
"""
|
634
|
+
part = self.get_part(part_name)
|
635
|
+
if not part:
|
636
|
+
print(f"오류: '{part_name}' NamePart가 존재하지 않습니다.")
|
637
|
+
return []
|
638
|
+
|
639
|
+
return part.get_korean_descriptions()
|
640
|
+
|
641
|
+
def get_prefix_parts(self) -> List[NamePart]:
|
642
|
+
"""
|
643
|
+
모든 PREFIX 타입 NamePart 가져오기
|
644
|
+
|
645
|
+
Returns:
|
646
|
+
PREFIX 타입의 NamePart 객체 리스트
|
647
|
+
"""
|
648
|
+
return [part for part in self.name_parts if part.is_prefix()]
|
649
|
+
|
650
|
+
def get_suffix_parts(self) -> List[NamePart]:
|
651
|
+
"""
|
652
|
+
모든 SUFFIX 타입 NamePart 가져오기
|
653
|
+
|
654
|
+
Returns:
|
655
|
+
SUFFIX 타입의 NamePart 객체 리스트
|
656
|
+
"""
|
657
|
+
return [part for part in self.name_parts if part.is_suffix()]
|
658
|
+
|
659
|
+
def get_realname_part(self) -> Optional[NamePart]:
|
660
|
+
"""
|
661
|
+
REALNAME 타입 NamePart 가져오기
|
662
|
+
|
663
|
+
Returns:
|
664
|
+
REALNAME 타입의 NamePart 객체, 없으면 None
|
665
|
+
"""
|
666
|
+
for part in self.name_parts:
|
667
|
+
if part.is_realname():
|
668
|
+
return part
|
669
|
+
return None
|
670
|
+
|
671
|
+
def get_index_part(self) -> Optional[NamePart]:
|
672
|
+
"""
|
673
|
+
INDEX 타입 NamePart 가져오기
|
674
|
+
|
675
|
+
Returns:
|
676
|
+
INDEX 타입의 NamePart 객체, 없으면 None
|
677
|
+
"""
|
678
|
+
for part in self.name_parts:
|
679
|
+
if part.is_index():
|
680
|
+
return part
|
681
|
+
return None
|
682
|
+
|
683
|
+
def save(self, file_path: Optional[str] = None) -> bool:
|
684
|
+
"""
|
685
|
+
현재 설정을 JSON 파일로 저장
|
686
|
+
|
687
|
+
Args:
|
688
|
+
file_path: 저장할 파일 경로 (기본값: self.default_file_path)
|
689
|
+
|
690
|
+
Returns:
|
691
|
+
저장 성공 여부 (True/False)
|
692
|
+
"""
|
693
|
+
save_path = file_path or self.default_file_path
|
694
|
+
|
695
|
+
try:
|
696
|
+
# 저장할 데이터 준비
|
697
|
+
save_data = {
|
698
|
+
"paddingNum": self.padding_num,
|
699
|
+
"partOrder": self.part_order, # 순서 정보 저장
|
700
|
+
"nameParts": []
|
701
|
+
}
|
702
|
+
|
703
|
+
# 각 NamePart 객체를 딕셔너리로 변환하여 추가
|
704
|
+
for part in self.name_parts:
|
705
|
+
save_data["nameParts"].append(part.to_dict())
|
706
|
+
|
707
|
+
# JSON 파일로 저장
|
708
|
+
with open(save_path, 'w', encoding='utf-8') as f:
|
709
|
+
json.dump(save_data, f, indent=4, ensure_ascii=False)
|
710
|
+
|
711
|
+
self.config_file_path = save_path
|
712
|
+
return True
|
713
|
+
except Exception as e:
|
714
|
+
print(f"설정 저장 중 오류 발생: {e}")
|
715
|
+
return False
|
716
|
+
|
717
|
+
def load(self, file_path: Optional[str] = None) -> bool:
|
718
|
+
"""
|
719
|
+
JSON 파일에서 설정 불러오기
|
720
|
+
|
721
|
+
Args:
|
722
|
+
file_path: 불러올 파일 경로 (기본값: self.default_file_path)
|
723
|
+
|
724
|
+
Returns:
|
725
|
+
로드 성공 여부 (True/False)
|
726
|
+
"""
|
727
|
+
load_path = file_path or self.default_file_path
|
728
|
+
|
729
|
+
try:
|
730
|
+
if os.path.exists(load_path):
|
731
|
+
with open(load_path, 'r', encoding='utf-8') as f:
|
732
|
+
loaded_data = json.load(f)
|
733
|
+
|
734
|
+
# 필수 키가 있는지 확인
|
735
|
+
if "nameParts" not in loaded_data:
|
736
|
+
print("경고: 설정 파일에 필수 키 'nameParts'가 없습니다.")
|
737
|
+
return False
|
738
|
+
|
739
|
+
# paddingNum 불러오기
|
740
|
+
if "paddingNum" in loaded_data:
|
741
|
+
self.padding_num = loaded_data["paddingNum"]
|
742
|
+
|
743
|
+
# 파트 순서 불러오기
|
744
|
+
if "partOrder" in loaded_data:
|
745
|
+
self.part_order = loaded_data["partOrder"]
|
746
|
+
else:
|
747
|
+
# 없으면 기본 순서 생성
|
748
|
+
self.part_order = [part_data["name"] for part_data in loaded_data["nameParts"]]
|
749
|
+
|
750
|
+
# NamePart 객체 리스트 생성
|
751
|
+
new_parts = []
|
752
|
+
for part_data in loaded_data["nameParts"]:
|
753
|
+
part = NamePart.from_dict(part_data)
|
754
|
+
new_parts.append(part)
|
755
|
+
|
756
|
+
# 필수 NamePart가 포함되어 있는지 확인
|
757
|
+
part_names = [part.get_name() for part in new_parts]
|
758
|
+
for required_name in self.required_parts:
|
759
|
+
if required_name not in part_names:
|
760
|
+
print(f"경고: 필수 NamePart '{required_name}'가 설정에 포함되어 있지 않습니다.")
|
761
|
+
return False
|
762
|
+
|
763
|
+
# 모든 확인이 통과되면 데이터 업데이트
|
764
|
+
self.name_parts = new_parts
|
765
|
+
self.config_file_path = load_path
|
766
|
+
|
767
|
+
# 순서에 따라 타입 업데이트
|
768
|
+
self._update_part_types_based_on_order()
|
769
|
+
self._update_part_order() # 순서 업데이트
|
770
|
+
return True
|
771
|
+
else:
|
772
|
+
print(f"설정 파일을 찾을 수 없습니다: {load_path}")
|
773
|
+
return False
|
774
|
+
except Exception as e:
|
775
|
+
print(f"설정 로드 중 오류 발생: {e}")
|
776
|
+
return False
|
777
|
+
|
778
|
+
def apply_to_naming(self, naming_instance) -> bool:
|
779
|
+
"""
|
780
|
+
설정을 Naming 인스턴스에 적용
|
781
|
+
|
782
|
+
Args:
|
783
|
+
naming_instance: 설정을 적용할 Naming 클래스 인스턴스
|
784
|
+
|
785
|
+
Returns:
|
786
|
+
적용 성공 여부 (True/False)
|
787
|
+
"""
|
788
|
+
try:
|
789
|
+
# NamePart 객체 리스트 복사하여 적용
|
790
|
+
naming_instance._nameParts = copy.deepcopy(self.name_parts)
|
791
|
+
|
792
|
+
# paddingNum 설정
|
793
|
+
naming_instance._paddingNum = self.padding_num
|
794
|
+
|
795
|
+
return True
|
796
|
+
except Exception as e:
|
797
|
+
print(f"설정 적용 중 오류 발생: {e}")
|
798
|
+
return False
|
799
|
+
|
800
|
+
def insert_part(self, name: str, part_type: NamePartType, position: int,
|
801
|
+
values: Optional[List[str]] = None,
|
802
|
+
descriptions: Optional[List[str]] = None,
|
803
|
+
korean_descriptions: Optional[List[str]] = None) -> bool: # Add value/description parameters
|
804
|
+
"""
|
805
|
+
특정 위치에 새 NamePart 삽입
|
806
|
+
|
807
|
+
Args:
|
808
|
+
name: 삽입할 NamePart 이름
|
809
|
+
part_type: NamePart 타입
|
810
|
+
position: 삽입할 위치 (인덱스)
|
811
|
+
values: 사전 정의된 값 목록 (기본값: None) # Add doc
|
812
|
+
descriptions: 값에 대한 설명 목록 (기본값: None) # Add doc
|
813
|
+
korean_descriptions: 값에 대한 한국어 설명 목록 (기본값: None) # Add doc
|
814
|
+
|
815
|
+
Returns:
|
816
|
+
삽입 성공 여부 (True/False)
|
817
|
+
"""
|
818
|
+
if not name:
|
819
|
+
print("오류: 유효한 NamePart 이름을 입력하세요.")
|
820
|
+
return False
|
821
|
+
|
822
|
+
# 이미 존재하는지 확인
|
823
|
+
if self.get_part(name) is not None:
|
824
|
+
print(f"오류: '{name}' NamePart가 이미 존재합니다.")
|
825
|
+
return False
|
826
|
+
|
827
|
+
# 위치 범위 확인
|
828
|
+
if position < 0 or position > len(self.name_parts):
|
829
|
+
print(f"오류: 위치가 유효하지 않습니다. 0에서 {len(self.name_parts)} 사이의 값이어야 합니다.")
|
830
|
+
return False
|
831
|
+
|
832
|
+
# 새 NamePart 생성 (값과 설명 포함)
|
833
|
+
new_part = NamePart(name, part_type, values or [], descriptions, False, korean_descriptions) # Pass values/descriptions
|
834
|
+
|
835
|
+
# 지정된 위치에 삽입
|
836
|
+
self.name_parts.insert(position, new_part)
|
837
|
+
|
838
|
+
# 순서 목록 업데이트
|
839
|
+
if name not in self.part_order:
|
840
|
+
self.part_order.insert(position, name)
|
841
|
+
|
842
|
+
# 순서에 따라 타입 업데이트
|
843
|
+
self._update_part_types_based_on_order()
|
844
|
+
return True
|