#!/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}")