#!/usr/bin/env python3 """ DXF 파일에서 TEXT, MTEXT, ATTRIB 엔티티를 추출하여 CSV 형식으로 변환하는 스크립트 의미 없는 텍스트는 필터링하고, 의미 있는 텍스트만 LLM에 전달 """ import re import sys from dataclasses import dataclass from typing import List, Tuple import csv import io @dataclass class TextEntity: """DXF 텍스트 엔티티""" entity_type: str # TEXT, MTEXT, ATTRIB text: str x: float y: float z: float layer: str height: float style: str def parse_dxf_text_entities(file_path: str) -> List[TextEntity]: """DXF 파일에서 TEXT, MTEXT, ATTRIB 엔티티를 파싱""" entities = [] with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() i = 0 while i < len(lines): line = lines[i].strip() # TEXT, MTEXT, ATTRIB 엔티티 찾기 if line in ('TEXT', 'MTEXT', 'ATTRIB'): entity_type = line entity = { 'entity_type': entity_type, 'text': '', 'x': 0.0, 'y': 0.0, 'z': 0.0, 'layer': '', 'height': 0.0, 'style': '' } # 엔티티 속성 파싱 (다음 0이 나올 때까지) i += 1 while i < len(lines): code = lines[i].strip() if code == '0': break if i + 1 < len(lines): value = lines[i + 1].strip() # 코드에 따라 값 파싱 if code == '1': # 텍스트 내용 (MTEXT의 경우 여러 줄 가능) if entity['text']: entity['text'] += ' ' + value else: entity['text'] = value elif code == '10': entity['x'] = float(value) elif code == '20': entity['y'] = float(value) elif code == '30': entity['z'] = float(value) elif code == '8': entity['layer'] = value elif code == '40': entity['height'] = float(value) elif code == '7': entity['style'] = value i += 1 i += 1 # 유효한 엔티티만 추가 if entity['text']: entities.append(TextEntity( entity_type=entity['entity_type'], text=entity['text'], x=entity['x'], y=entity['y'], z=entity['z'], layer=entity['layer'], height=entity['height'], style=entity['style'] )) else: i += 1 return entities def filter_meaningful_text(entities: List[TextEntity]) -> List[TextEntity]: """ 의미 있는 텍스트만 필터링 제거할 텍스트: - 너무 짧은 텍스트 (2자 미만) - 숫자만 있는 텍스트 - 특수문자만 있는 텍스트 - 반복되는 패턴 (예: "0", "1", "2" 등 단일 숫자) - DXF 내부 메타데이터 (예: "$ACADVER", "$LIMMAX" 등) """ meaningful = [] # 제거할 패턴들 remove_patterns = [ r'^\$[A-Z]+$', # DXV 시스템 변수 ($ACADVER, $LIMMAX 등) r'^[0-9]+$', # 숫자만 있는 텍스트 r'^[0-9.]+$', # 숫자와 점만 있는 텍스트 r'^[a-zA-Z0-9_]{1}$', # 1자 알파벳/숫자/언더스코어 r'^[ \t]+$', # 공백만 있는 텍스트 r'^[a-zA-Z0-9]{1,2}$', # 2자 이하의 알파벳/숫자 조합 ] # 허용할 패턴들 (의미 있는 텍스트) allow_patterns = [ r'[가-힣]', # 한글 포함 r'[A-Z]{2,}', # 2자 이상 대문자 (예: P-101, PIC-6211) r'[-_]{1}', # 하이픈/언더스코어 포함 (태그명 패턴) r'[0-9]{3,}', # 3자 이상 숫자 ] for entity in entities: text = entity.text.strip() # 빈 텍스트 제거 if not text: continue # 시스템 변수 제거 is_system_var = False for pattern in remove_patterns: if re.match(pattern, text): is_system_var = True break if is_system_var: continue # 의미 있는 텍스트인지 확인 is_meaningful = False for pattern in allow_patterns: if re.search(pattern, text): is_meaningful = True break # 한글이 포함되어 있거나, 태그명 패턴(P-101, PIC-6211 등)이면 허용 if not is_meaningful: # 태그명 패턴 확인 (예: P-101, PIC-6211, T-10101) if re.match(r'^[A-Z]+[-_][A-Z0-9]+$', text): is_meaningful = True # 3자 이상이고 알파벳/숫자/한글이 포함된 경우 elif len(text) >= 3 and (re.search(r'[A-Z]', text) or re.search(r'[0-9]', text)): is_meaningful = True if is_meaningful: meaningful.append(TextEntity( entity_type=entity.entity_type, text=text, x=entity.x, y=entity.y, z=entity.z, layer=entity.layer, height=entity.height, style=entity.style )) return meaningful def export_to_csv(entities: List[TextEntity], output_path: str): """CSV 형식으로 내보내기""" with open(output_path, 'w', encoding='utf-8', newline='') as f: writer = csv.writer(f) writer.writerow(['entity_type', 'text', 'x', 'y', 'z', 'layer', 'height', 'style']) for entity in entities: writer.writerow([ entity.entity_type, entity.text, entity.x, entity.y, entity.z, entity.layer, entity.height, entity.style ]) def export_to_llm_format(entities: List[TextEntity]) -> str: """LLM에 전달할 형식으로 변환 (CSV 문자열)""" output = io.StringIO() writer = csv.writer(output) writer.writerow(['entity_type', 'text', 'x', 'y', 'z', 'layer', 'height', 'style']) for entity in entities: writer.writerow([ entity.entity_type, entity.text, entity.x, entity.y, entity.z, entity.layer, entity.height, entity.style ]) return output.getvalue() def main(): if len(sys.argv) < 2: print("사용법: python dxf_extractor.py [output_csv_path]") sys.exit(1) dxf_path = sys.argv[1] output_csv = sys.argv[2] if len(sys.argv) > 2 else None print(f"DXF 파일 파싱 중: {dxf_path}") entities = parse_dxf_text_entities(dxf_path) print(f"총 {len(entities)}개 텍스트 엔티티 found") print("의미 있는 텍스트 필터링 중...") meaningful = filter_meaningful_text(entities) print(f"의미 있는 텍스트: {len(meaningful)}개") if output_csv: export_to_csv(meaningful, output_csv) print(f"CSV로 저장 완료: {output_csv}") # LLM 포맷으로 출력 print("\n" + "="*80) print("LLM에 전달할 CSV 형식:") print("="*80) print(export_to_llm_format(meaningful)) # 샘플 출력 print("\n" + "="*80) print("샘플 텍스트 (상위 10개):") print("="*80) for i, entity in enumerate(meaningful[:10]): print(f"{i+1}. [{entity.entity_type}] {entity.text} (layer: {entity.layer}, x:{entity.x}, y:{entity.y})") if __name__ == '__main__': main()