first commit
This commit is contained in:
306
csv_exporter.py
Normal file
306
csv_exporter.py
Normal file
@@ -0,0 +1,306 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
CSV 저장 유틸리티 모듈
|
||||
DXF 타이틀블럭 Attribute 정보를 CSV 형식으로 저장
|
||||
"""
|
||||
|
||||
import csv
|
||||
import os
|
||||
import logging
|
||||
from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TitleBlockCSVExporter:
|
||||
"""타이틀블럭 속성 정보 CSV 저장 클래스"""
|
||||
|
||||
def __init__(self, output_dir: str = None):
|
||||
"""CSV 저장기 초기화"""
|
||||
self.output_dir = output_dir or Config.RESULTS_FOLDER
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
|
||||
def export_title_block_attributes(
|
||||
self,
|
||||
title_block_info: Dict[str, Any],
|
||||
filename: str = None
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
타이틀블럭 속성 정보를 CSV 파일로 저장
|
||||
|
||||
Args:
|
||||
title_block_info: 타이틀블럭 정보 딕셔너리
|
||||
filename: 저장할 파일명 (없으면 자동 생성)
|
||||
|
||||
Returns:
|
||||
저장된 파일 경로 또는 None (실패시)
|
||||
"""
|
||||
try:
|
||||
if not title_block_info or not title_block_info.get('all_attributes'):
|
||||
logger.warning("타이틀블럭 속성 정보가 없습니다.")
|
||||
return None
|
||||
|
||||
# 파일명 생성
|
||||
if not filename:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
block_name = title_block_info.get('block_name', 'Unknown_Block')
|
||||
filename = f"title_block_attributes_{block_name}_{timestamp}.csv"
|
||||
|
||||
# 확장자 확인
|
||||
if not filename.endswith('.csv'):
|
||||
filename += '.csv'
|
||||
|
||||
filepath = os.path.join(self.output_dir, filename)
|
||||
|
||||
# CSV 헤더 정의
|
||||
headers = [
|
||||
'block_name', # block_ref.name
|
||||
'attr_prompt', # attr.prompt
|
||||
'attr_text', # attr.text
|
||||
'attr_tag', # attr.tag
|
||||
'attr_insert_x', # attr.insert_x
|
||||
'attr_insert_y', # attr.insert_y
|
||||
'bounding_box_min_x', # attr.bounding_box.min_x
|
||||
'bounding_box_min_y', # attr.bounding_box.min_y
|
||||
'bounding_box_max_x', # attr.bounding_box.max_x
|
||||
'bounding_box_max_y', # attr.bounding_box.max_y
|
||||
'bounding_box_width', # attr.bounding_box.width
|
||||
'bounding_box_height', # attr.bounding_box.height
|
||||
'attr_height', # 추가: 텍스트 높이
|
||||
'attr_rotation', # 추가: 회전각
|
||||
'attr_layer', # 추가: 레이어
|
||||
'attr_style', # 추가: 스타일
|
||||
'entity_handle' # 추가: 엔티티 핸들
|
||||
]
|
||||
|
||||
# CSV 데이터 준비
|
||||
csv_rows = []
|
||||
block_name = title_block_info.get('block_name', '')
|
||||
|
||||
for attr in title_block_info.get('all_attributes', []):
|
||||
row = {
|
||||
'block_name': block_name,
|
||||
'attr_prompt': attr.get('prompt', '') or '',
|
||||
'attr_text': attr.get('text', '') or '',
|
||||
'attr_tag': attr.get('tag', '') or '',
|
||||
'attr_insert_x': attr.get('insert_x', '') or '',
|
||||
'attr_insert_y': attr.get('insert_y', '') or '',
|
||||
'attr_height': attr.get('height', '') or '',
|
||||
'attr_rotation': attr.get('rotation', '') or '',
|
||||
'attr_layer': attr.get('layer', '') or '',
|
||||
'attr_style': attr.get('style', '') or '',
|
||||
'entity_handle': attr.get('entity_handle', '') or '',
|
||||
}
|
||||
|
||||
# 바운딩 박스 정보 추가
|
||||
bbox = attr.get('bounding_box')
|
||||
if bbox:
|
||||
row.update({
|
||||
'bounding_box_min_x': bbox.get('min_x', ''),
|
||||
'bounding_box_min_y': bbox.get('min_y', ''),
|
||||
'bounding_box_max_x': bbox.get('max_x', ''),
|
||||
'bounding_box_max_y': bbox.get('max_y', ''),
|
||||
'bounding_box_width': bbox.get('max_x', 0) - bbox.get('min_x', 0) if bbox.get('max_x') and bbox.get('min_x') else '',
|
||||
'bounding_box_height': bbox.get('max_y', 0) - bbox.get('min_y', 0) if bbox.get('max_y') and bbox.get('min_y') else '',
|
||||
})
|
||||
else:
|
||||
row.update({
|
||||
'bounding_box_min_x': '',
|
||||
'bounding_box_min_y': '',
|
||||
'bounding_box_max_x': '',
|
||||
'bounding_box_max_y': '',
|
||||
'bounding_box_width': '',
|
||||
'bounding_box_height': '',
|
||||
})
|
||||
|
||||
csv_rows.append(row)
|
||||
|
||||
# CSV 파일 저장
|
||||
with open(filepath, 'w', newline='', encoding='utf-8-sig') as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames=headers)
|
||||
|
||||
# 헤더 작성
|
||||
writer.writeheader()
|
||||
|
||||
# 데이터 작성
|
||||
writer.writerows(csv_rows)
|
||||
|
||||
logger.info(f"타이틀블럭 속성 CSV 저장 완료: {filepath}")
|
||||
return filepath
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"CSV 저장 중 오류: {e}")
|
||||
return None
|
||||
|
||||
def create_attribute_table_data(
|
||||
self,
|
||||
title_block_info: Dict[str, Any]
|
||||
) -> List[Dict[str, str]]:
|
||||
"""
|
||||
UI 테이블 표시용 데이터 생성
|
||||
|
||||
Args:
|
||||
title_block_info: 타이틀블럭 정보 딕셔너리
|
||||
|
||||
Returns:
|
||||
테이블 표시용 데이터 리스트
|
||||
"""
|
||||
try:
|
||||
if not title_block_info or not title_block_info.get('all_attributes'):
|
||||
return []
|
||||
|
||||
table_data = []
|
||||
block_name = title_block_info.get('block_name', '')
|
||||
|
||||
for i, attr in enumerate(title_block_info.get('all_attributes', [])):
|
||||
# 바운딩 박스 정보 포맷팅
|
||||
bbox_str = ""
|
||||
bbox = attr.get('bounding_box')
|
||||
if bbox:
|
||||
bbox_str = f"({bbox.get('min_x', 0):.1f}, {bbox.get('min_y', 0):.1f}) - ({bbox.get('max_x', 0):.1f}, {bbox.get('max_y', 0):.1f})"
|
||||
|
||||
row = {
|
||||
'No.': str(i + 1),
|
||||
'Block Name': block_name,
|
||||
'Tag': attr.get('tag', ''),
|
||||
'Text': attr.get('text', '')[:30] + ('...' if len(attr.get('text', '')) > 30 else ''), # 텍스트 길이 제한
|
||||
'Prompt': attr.get('prompt', '') or 'N/A',
|
||||
'X': f"{attr.get('insert_x', 0):.1f}",
|
||||
'Y': f"{attr.get('insert_y', 0):.1f}",
|
||||
'Bounding Box': bbox_str or 'N/A',
|
||||
'Height': f"{attr.get('height', 0):.1f}",
|
||||
'Layer': attr.get('layer', ''),
|
||||
}
|
||||
|
||||
table_data.append(row)
|
||||
|
||||
return table_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"테이블 데이터 생성 중 오류: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def main():
|
||||
"""테스트용 메인 함수"""
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# 테스트 데이터
|
||||
test_title_block = {
|
||||
'block_name': 'TEST_TITLE_BLOCK',
|
||||
'all_attributes': [
|
||||
{
|
||||
'tag': 'DRAWING_NAME',
|
||||
'text': '테스트 도면',
|
||||
'prompt': '도면명을 입력하세요',
|
||||
'insert_x': 100.0,
|
||||
'insert_y': 200.0,
|
||||
'height': 5.0,
|
||||
'rotation': 0.0,
|
||||
'layer': '0',
|
||||
'style': 'Standard',
|
||||
'entity_handle': 'ABC123',
|
||||
'bounding_box': {
|
||||
'min_x': 100.0,
|
||||
'min_y': 200.0,
|
||||
'max_x': 180.0,
|
||||
'max_y': 210.0
|
||||
}
|
||||
},
|
||||
{
|
||||
'tag': 'DRAWING_NUMBER',
|
||||
'text': 'TEST-001',
|
||||
'prompt': '도면번호를 입력하세요',
|
||||
'insert_x': 100.0,
|
||||
'insert_y': 190.0,
|
||||
'height': 4.0,
|
||||
'rotation': 0.0,
|
||||
'layer': '0',
|
||||
'style': 'Standard',
|
||||
'entity_handle': 'DEF456',
|
||||
'bounding_box': {
|
||||
'min_x': 100.0,
|
||||
'min_y': 190.0,
|
||||
'max_x': 150.0,
|
||||
'max_y': 198.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# CSV 저장 테스트
|
||||
exporter = TitleBlockCSVExporter()
|
||||
|
||||
# 테이블 데이터 생성 테스트
|
||||
table_data = exporter.create_attribute_table_data(test_title_block)
|
||||
print("테이블 데이터:")
|
||||
for row in table_data:
|
||||
print(row)
|
||||
|
||||
# CSV 저장 테스트
|
||||
saved_path = exporter.export_title_block_attributes(test_title_block, "test_export.csv")
|
||||
if saved_path:
|
||||
print(f"\nCSV 저장 성공: {saved_path}")
|
||||
else:
|
||||
print("\nCSV 저장 실패")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
import json
|
||||
|
||||
def export_analysis_results_to_csv(data: List[Dict[str, Any]], file_path: str):
|
||||
"""
|
||||
분석 결과를 CSV 파일로 저장합니다. pdf_analysis_result 컬럼의 JSON 데이터를 평탄화합니다.
|
||||
Args:
|
||||
data: 분석 결과 딕셔너리 리스트
|
||||
file_path: 저장할 CSV 파일 경로
|
||||
"""
|
||||
if not data:
|
||||
logger.warning("내보낼 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
all_keys = set()
|
||||
processed_data = []
|
||||
|
||||
for row in data:
|
||||
new_row = row.copy()
|
||||
if 'pdf_analysis_result' in new_row and new_row['pdf_analysis_result']:
|
||||
try:
|
||||
json_data = new_row['pdf_analysis_result']
|
||||
if isinstance(json_data, str):
|
||||
json_data = json.loads(json_data)
|
||||
|
||||
if isinstance(json_data, dict):
|
||||
for k, v in json_data.items():
|
||||
new_row[f"pdf_analysis_result_{k}"] = v
|
||||
del new_row['pdf_analysis_result']
|
||||
else:
|
||||
new_row['pdf_analysis_result'] = str(json_data)
|
||||
except (json.JSONDecodeError, TypeError) as e:
|
||||
logger.warning(f"pdf_analysis_result 파싱 오류: {e}, 원본 데이터 유지: {new_row['pdf_analysis_result']}")
|
||||
new_row['pdf_analysis_result'] = str(new_row['pdf_analysis_result'])
|
||||
|
||||
processed_data.append(new_row)
|
||||
all_keys.update(new_row.keys())
|
||||
|
||||
# 'pdf_analysis_result'가 평탄화된 경우 최종 키에서 제거
|
||||
if 'pdf_analysis_result' in all_keys:
|
||||
all_keys.remove('pdf_analysis_result')
|
||||
|
||||
sorted_keys = sorted(list(all_keys))
|
||||
|
||||
try:
|
||||
with open(file_path, 'w', newline='', encoding='utf-8-sig') as output_file:
|
||||
dict_writer = csv.DictWriter(output_file, sorted_keys)
|
||||
dict_writer.writeheader()
|
||||
dict_writer.writerows(processed_data)
|
||||
logger.info(f"분석 결과 CSV 저장 완료: {file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"분석 결과 CSV 저장 중 오류: {e}")
|
||||
|
||||
Reference in New Issue
Block a user