Files
ExperionCrawler/test_dxf_extract.py

260 lines
8.1 KiB
Python

#!/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 <dxf_file_path> [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()