pyjallib 0.1.11__py3-none-any.whl → 0.1.12__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 +1 -1
- pyjallib/max/bip.py +67 -2
- pyjallib/naming.py +26 -1
- pyjallib/namingConfig.py +3 -2
- pyjallib/perforce.py +362 -0
- {pyjallib-0.1.11.dist-info → pyjallib-0.1.12.dist-info}/METADATA +1 -1
- {pyjallib-0.1.11.dist-info → pyjallib-0.1.12.dist-info}/RECORD +8 -8
- {pyjallib-0.1.11.dist-info → pyjallib-0.1.12.dist-info}/WHEEL +0 -0
pyjallib/__init__.py
CHANGED
pyjallib/max/bip.py
CHANGED
@@ -439,6 +439,71 @@ class Bip:
|
|
439
439
|
if colNum > 0:
|
440
440
|
rt.biped.deleteAllCopyCollections(inBipRoot.controller)
|
441
441
|
|
442
|
+
def collapse_layers(self, inBipRoot):
|
443
|
+
"""
|
444
|
+
Biped 레이어 병합
|
445
|
+
|
446
|
+
Args:
|
447
|
+
inBipRoot: 대상 Biped 객체
|
448
|
+
"""
|
449
|
+
if not self.is_biped_object(inBipRoot):
|
450
|
+
return False
|
451
|
+
|
452
|
+
layerNum = rt.biped.numLayers(inBipRoot.controller)
|
453
|
+
while layerNum > 0:
|
454
|
+
rt.biped.collapseAtLayer(inBipRoot.controller, 0)
|
455
|
+
layerNum = rt.biped.numLayers(inBipRoot.controller)
|
456
|
+
|
457
|
+
def save_bip_file(self, inBipRoot, inFile, inBakeAllKeys=True, inCollapseLayers=True, progress_callback=None):
|
458
|
+
"""
|
459
|
+
Biped BIP 파일 저장
|
460
|
+
|
461
|
+
Args:
|
462
|
+
inBipRoot: 저장 대상 Biped 루트 노드
|
463
|
+
inFile: 저장할 BIP 파일 경로
|
464
|
+
inBakeAllKeys: 모든 키를 베이크할지 여부 (기본값: True)
|
465
|
+
inCollapseLayers: 레이어를 병합할지 여부 (기본값: True)
|
466
|
+
|
467
|
+
Returns:
|
468
|
+
bool: 저장 성공 시 True, 실패 시 False
|
469
|
+
"""
|
470
|
+
if not self.is_biped_object(inBipRoot):
|
471
|
+
return False
|
472
|
+
|
473
|
+
directory = os.path.dirname(inFile)
|
474
|
+
if directory and not os.path.exists(directory):
|
475
|
+
try:
|
476
|
+
os.makedirs(directory, exist_ok=True)
|
477
|
+
except OSError as e:
|
478
|
+
return False
|
479
|
+
|
480
|
+
if inCollapseLayers:
|
481
|
+
self.collapse_layers(inBipRoot)
|
482
|
+
|
483
|
+
if inBakeAllKeys:
|
484
|
+
allTargetBipedObjs = self.get_nodes(inBipRoot)
|
485
|
+
startFrame = rt.execute("(animationRange.start as integer) / TicksPerFrame")
|
486
|
+
endFrame = rt.execute("(animationRange.end as integer) / TicksPerFrame")
|
487
|
+
totalFrame = endFrame - startFrame + 1
|
488
|
+
|
489
|
+
for frame in range(startFrame, endFrame + 1):
|
490
|
+
for item in allTargetBipedObjs:
|
491
|
+
if item == item.controller.rootNode:
|
492
|
+
horizontalController = rt.getPropertyController(item.controller, "horizontal")
|
493
|
+
verticalController = rt.getPropertyController(item.controller, "vertical")
|
494
|
+
turningController = rt.getPropertyController(item.controller, "turning")
|
495
|
+
|
496
|
+
rt.biped.addNewKey(horizontalController, frame)
|
497
|
+
rt.biped.addNewKey(verticalController, frame)
|
498
|
+
rt.biped.addNewKey(turningController, frame)
|
499
|
+
else:
|
500
|
+
rt.biped.addNewKey(item.controller, frame)
|
501
|
+
if progress_callback:
|
502
|
+
progress_callback(frame - startFrame + 1, totalFrame)
|
503
|
+
|
504
|
+
rt.biped.saveBipFile(inBipRoot.controller, inFile)
|
505
|
+
return True
|
506
|
+
|
442
507
|
def link_base_skeleton(self, skinBoneBaseName="b"):
|
443
508
|
"""
|
444
509
|
기본 스켈레톤 링크 (Biped와 일반 뼈대 연결)
|
@@ -690,8 +755,8 @@ class Bip:
|
|
690
755
|
toeNub.name = self.name.replace_name_part("Nub", toeNub.name, self.name.get_name_part_value_by_description("Nub", "Nub"))
|
691
756
|
|
692
757
|
if toeLinkNum == 1:
|
693
|
-
rToesList[0][0].name = self.name.replace_name_part("RealName",
|
694
|
-
rToesList[0][0].name = self.name.remove_name_part("Index",
|
758
|
+
rToesList[0][0].name = self.name.replace_name_part("RealName", rToesList[0][0].name, "ball")
|
759
|
+
rToesList[0][0].name = self.name.remove_name_part("Index", rToesList[0][0].name)
|
695
760
|
else:
|
696
761
|
for i, item in enumerate(rToesList[0]):
|
697
762
|
item.name = self.name.replace_name_part("RealName", item.name, "ball")
|
pyjallib/naming.py
CHANGED
@@ -258,6 +258,18 @@ class Naming:
|
|
258
258
|
패딩 숫자
|
259
259
|
"""
|
260
260
|
return self._paddingNum
|
261
|
+
|
262
|
+
def get_filtering_char(self, inStr):
|
263
|
+
"""
|
264
|
+
문자열에서 구분자 문자 가져오기
|
265
|
+
|
266
|
+
Args:
|
267
|
+
inStr: 확인할 문자열
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
구분자 문자 (' ' 또는 '_' 또는 '')
|
271
|
+
"""
|
272
|
+
return self._get_filtering_char(inStr)
|
261
273
|
|
262
274
|
def get_name_part(self, inNamePartName):
|
263
275
|
"""
|
@@ -273,6 +285,15 @@ class Naming:
|
|
273
285
|
if part.get_name() == inNamePartName:
|
274
286
|
return part
|
275
287
|
return None
|
288
|
+
|
289
|
+
def get_name_parts(self):
|
290
|
+
"""
|
291
|
+
모든 namePart 객체 가져오기
|
292
|
+
|
293
|
+
Returns:
|
294
|
+
namePart 객체 리스트
|
295
|
+
"""
|
296
|
+
return self._nameParts
|
276
297
|
|
277
298
|
def get_name_part_index(self, inNamePartName):
|
278
299
|
"""
|
@@ -473,7 +494,11 @@ class Naming:
|
|
473
494
|
|
474
495
|
# 배열을 문자열로 결합
|
475
496
|
newName = self._combine(combinedNameArray, inFilChar)
|
476
|
-
|
497
|
+
|
498
|
+
# "Index"키가 있을 때만 패딩 적용
|
499
|
+
if "Index" in inPartsDict:
|
500
|
+
newName = self.set_index_padding_num(newName)
|
501
|
+
|
477
502
|
return newName
|
478
503
|
|
479
504
|
def get_RealName(self, inStr):
|
pyjallib/namingConfig.py
CHANGED
@@ -388,7 +388,7 @@ class NamingConfig:
|
|
388
388
|
|
389
389
|
return True
|
390
390
|
|
391
|
-
def set_part_value_by_csv(self, part_name: str, csv_file_path: str) -> bool:
|
391
|
+
def set_part_value_by_csv(self, part_name: str, csv_file_path: str, encoding: str = "utf-8") -> bool:
|
392
392
|
"""
|
393
393
|
특정 NamePart의 사전 정의 값을 CSV 파일로 설정
|
394
394
|
CSV 파일 형식: value,description,koreanDescription (각 줄당)
|
@@ -396,6 +396,7 @@ class NamingConfig:
|
|
396
396
|
Args:
|
397
397
|
part_name: NamePart 이름
|
398
398
|
csv_file_path: CSV 파일 경로
|
399
|
+
encoding: CSV 파일 인코딩 (기본값: "utf-8", "utf-8-sig" 등 사용 가능)
|
399
400
|
|
400
401
|
Returns:
|
401
402
|
설정 성공 여부 (True/False)
|
@@ -415,7 +416,7 @@ class NamingConfig:
|
|
415
416
|
descriptions = []
|
416
417
|
korean_descriptions = []
|
417
418
|
try:
|
418
|
-
with open(csv_file_path, 'r', encoding=
|
419
|
+
with open(csv_file_path, 'r', encoding=encoding, newline='') as f:
|
419
420
|
reader = csv.reader(f)
|
420
421
|
for row in reader:
|
421
422
|
if len(row) >= 3: # Ensure row has at least 3 columns
|
pyjallib/perforce.py
CHANGED
@@ -195,6 +195,183 @@ class Perforce:
|
|
195
195
|
self._handle_p4_exception(e, f"설명으로 체인지 리스트 조회 ('{description}')")
|
196
196
|
return {}
|
197
197
|
|
198
|
+
def get_change_list_by_description_pattern(self, description_pattern: str, exact_match: bool = False) -> list:
|
199
|
+
"""설명 패턴과 일치하는 Pending 체인지 리스트들을 가져옵니다.
|
200
|
+
|
201
|
+
Args:
|
202
|
+
description_pattern (str): 검색할 설명 패턴
|
203
|
+
exact_match (bool, optional): True면 정확히 일치하는 설명만,
|
204
|
+
False면 패턴이 포함된 설명도 포함. 기본값 False
|
205
|
+
|
206
|
+
Returns:
|
207
|
+
list: 패턴과 일치하는 체인지 리스트 정보들의 리스트
|
208
|
+
"""
|
209
|
+
if not self._is_connected():
|
210
|
+
return []
|
211
|
+
|
212
|
+
search_type = "정확히 일치" if exact_match else "패턴 포함"
|
213
|
+
logger.debug(f"설명 패턴으로 체인지 리스트 조회 중 ({search_type}): '{description_pattern}'")
|
214
|
+
|
215
|
+
try:
|
216
|
+
pending_changes = self.p4.run_changes("-l", "-s", "pending", "-u", self.p4.user, "-c", self.p4.client)
|
217
|
+
matching_changes = []
|
218
|
+
|
219
|
+
for cl in pending_changes:
|
220
|
+
cl_desc = cl.get('Description', b'').decode('utf-8', 'replace').strip()
|
221
|
+
|
222
|
+
# 패턴 매칭 로직
|
223
|
+
is_match = False
|
224
|
+
if exact_match:
|
225
|
+
# 정확한 일치
|
226
|
+
is_match = (cl_desc == description_pattern.strip())
|
227
|
+
else:
|
228
|
+
# 패턴이 포함되어 있는지 확인 (대소문자 구분 없음)
|
229
|
+
is_match = (description_pattern.lower().strip() in cl_desc.lower())
|
230
|
+
|
231
|
+
if is_match:
|
232
|
+
change_number = int(cl['change'])
|
233
|
+
change_info = self.get_change_list_by_number(change_number)
|
234
|
+
if change_info:
|
235
|
+
matching_changes.append(change_info)
|
236
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {change_number} 발견: '{cl_desc}'")
|
237
|
+
|
238
|
+
if matching_changes:
|
239
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 체인지 리스트 {len(matching_changes)}개 조회 완료.")
|
240
|
+
else:
|
241
|
+
logger.info(f"패턴 '{description_pattern}'에 매칭되는 Pending 체인지 리스트를 찾을 수 없습니다.")
|
242
|
+
|
243
|
+
return matching_changes
|
244
|
+
except P4Exception as e:
|
245
|
+
self._handle_p4_exception(e, f"설명 패턴으로 체인지 리스트 조회 ('{description_pattern}')")
|
246
|
+
return []
|
247
|
+
|
248
|
+
def check_files_checked_out(self, file_paths: list) -> dict:
|
249
|
+
"""파일들의 체크아웃 상태를 확인합니다.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
file_paths (list): 확인할 파일 경로 리스트
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
dict: 파일별 체크아웃 상태 정보
|
256
|
+
{
|
257
|
+
'file_path': {
|
258
|
+
'is_checked_out': bool,
|
259
|
+
'change_list': int or None,
|
260
|
+
'action': str or None,
|
261
|
+
'user': str or None,
|
262
|
+
'workspace': str or None
|
263
|
+
}
|
264
|
+
}
|
265
|
+
"""
|
266
|
+
if not self._is_connected():
|
267
|
+
return {}
|
268
|
+
if not file_paths:
|
269
|
+
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
270
|
+
return {}
|
271
|
+
|
272
|
+
logger.debug(f"파일 체크아웃 상태 확인 중 (파일 {len(file_paths)}개)")
|
273
|
+
|
274
|
+
result = {}
|
275
|
+
try:
|
276
|
+
# 각 파일의 상태 확인
|
277
|
+
for file_path in file_paths:
|
278
|
+
file_status = {
|
279
|
+
'is_checked_out': False,
|
280
|
+
'change_list': None,
|
281
|
+
'action': None,
|
282
|
+
'user': None,
|
283
|
+
'workspace': None
|
284
|
+
}
|
285
|
+
|
286
|
+
try:
|
287
|
+
# p4 opened 명령으로 파일이 열려있는지 확인
|
288
|
+
opened_files = self.p4.run_opened(file_path)
|
289
|
+
|
290
|
+
if opened_files:
|
291
|
+
# 파일이 체크아웃되어 있음
|
292
|
+
file_info = opened_files[0]
|
293
|
+
file_status['is_checked_out'] = True
|
294
|
+
file_status['change_list'] = int(file_info.get('change', 0))
|
295
|
+
file_status['action'] = file_info.get('action', '')
|
296
|
+
file_status['user'] = file_info.get('user', '')
|
297
|
+
file_status['workspace'] = file_info.get('client', '')
|
298
|
+
|
299
|
+
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
300
|
+
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
301
|
+
f"워크스페이스: {file_status['workspace']}")
|
302
|
+
else:
|
303
|
+
# 파일이 체크아웃되지 않음
|
304
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음")
|
305
|
+
|
306
|
+
except P4Exception as e:
|
307
|
+
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
308
|
+
if any("not opened" in err.lower() or "no such file" in err.lower()
|
309
|
+
for err in self.p4.errors):
|
310
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
311
|
+
else:
|
312
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인")
|
313
|
+
|
314
|
+
result[file_path] = file_status
|
315
|
+
|
316
|
+
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
317
|
+
logger.info(f"파일 체크아웃 상태 확인 완료: 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
318
|
+
|
319
|
+
return result
|
320
|
+
|
321
|
+
except P4Exception as e:
|
322
|
+
self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 ({file_paths})")
|
323
|
+
return {}
|
324
|
+
|
325
|
+
def is_file_checked_out(self, file_path: str) -> bool:
|
326
|
+
"""단일 파일의 체크아웃 상태를 간단히 확인합니다.
|
327
|
+
|
328
|
+
Args:
|
329
|
+
file_path (str): 확인할 파일 경로
|
330
|
+
|
331
|
+
Returns:
|
332
|
+
bool: 체크아웃되어 있으면 True, 아니면 False
|
333
|
+
"""
|
334
|
+
result = self.check_files_checked_out([file_path])
|
335
|
+
return result.get(file_path, {}).get('is_checked_out', False)
|
336
|
+
|
337
|
+
def is_file_in_pending_changelist(self, file_path: str, change_list_number: int) -> bool:
|
338
|
+
"""특정 파일이 지정된 pending 체인지 리스트에 있는지 확인합니다.
|
339
|
+
|
340
|
+
Args:
|
341
|
+
file_path (str): 확인할 파일 경로
|
342
|
+
change_list_number (int): 확인할 체인지 리스트 번호
|
343
|
+
|
344
|
+
Returns:
|
345
|
+
bool: 파일이 해당 체인지 리스트에 있으면 True, 아니면 False
|
346
|
+
"""
|
347
|
+
if not self._is_connected():
|
348
|
+
return False
|
349
|
+
|
350
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 있는지 확인 중...")
|
351
|
+
|
352
|
+
try:
|
353
|
+
# 해당 체인지 리스트의 파일들 가져오기
|
354
|
+
opened_files = self.p4.run_opened("-c", change_list_number)
|
355
|
+
|
356
|
+
# 파일 경로 정규화
|
357
|
+
normalized_file_path = os.path.normpath(file_path)
|
358
|
+
|
359
|
+
for file_info in opened_files:
|
360
|
+
client_file = file_info.get('clientFile', '')
|
361
|
+
normalized_client_file = os.path.normpath(client_file)
|
362
|
+
|
363
|
+
if normalized_client_file == normalized_file_path:
|
364
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에서 발견됨 "
|
365
|
+
f"(액션: {file_info.get('action', '')})")
|
366
|
+
return True
|
367
|
+
|
368
|
+
logger.debug(f"파일 '{file_path}'가 체인지 리스트 {change_list_number}에 없음")
|
369
|
+
return False
|
370
|
+
|
371
|
+
except P4Exception as e:
|
372
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체인지 리스트 {change_list_number} 포함 여부 확인")
|
373
|
+
return False
|
374
|
+
|
198
375
|
def edit_change_list(self, change_list_number: int, description: str = None, add_file_paths: list = None, remove_file_paths: list = None) -> dict:
|
199
376
|
"""체인지 리스트를 편집합니다.
|
200
377
|
|
@@ -644,3 +821,188 @@ class Perforce:
|
|
644
821
|
def __del__(self):
|
645
822
|
"""객체가 소멸될 때 자동으로 연결을 해제합니다."""
|
646
823
|
self.disconnect()
|
824
|
+
|
825
|
+
def check_files_checked_out_all_users(self, file_paths: list) -> dict:
|
826
|
+
"""파일들의 체크아웃 상태를 모든 사용자/워크스페이스에서 확인합니다.
|
827
|
+
|
828
|
+
Args:
|
829
|
+
file_paths (list): 확인할 파일 경로 리스트
|
830
|
+
|
831
|
+
Returns:
|
832
|
+
dict: 파일별 체크아웃 상태 정보
|
833
|
+
{
|
834
|
+
'file_path': {
|
835
|
+
'is_checked_out': bool,
|
836
|
+
'change_list': int or None,
|
837
|
+
'action': str or None,
|
838
|
+
'user': str or None,
|
839
|
+
'client': str or None
|
840
|
+
}
|
841
|
+
}
|
842
|
+
"""
|
843
|
+
if not self._is_connected():
|
844
|
+
return {}
|
845
|
+
if not file_paths:
|
846
|
+
logger.debug("체크아웃 상태 확인할 파일 목록이 비어있습니다.")
|
847
|
+
return {}
|
848
|
+
|
849
|
+
logger.debug(f"파일 체크아웃 상태 확인 중 - 모든 사용자 (파일 {len(file_paths)}개)")
|
850
|
+
|
851
|
+
result = {}
|
852
|
+
try:
|
853
|
+
# 각 파일의 상태 확인
|
854
|
+
for file_path in file_paths:
|
855
|
+
file_status = {
|
856
|
+
'is_checked_out': False,
|
857
|
+
'change_list': None,
|
858
|
+
'action': None,
|
859
|
+
'user': None,
|
860
|
+
'client': None
|
861
|
+
}
|
862
|
+
|
863
|
+
try:
|
864
|
+
# p4 opened -a 명령으로 모든 사용자의 파일 체크아웃 상태 확인
|
865
|
+
opened_files = self.p4.run_opened("-a", file_path)
|
866
|
+
|
867
|
+
if opened_files:
|
868
|
+
# 파일이 체크아웃되어 있음 (첫 번째 결과 사용)
|
869
|
+
file_info = opened_files[0]
|
870
|
+
file_status['is_checked_out'] = True
|
871
|
+
file_status['change_list'] = int(file_info.get('change', 0))
|
872
|
+
file_status['action'] = file_info.get('action', '')
|
873
|
+
file_status['user'] = file_info.get('user', '')
|
874
|
+
file_status['client'] = file_info.get('client', '')
|
875
|
+
|
876
|
+
logger.debug(f"파일 '{file_path}' 체크아웃됨: CL {file_status['change_list']}, "
|
877
|
+
f"액션: {file_status['action']}, 사용자: {file_status['user']}, "
|
878
|
+
f"클라이언트: {file_status['client']}")
|
879
|
+
else:
|
880
|
+
# 파일이 체크아웃되지 않음
|
881
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (모든 사용자)")
|
882
|
+
|
883
|
+
except P4Exception as e:
|
884
|
+
# 파일이 perforce에 없거나 접근할 수 없는 경우
|
885
|
+
if any("not opened" in err.lower() or "no such file" in err.lower()
|
886
|
+
for err in self.p4.errors):
|
887
|
+
logger.debug(f"파일 '{file_path}' 체크아웃되지 않음 (perforce에 없거나 접근 불가)")
|
888
|
+
else:
|
889
|
+
self._handle_p4_exception(e, f"파일 '{file_path}' 체크아웃 상태 확인 (모든 사용자)")
|
890
|
+
|
891
|
+
result[file_path] = file_status
|
892
|
+
|
893
|
+
checked_out_count = sum(1 for status in result.values() if status['is_checked_out'])
|
894
|
+
logger.info(f"파일 체크아웃 상태 확인 완료 (모든 사용자): 전체 {len(file_paths)}개 중 {checked_out_count}개 체크아웃됨")
|
895
|
+
|
896
|
+
return result
|
897
|
+
|
898
|
+
except P4Exception as e:
|
899
|
+
self._handle_p4_exception(e, f"파일들 체크아웃 상태 확인 - 모든 사용자 ({file_paths})")
|
900
|
+
return {}
|
901
|
+
|
902
|
+
def is_file_checked_out_by_others(self, file_path: str) -> bool:
|
903
|
+
"""단일 파일이 다른 사용자/워크스페이스에 의해 체크아웃되어 있는지 확인합니다.
|
904
|
+
|
905
|
+
Args:
|
906
|
+
file_path (str): 확인할 파일 경로
|
907
|
+
|
908
|
+
Returns:
|
909
|
+
bool: 다른 사용자에 의해 체크아웃되어 있으면 True, 아니면 False
|
910
|
+
"""
|
911
|
+
result = self.check_files_checked_out_all_users([file_path])
|
912
|
+
file_status = result.get(file_path, {})
|
913
|
+
|
914
|
+
if not file_status.get('is_checked_out', False):
|
915
|
+
return False
|
916
|
+
|
917
|
+
# 현재 사용자와 클라이언트가 아닌 경우 다른 사용자로 간주
|
918
|
+
current_user = self.p4.user
|
919
|
+
current_client = self.p4.client
|
920
|
+
|
921
|
+
file_user = file_status.get('user', '')
|
922
|
+
file_client = file_status.get('client', '')
|
923
|
+
|
924
|
+
return (file_user != current_user) or (file_client != current_client)
|
925
|
+
|
926
|
+
def get_file_checkout_info_all_users(self, file_path: str) -> dict:
|
927
|
+
"""단일 파일의 상세 체크아웃 정보를 모든 사용자에서 가져옵니다.
|
928
|
+
|
929
|
+
Args:
|
930
|
+
file_path (str): 확인할 파일 경로
|
931
|
+
|
932
|
+
Returns:
|
933
|
+
dict: 체크아웃 정보 또는 빈 딕셔너리
|
934
|
+
{
|
935
|
+
'is_checked_out': bool,
|
936
|
+
'change_list': int or None,
|
937
|
+
'action': str or None,
|
938
|
+
'user': str or None,
|
939
|
+
'client': str or None,
|
940
|
+
'is_checked_out_by_current_user': bool,
|
941
|
+
'is_checked_out_by_others': bool
|
942
|
+
}
|
943
|
+
"""
|
944
|
+
result = self.check_files_checked_out_all_users([file_path])
|
945
|
+
file_status = result.get(file_path, {})
|
946
|
+
|
947
|
+
if file_status.get('is_checked_out', False):
|
948
|
+
# 현재 사용자와 클라이언트인지 확인
|
949
|
+
current_user = self.p4.user
|
950
|
+
current_client = self.p4.client
|
951
|
+
|
952
|
+
file_user = file_status.get('user', '')
|
953
|
+
file_client = file_status.get('client', '')
|
954
|
+
|
955
|
+
is_current_user = (file_user == current_user) and (file_client == current_client)
|
956
|
+
|
957
|
+
file_status['is_checked_out_by_current_user'] = is_current_user
|
958
|
+
file_status['is_checked_out_by_others'] = not is_current_user
|
959
|
+
else:
|
960
|
+
file_status['is_checked_out_by_current_user'] = False
|
961
|
+
file_status['is_checked_out_by_others'] = False
|
962
|
+
|
963
|
+
return file_status
|
964
|
+
|
965
|
+
def get_files_checked_out_by_others(self, file_paths: list) -> list:
|
966
|
+
"""파일 목록에서 다른 사용자/워크스페이스에 의해 체크아웃된 파일들을 찾습니다.
|
967
|
+
|
968
|
+
Args:
|
969
|
+
file_paths (list): 확인할 파일 경로 리스트
|
970
|
+
|
971
|
+
Returns:
|
972
|
+
list: 다른 사용자에 의해 체크아웃된 파일 정보 리스트
|
973
|
+
[
|
974
|
+
{
|
975
|
+
'file_path': str,
|
976
|
+
'user': str,
|
977
|
+
'client': str,
|
978
|
+
'change_list': int,
|
979
|
+
'action': str
|
980
|
+
}
|
981
|
+
]
|
982
|
+
"""
|
983
|
+
if not file_paths:
|
984
|
+
return []
|
985
|
+
|
986
|
+
result = self.check_files_checked_out_all_users(file_paths)
|
987
|
+
files_by_others = []
|
988
|
+
|
989
|
+
current_user = self.p4.user
|
990
|
+
current_client = self.p4.client
|
991
|
+
|
992
|
+
for file_path, status in result.items():
|
993
|
+
if status.get('is_checked_out', False):
|
994
|
+
file_user = status.get('user', '')
|
995
|
+
file_client = status.get('client', '')
|
996
|
+
|
997
|
+
# 다른 사용자/클라이언트에 의해 체크아웃된 경우
|
998
|
+
if (file_user != current_user) or (file_client != current_client):
|
999
|
+
files_by_others.append({
|
1000
|
+
'file_path': file_path,
|
1001
|
+
'user': file_user,
|
1002
|
+
'client': file_client,
|
1003
|
+
'change_list': status.get('change_list'),
|
1004
|
+
'action': status.get('action', '')
|
1005
|
+
})
|
1006
|
+
|
1007
|
+
logger.info(f"다른 사용자에 의해 체크아웃된 파일: {len(files_by_others)}개")
|
1008
|
+
return files_by_others
|
@@ -1,9 +1,9 @@
|
|
1
|
-
pyjallib/__init__.py,sha256=
|
1
|
+
pyjallib/__init__.py,sha256=4EmbUX3I3DLJ3DpGz7kBAADCFskvffdUEPd3HIu7-9k,509
|
2
2
|
pyjallib/namePart.py,sha256=lKIiOVkWrtAW-D3nuv--vHmdAnlQeVPaXLYUDhcr8QU,24177
|
3
3
|
pyjallib/nameToPath.py,sha256=aBeezepLYdpv3VYxnQ2c4ZWzz2WjticXjkdbAIlVa1k,4676
|
4
|
-
pyjallib/naming.py,sha256=
|
5
|
-
pyjallib/namingConfig.py,sha256=
|
6
|
-
pyjallib/perforce.py,sha256=
|
4
|
+
pyjallib/naming.py,sha256=b2C-P9VWV4Q2StqkizEwABblYOC5g6sXHzN0KpOZ_Ys,37419
|
5
|
+
pyjallib/namingConfig.py,sha256=QGpK5mCnRiclKqNKz3GJ2PeJO8fbVitAEdqWwnwo8oA,34127
|
6
|
+
pyjallib/perforce.py,sha256=XcF-YG250bh3obOkajPzMNwaJcCmXKIHIlJIEEFw6JE,46459
|
7
7
|
pyjallib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
pyjallib/reloadModules.py,sha256=RAEG3IxzJ0TlsjvnZwJt56JOkc2j8voqAnRbfQuZ44g,1151
|
9
9
|
pyjallib/ConfigFiles/namingConfig.json,sha256=Ov4bbVJb6qodPaooU63e11YUMGXXPWFAA4AQq1sLBYU,1486
|
@@ -11,7 +11,7 @@ pyjallib/max/__init__.py,sha256=sLF07So7OcCccPOWixSjIkAhuye37-buK-1I5Hp8F4U,1537
|
|
11
11
|
pyjallib/max/align.py,sha256=HKjCViQCuicGmtvHB6xxVv4BEGEBGtV2gO3NvR_6R2A,5183
|
12
12
|
pyjallib/max/anim.py,sha256=QTpR8T047IMpV40wnMMNo080wY9rHMV9k7ISrh4P61I,26083
|
13
13
|
pyjallib/max/autoClavicle.py,sha256=Iga8bWUhRabfFePObdwGJSshp5Gww1Jv1Hwcul_y0V4,9559
|
14
|
-
pyjallib/max/bip.py,sha256=
|
14
|
+
pyjallib/max/bip.py,sha256=kEneUnYph_GQXY1oojmzpJOV9bAYNBKagE0Ht3EGZ7o,30199
|
15
15
|
pyjallib/max/bone.py,sha256=XVtX6e5UbMcGaOqz5UeoMEpQNMfbyQWDNM-UuS1CCUA,50019
|
16
16
|
pyjallib/max/boneChain.py,sha256=weuOmGk7Y7i-0QNCr7G2hRPOecb5xmErshqpmXtikI0,5660
|
17
17
|
pyjallib/max/constraint.py,sha256=93g-X0aZHtZMKXVKr8xMjIhbKou61yc2b3ubQKJquBs,40589
|
@@ -38,6 +38,6 @@ pyjallib/max/macro/jal_macro_helper.py,sha256=hd8e5x56aq7Qt0g-hP5bY0p-njVy8ja77_
|
|
38
38
|
pyjallib/max/macro/jal_macro_link.py,sha256=E8i3z2xsrQiGDEz4Qoxc75hkpalzS95mOMcIic0J-Fc,1193
|
39
39
|
pyjallib/max/macro/jal_macro_select.py,sha256=jeSFR_mqqudTTrZE1rU6qifJ4g441cYxXWcHPTWh1CU,2289
|
40
40
|
pyjallib/max/ui/Container.py,sha256=QSk3oCqhfiR4aglSSkherRGAyPFqMRUt83L-0ENBz-s,5571
|
41
|
-
pyjallib-0.1.
|
42
|
-
pyjallib-0.1.
|
43
|
-
pyjallib-0.1.
|
41
|
+
pyjallib-0.1.12.dist-info/METADATA,sha256=0jeCReJS2g2RlmWhefBDeSPwlIoFDturmBEqrw_s3Y8,870
|
42
|
+
pyjallib-0.1.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
43
|
+
pyjallib-0.1.12.dist-info/RECORD,,
|
File without changes
|