# -*- coding: utf-8 -*- """ 향상된 DXF 파일 처리 모듈 ezdxf 라이브러리를 사용하여 DXF 파일에서 도곽 정보, 텍스트 엔티티 및 모든 Block Reference/Attribute Reference를 추출 """ import os import json import logging from typing import Dict, List, Optional, Tuple, Any from dataclasses import dataclass, asdict, field try: import ezdxf from ezdxf.document import Drawing from ezdxf.entities import Insert, Attrib, AttDef, Text, MText from ezdxf.layouts import BlockLayout, Modelspace from ezdxf import bbox, disassemble EZDXF_AVAILABLE = True except ImportError: EZDXF_AVAILABLE = False logging.warning("ezdxf 라이브러리가 설치되지 않았습니다. DXF 기능이 비활성화됩니다.") from config import Config @dataclass class BoundingBox: """바운딩 박스 정보를 담는 데이터 클래스""" min_x: float min_y: float max_x: float max_y: float @property def width(self) -> float: return self.max_x - self.min_x @property def height(self) -> float: return self.max_y - self.min_y @property def center(self) -> Tuple[float, float]: return ((self.min_x + self.max_x) / 2, (self.min_y + self.max_y) / 2) def merge(self, other: 'BoundingBox') -> 'BoundingBox': """다른 바운딩 박스와 병합하여 가장 큰 외곽 박스 반환""" return BoundingBox( min_x=min(self.min_x, other.min_x), min_y=min(self.min_y, other.min_y), max_x=max(self.max_x, other.max_x), max_y=max(self.max_y, other.max_y) ) @dataclass class TextInfo: """텍스트 엔티티 정보를 담는 데이터 클래스""" entity_type: str # TEXT, MTEXT, ATTRIB text: str position: Tuple[float, float, float] height: float rotation: float layer: str bounding_box: Optional[BoundingBox] = None entity_handle: Optional[str] = None style: Optional[str] = None color: Optional[int] = None @dataclass class AttributeInfo: """속성 정보를 담는 데이터 클래스 - 모든 DXF 속성 포함""" tag: str text: str position: Tuple[float, float, float] # insert point (x, y, z) height: float width: float rotation: float layer: str bounding_box: Optional[BoundingBox] = None # 추가 DXF 속성들 prompt: Optional[str] = None style: Optional[str] = None invisible: bool = False const: bool = False verify: bool = False preset: bool = False align_point: Optional[Tuple[float, float, float]] = None halign: int = 0 valign: int = 0 text_generation_flag: int = 0 oblique_angle: float = 0.0 width_factor: float = 1.0 color: Optional[int] = None linetype: Optional[str] = None lineweight: Optional[int] = None # 좌표 정보 insert_x: float = 0.0 insert_y: float = 0.0 insert_z: float = 0.0 # 계산된 정보 estimated_width: float = 0.0 entity_handle: Optional[str] = None @dataclass class BlockInfo: """블록 정보를 담는 데이터 클래스""" name: str position: Tuple[float, float, float] scale: Tuple[float, float, float] rotation: float layer: str attributes: List[AttributeInfo] bounding_box: Optional[BoundingBox] = None @dataclass class TitleBlockInfo: """도곽 정보를 담는 데이터 클래스""" drawing_name: Optional[str] = None drawing_number: Optional[str] = None construction_field: Optional[str] = None construction_stage: Optional[str] = None scale: Optional[str] = None project_name: Optional[str] = None designer: Optional[str] = None date: Optional[str] = None revision: Optional[str] = None location: Optional[str] = None bounding_box: Optional[BoundingBox] = None block_name: Optional[str] = None # 모든 attributes 정보 저장 all_attributes: List[AttributeInfo] = field(default_factory=list) attributes_count: int = 0 # 추가 메타데이터 block_position: Optional[Tuple[float, float, float]] = None block_scale: Optional[Tuple[float, float, float]] = None block_rotation: float = 0.0 block_layer: Optional[str] = None def __post_init__(self): """초기화 후 처리""" self.attributes_count = len(self.all_attributes) @dataclass class ComprehensiveExtractionResult: """종합적인 추출 결과를 담는 데이터 클래스""" text_entities: List[TextInfo] = field(default_factory=list) all_block_references: List[BlockInfo] = field(default_factory=list) title_block: Optional[TitleBlockInfo] = None overall_bounding_box: Optional[BoundingBox] = None summary: Dict[str, Any] = field(default_factory=dict) class EnhancedDXFProcessor: """향상된 DXF 파일 처리 클래스""" # 도곽 식별을 위한 키워드 정의 TITLE_BLOCK_KEYWORDS = { '건설분야': ['construction_field', 'field', '분야', '공사', 'category'], '건설단계': ['construction_stage', 'stage', '단계', 'phase'], '도면명': ['drawing_name', 'title', '제목', 'name', '명'], '축척': ['scale', '축척', 'ratio', '비율'], '도면번호': ['drawing_number', 'number', '번호', 'no', 'dwg'], '설계자': ['designer', '설계', 'design', 'drawn'], '프로젝트': ['project', '사업', '공사명'], '날짜': ['date', '일자', '작성일'], '리비전': ['revision', 'rev', '개정'], '위치': ['location', '위치', '지역'] } def __init__(self): """DXF 처리기 초기화""" self.logger = logging.getLogger(__name__) if not EZDXF_AVAILABLE: raise ImportError("ezdxf 라이브러리가 필요합니다. 'pip install ezdxf'로 설치하세요.") def validate_dxf_file(self, file_path: str) -> bool: """DXF 파일 유효성 검사""" try: if not os.path.exists(file_path): self.logger.error(f"파일이 존재하지 않습니다: {file_path}") return False if not file_path.lower().endswith('.dxf'): self.logger.error(f"DXF 파일이 아닙니다: {file_path}") return False # ezdxf로 파일 읽기 시도 doc = ezdxf.readfile(file_path) if doc is None: return False self.logger.info(f"DXF 파일 유효성 검사 성공: {file_path}") return True except ezdxf.DXFStructureError as e: self.logger.error(f"DXF 구조 오류: {e}") return False except Exception as e: self.logger.error(f"DXF 파일 검증 중 오류: {e}") return False def load_dxf_document(self, file_path: str) -> Optional[Drawing]: """DXF 문서 로드""" try: doc = ezdxf.readfile(file_path) self.logger.info(f"DXF 문서 로드 성공: {file_path}") return doc except Exception as e: self.logger.error(f"DXF 문서 로드 실패: {e}") return None def _is_empty_text(self, text: str) -> bool: """텍스트가 비어있는지 확인 (공백 문자만 있거나 완전히 비어있는 경우)""" return not text or text.strip() == "" def calculate_comprehensive_bounding_box(self, doc: Drawing) -> Optional[BoundingBox]: """전체 문서의 종합적인 바운딩 박스 계산 (ezdxf.bbox 사용)""" try: msp = doc.modelspace() # ezdxf의 bbox 모듈을 사용하여 전체 바운딩 박스 계산 cache = bbox.Cache() overall_bbox = bbox.extents(msp, cache=cache) if overall_bbox: self.logger.info(f"전체 바운딩 박스: {overall_bbox}") return BoundingBox( min_x=overall_bbox.extmin.x, min_y=overall_bbox.extmin.y, max_x=overall_bbox.extmax.x, max_y=overall_bbox.extmax.y ) else: self.logger.warning("바운딩 박스 계산 실패") return None except Exception as e: self.logger.warning(f"바운딩 박스 계산 중 오류: {e}") return None def extract_all_text_entities(self, doc: Drawing) -> List[TextInfo]: """모든 텍스트 엔티티 추출 (TEXT, MTEXT, DBTEXT)""" text_entities = [] try: msp = doc.modelspace() # TEXT 엔티티 추출 for text_entity in msp.query('TEXT'): text_content = getattr(text_entity.dxf, 'text', '') if not self._is_empty_text(text_content): text_info = self._extract_text_info(text_entity, 'TEXT') if text_info: text_entities.append(text_info) # MTEXT 엔티티 추출 for mtext_entity in msp.query('MTEXT'): # MTEXT는 .text 속성 사용 text_content = getattr(mtext_entity, 'text', '') or getattr(mtext_entity.dxf, 'text', '') if not self._is_empty_text(text_content): text_info = self._extract_text_info(mtext_entity, 'MTEXT') if text_info: text_entities.append(text_info) # ATTRIB 엔티티 추출 (블록 외부의 독립적인 속성) for attrib_entity in msp.query('ATTRIB'): text_content = getattr(attrib_entity.dxf, 'text', '') if not self._is_empty_text(text_content): text_info = self._extract_text_info(attrib_entity, 'ATTRIB') if text_info: text_entities.append(text_info) # 페이퍼스페이스도 확인 for layout_name in doc.layout_names_in_taborder(): if layout_name.startswith('*'): # 모델스페이스 제외 continue try: layout = doc.paperspace(layout_name) # TEXT, MTEXT, ATTRIB 추출 for entity_type in ['TEXT', 'MTEXT', 'ATTRIB']: for entity in layout.query(entity_type): if entity_type == 'MTEXT': text_content = getattr(entity, 'text', '') or getattr(entity.dxf, 'text', '') else: text_content = getattr(entity.dxf, 'text', '') if not self._is_empty_text(text_content): text_info = self._extract_text_info(entity, entity_type) if text_info: text_entities.append(text_info) except Exception as e: self.logger.warning(f"레이아웃 {layout_name} 처리 중 오류: {e}") self.logger.info(f"총 {len(text_entities)}개의 텍스트 엔티티를 찾았습니다.") return text_entities except Exception as e: self.logger.error(f"텍스트 엔티티 추출 중 오류: {e}") return [] def _extract_text_info(self, entity, entity_type: str) -> Optional[TextInfo]: """텍스트 엔티티에서 정보 추출""" try: # 텍스트 내용 추출 if entity_type == 'MTEXT': text_content = getattr(entity, 'text', '') or getattr(entity.dxf, 'text', '') else: text_content = getattr(entity.dxf, 'text', '') # 위치 정보 insert_point = getattr(entity.dxf, 'insert', (0, 0, 0)) position = ( insert_point.x if hasattr(insert_point, 'x') else insert_point[0], insert_point.y if hasattr(insert_point, 'y') else insert_point[1], insert_point.z if hasattr(insert_point, 'z') else insert_point[2] ) # 기본 속성 height = getattr(entity.dxf, 'height', 1.0) rotation = getattr(entity.dxf, 'rotation', 0.0) layer = getattr(entity.dxf, 'layer', '0') entity_handle = getattr(entity.dxf, 'handle', None) style = getattr(entity.dxf, 'style', None) color = getattr(entity.dxf, 'color', None) # 바운딩 박스 계산 bounding_box = self._calculate_text_bounding_box(entity) return TextInfo( entity_type=entity_type, text=text_content, position=position, height=height, rotation=rotation, layer=layer, bounding_box=bounding_box, entity_handle=entity_handle, style=style, color=color ) except Exception as e: self.logger.warning(f"텍스트 정보 추출 중 오류: {e}") return None def _calculate_text_bounding_box(self, entity) -> Optional[BoundingBox]: """텍스트 엔티티의 바운딩 박스 계산""" try: # ezdxf bbox 모듈 사용 entity_bbox = bbox.extents([entity]) if entity_bbox: return BoundingBox( min_x=entity_bbox.extmin.x, min_y=entity_bbox.extmin.y, max_x=entity_bbox.extmax.x, max_y=entity_bbox.extmax.y ) except Exception as e: self.logger.debug(f"바운딩 박스 계산 실패, 추정값 사용: {e}") # 대안: 추정 계산 try: if hasattr(entity, 'dxf'): insert_point = getattr(entity.dxf, 'insert', (0, 0, 0)) height = getattr(entity.dxf, 'height', 1.0) # 텍스트 내용 길이 추정 if hasattr(entity, 'text'): text_content = entity.text elif hasattr(entity.dxf, 'text'): text_content = entity.dxf.text else: text_content = "" # 텍스트 너비 추정 (높이의 0.6배 * 글자 수) estimated_width = len(text_content) * height * 0.6 x, y = insert_point[0], insert_point[1] return BoundingBox( min_x=x, min_y=y, max_x=x + estimated_width, max_y=y + height ) except Exception as e: self.logger.warning(f"텍스트 바운딩 박스 계산 실패: {e}") return None def extract_all_block_references(self, doc: Drawing) -> List[BlockInfo]: """모든 Block Reference 추출 (재귀적으로 중첩된 블록도 포함)""" block_refs = [] try: # 모델스페이스에서 INSERT 엔티티 찾기 msp = doc.modelspace() for insert in msp.query('INSERT'): block_info = self._process_block_reference(doc, insert) if block_info: block_refs.append(block_info) # 페이퍼스페이스도 확인 for layout_name in doc.layout_names_in_taborder(): if layout_name.startswith('*'): # 모델스페이스 제외 continue try: layout = doc.paperspace(layout_name) for insert in layout.query('INSERT'): block_info = self._process_block_reference(doc, insert) if block_info: block_refs.append(block_info) except Exception as e: self.logger.warning(f"레이아웃 {layout_name} 처리 중 오류: {e}") # 블록 정의 내부도 재귀적으로 검사 for block_layout in doc.blocks: if not block_layout.name.startswith('*'): # 시스템 블록 제외 for insert in block_layout.query('INSERT'): block_info = self._process_block_reference(doc, insert) if block_info: block_refs.append(block_info) self.logger.info(f"총 {len(block_refs)}개의 블록 참조를 찾았습니다.") return block_refs except Exception as e: self.logger.error(f"블록 참조 추출 중 오류: {e}") return [] def _process_block_reference(self, doc: Drawing, insert: Insert) -> Optional[BlockInfo]: """개별 Block Reference 처리 - ATTDEF 정보도 함께 수집""" try: # 블록 정보 추출 block_name = insert.dxf.name position = (insert.dxf.insert.x, insert.dxf.insert.y, insert.dxf.insert.z) scale = ( getattr(insert.dxf, 'xscale', 1.0), getattr(insert.dxf, 'yscale', 1.0), getattr(insert.dxf, 'zscale', 1.0) ) rotation = getattr(insert.dxf, 'rotation', 0.0) layer = getattr(insert.dxf, 'layer', '0') # ATTDEF 정보 수집 (프롬프트 정보 포함) attdef_info = {} try: block_layout = doc.blocks.get(block_name) if block_layout: for attdef in block_layout.query('ATTDEF'): tag = getattr(attdef.dxf, 'tag', '') prompt = getattr(attdef.dxf, 'prompt', '') if tag: attdef_info[tag] = { 'prompt': prompt, 'default_text': getattr(attdef.dxf, 'text', ''), 'position': (attdef.dxf.insert.x, attdef.dxf.insert.y, attdef.dxf.insert.z), 'height': getattr(attdef.dxf, 'height', 1.0), 'style': getattr(attdef.dxf, 'style', 'Standard'), 'invisible': getattr(attdef.dxf, 'invisible', False), 'const': getattr(attdef.dxf, 'const', False), 'verify': getattr(attdef.dxf, 'verify', False), 'preset': getattr(attdef.dxf, 'preset', False) } except Exception as e: self.logger.debug(f"ATTDEF 정보 수집 실패: {e}") # ATTRIB 속성 추출 및 ATTDEF 정보와 결합 (빈 텍스트 제외) attributes = [] for attrib in insert.attribs: attr_info = self._extract_attribute_info(attrib) if attr_info and not self._is_empty_text(attr_info.text): # ATTDEF에서 프롬프트 정보 추가 if attr_info.tag in attdef_info: attr_info.prompt = attdef_info[attr_info.tag]['prompt'] attributes.append(attr_info) # 블록 바운딩 박스 계산 block_bbox = self._calculate_block_bounding_box(insert) return BlockInfo( name=block_name, position=position, scale=scale, rotation=rotation, layer=layer, attributes=attributes, bounding_box=block_bbox ) except Exception as e: self.logger.warning(f"블록 참조 처리 중 오류: {e}") return None def _calculate_block_bounding_box(self, insert: Insert) -> Optional[BoundingBox]: """블록의 바운딩 박스 계산""" try: # ezdxf bbox 모듈 사용 block_bbox = bbox.extents([insert]) if block_bbox: return BoundingBox( min_x=block_bbox.extmin.x, min_y=block_bbox.extmin.y, max_x=block_bbox.extmax.x, max_y=block_bbox.extmax.y ) except Exception as e: self.logger.debug(f"블록 바운딩 박스 계산 실패: {e}") return None def _extract_attribute_info(self, attrib: Attrib) -> Optional[AttributeInfo]: """Attribute Reference에서 모든 정보 추출 (빈 텍스트 포함)""" try: # 기본 속성 tag = getattr(attrib.dxf, 'tag', '') text = getattr(attrib.dxf, 'text', '') # 위치 정보 insert_point = getattr(attrib.dxf, 'insert', (0, 0, 0)) position = (insert_point.x if hasattr(insert_point, 'x') else insert_point[0], insert_point.y if hasattr(insert_point, 'y') else insert_point[1], insert_point.z if hasattr(insert_point, 'z') else insert_point[2]) # 텍스트 속성 height = getattr(attrib.dxf, 'height', 1.0) width = getattr(attrib.dxf, 'width', 1.0) rotation = getattr(attrib.dxf, 'rotation', 0.0) # 레이어 및 스타일 layer = getattr(attrib.dxf, 'layer', '0') style = getattr(attrib.dxf, 'style', 'Standard') # 속성 플래그 invisible = getattr(attrib.dxf, 'invisible', False) const = getattr(attrib.dxf, 'const', False) verify = getattr(attrib.dxf, 'verify', False) preset = getattr(attrib.dxf, 'preset', False) # 정렬 정보 align_point_data = getattr(attrib.dxf, 'align_point', None) align_point = None if align_point_data: align_point = (align_point_data.x if hasattr(align_point_data, 'x') else align_point_data[0], align_point_data.y if hasattr(align_point_data, 'y') else align_point_data[1], align_point_data.z if hasattr(align_point_data, 'z') else align_point_data[2]) halign = getattr(attrib.dxf, 'halign', 0) valign = getattr(attrib.dxf, 'valign', 0) # 텍스트 형식 text_generation_flag = getattr(attrib.dxf, 'text_generation_flag', 0) oblique_angle = getattr(attrib.dxf, 'oblique_angle', 0.0) width_factor = getattr(attrib.dxf, 'width_factor', 1.0) # 시각적 속성 color = getattr(attrib.dxf, 'color', None) linetype = getattr(attrib.dxf, 'linetype', None) lineweight = getattr(attrib.dxf, 'lineweight', None) # 엔티티 핸들 entity_handle = getattr(attrib.dxf, 'handle', None) # 텍스트 폭 추정 estimated_width = len(text) * height * 0.6 * width_factor # 바운딩 박스 계산 bounding_box = self._calculate_text_bounding_box(attrib) return AttributeInfo( tag=tag, text=text, position=position, height=height, width=width, rotation=rotation, layer=layer, bounding_box=bounding_box, prompt=None, # 나중에 ATTDEF에서 설정 style=style, invisible=invisible, const=const, verify=verify, preset=preset, align_point=align_point, halign=halign, valign=valign, text_generation_flag=text_generation_flag, oblique_angle=oblique_angle, width_factor=width_factor, color=color, linetype=linetype, lineweight=lineweight, insert_x=position[0], insert_y=position[1], insert_z=position[2], estimated_width=estimated_width, entity_handle=entity_handle ) except Exception as e: self.logger.warning(f"속성 정보 추출 중 오류: {e}") return None def identify_title_block(self, block_refs: List[BlockInfo]) -> Optional[TitleBlockInfo]: """블록 참조들 중에서 도곽을 식별하고 정보 추출""" title_block_candidates = [] for block_ref in block_refs: # 도곽 키워드를 포함한 속성이 있는지 확인 keyword_matches = 0 for attr in block_ref.attributes: for keyword_group in self.TITLE_BLOCK_KEYWORDS.keys(): if self._contains_keyword(attr.tag, keyword_group) or \ self._contains_keyword(attr.text, keyword_group): keyword_matches += 1 break # 충분한 키워드가 매칭되면 도곽 후보로 추가 if keyword_matches >= 2: # 최소 2개 이상의 키워드 매칭 title_block_candidates.append((block_ref, keyword_matches)) if not title_block_candidates: self.logger.warning("도곽 블록을 찾을 수 없습니다.") return None # 가장 많은 키워드를 포함한 블록을 도곽으로 선택 title_block_candidates.sort(key=lambda x: x[1], reverse=True) best_candidate = title_block_candidates[0][0] self.logger.info(f"도곽 블록 발견: {best_candidate.name} (키워드 매칭: {title_block_candidates[0][1]})") return self._extract_title_block_info(best_candidate) def _contains_keyword(self, text: str, keyword_group: str) -> bool: """텍스트에 특정 키워드 그룹의 단어가 포함되어 있는지 확인""" if not text: return False text_lower = text.lower() keywords = self.TITLE_BLOCK_KEYWORDS.get(keyword_group, []) return any(keyword.lower() in text_lower for keyword in keywords) def _extract_title_block_info(self, block_ref: BlockInfo) -> TitleBlockInfo: """도곽 블록에서 상세 정보 추출""" # TitleBlockInfo 객체 생성 title_block = TitleBlockInfo( block_name=block_ref.name, all_attributes=block_ref.attributes.copy(), block_position=block_ref.position, block_scale=block_ref.scale, block_rotation=block_ref.rotation, block_layer=block_ref.layer ) # 속성들을 분석하여 도곽 정보 매핑 for attr in block_ref.attributes: text_value = attr.text.strip() if not text_value: continue # 각 키워드 그룹별로 매칭 시도 if self._contains_keyword(attr.tag, '도면명') or self._contains_keyword(attr.text, '도면명'): title_block.drawing_name = text_value elif self._contains_keyword(attr.tag, '도면번호') or self._contains_keyword(attr.text, '도면번호'): title_block.drawing_number = text_value elif self._contains_keyword(attr.tag, '건설분야') or self._contains_keyword(attr.text, '건설분야'): title_block.construction_field = text_value elif self._contains_keyword(attr.tag, '건설단계') or self._contains_keyword(attr.text, '건설단계'): title_block.construction_stage = text_value elif self._contains_keyword(attr.tag, '축척') or self._contains_keyword(attr.text, '축척'): title_block.scale = text_value elif self._contains_keyword(attr.tag, '설계자') or self._contains_keyword(attr.text, '설계자'): title_block.designer = text_value elif self._contains_keyword(attr.tag, '프로젝트') or self._contains_keyword(attr.text, '프로젝트'): title_block.project_name = text_value elif self._contains_keyword(attr.tag, '날짜') or self._contains_keyword(attr.text, '날짜'): title_block.date = text_value elif self._contains_keyword(attr.tag, '리비전') or self._contains_keyword(attr.text, '리비전'): title_block.revision = text_value elif self._contains_keyword(attr.tag, '위치') or self._contains_keyword(attr.text, '위치'): title_block.location = text_value # 도곽 바운딩 박스는 블록의 바운딩 박스 사용 title_block.bounding_box = block_ref.bounding_box # 속성 개수 업데이트 title_block.attributes_count = len(title_block.all_attributes) self.logger.info(f"도곽 '{block_ref.name}'에서 {title_block.attributes_count}개의 속성 추출 완료") return title_block def process_dxf_file_comprehensive(self, file_path: str) -> Dict[str, Any]: """DXF 파일 종합적인 처리""" result = { 'success': False, 'error': None, 'file_path': file_path, 'comprehensive_result': None, 'summary': {} } try: # 파일 유효성 검사 if not self.validate_dxf_file(file_path): result['error'] = "유효하지 않은 DXF 파일입니다." return result # DXF 문서 로드 doc = self.load_dxf_document(file_path) if not doc: result['error'] = "DXF 문서를 로드할 수 없습니다." return result # 종합적인 추출 시작 comprehensive_result = ComprehensiveExtractionResult() # 1. 모든 텍스트 엔티티 추출 self.logger.info("텍스트 엔티티 추출 중...") comprehensive_result.text_entities = self.extract_all_text_entities(doc) # 2. 모든 블록 참조 추출 self.logger.info("블록 참조 추출 중...") comprehensive_result.all_block_references = self.extract_all_block_references(doc) # 3. 도곽 정보 추출 self.logger.info("도곽 정보 추출 중...") comprehensive_result.title_block = self.identify_title_block(comprehensive_result.all_block_references) # 4. 전체 바운딩 박스 계산 self.logger.info("전체 바운딩 박스 계산 중...") comprehensive_result.overall_bounding_box = self.calculate_comprehensive_bounding_box(doc) # 5. 요약 정보 생성 comprehensive_result.summary = { 'total_text_entities': len(comprehensive_result.text_entities), 'total_block_references': len(comprehensive_result.all_block_references), 'title_block_found': comprehensive_result.title_block is not None, 'title_block_name': comprehensive_result.title_block.block_name if comprehensive_result.title_block else None, 'total_attributes': sum(len(br.attributes) for br in comprehensive_result.all_block_references), 'non_empty_attributes': sum(len([attr for attr in br.attributes if not self._is_empty_text(attr.text)]) for br in comprehensive_result.all_block_references), 'overall_bounding_box': comprehensive_result.overall_bounding_box.__dict__ if comprehensive_result.overall_bounding_box else None } # 결과 저장 result['comprehensive_result'] = asdict(comprehensive_result) result['summary'] = comprehensive_result.summary result['success'] = True self.logger.info(f"DXF 파일 종합 처리 완료: {file_path}") self.logger.info(f"추출 요약: 텍스트 엔티티 {comprehensive_result.summary['total_text_entities']}개, " f"블록 참조 {comprehensive_result.summary['total_block_references']}개, " f"비어있지 않은 속성 {comprehensive_result.summary['non_empty_attributes']}개") except Exception as e: self.logger.error(f"DXF 파일 처리 중 오류: {e}") result['error'] = str(e) return result def save_analysis_result(self, result: Dict[str, Any], output_file: str) -> bool: """분석 결과를 JSON 파일로 저장""" try: os.makedirs(Config.RESULTS_FOLDER, exist_ok=True) output_path = os.path.join(Config.RESULTS_FOLDER, output_file) with open(output_path, 'w', encoding='utf-8') as f: json.dump(result, f, ensure_ascii=False, indent=2, default=str) self.logger.info(f"분석 결과 저장 완료: {output_path}") return True except Exception as e: self.logger.error(f"분석 결과 저장 실패: {e}") return False # 기존 클래스명과의 호환성을 위한 별칭 DXFProcessor = EnhancedDXFProcessor def main(): """테스트용 메인 함수""" logging.basicConfig(level=logging.INFO) if not EZDXF_AVAILABLE: print("ezdxf 라이브러리가 설치되지 않았습니다.") return processor = EnhancedDXFProcessor() # 테스트 파일 경로 (실제 파일 경로로 변경 필요) test_file = "test_drawing.dxf" if os.path.exists(test_file): result = processor.process_dxf_file_comprehensive(test_file) if result['success']: print("DXF 파일 종합 처리 성공!") summary = result['summary'] print(f"텍스트 엔티티: {summary['total_text_entities']}") print(f"블록 참조: {summary['total_block_references']}") print(f"도곽 발견: {summary['title_block_found']}") print(f"비어있지 않은 속성: {summary['non_empty_attributes']}") if summary['overall_bounding_box']: bbox_info = summary['overall_bounding_box'] print(f"전체 바운딩 박스: ({bbox_info['min_x']:.2f}, {bbox_info['min_y']:.2f}) ~ " f"({bbox_info['max_x']:.2f}, {bbox_info['max_y']:.2f})") else: print(f"처리 실패: {result['error']}") else: print(f"테스트 파일을 찾을 수 없습니다: {test_file}") def process_dxf_file(self, file_path: str) -> Dict[str, Any]: """ 기존 코드와의 호환성을 위한 메서드 process_dxf_file_comprehensive를 호출하고 기존 형식으로 변환 """ try: # 새로운 종합 처리 메서드 호출 comprehensive_result = self.process_dxf_file_comprehensive(file_path) if not comprehensive_result['success']: return comprehensive_result # 기존 형식으로 변환 comp_data = comprehensive_result['comprehensive_result'] # 기존 형식으로 데이터 재구성 result = { 'success': True, 'error': None, 'file_path': file_path, 'title_block': comp_data.get('title_block'), 'block_references': comp_data.get('all_block_references', []), 'summary': comp_data.get('summary', {}) } return result except Exception as e: self.logger.error(f"DXF 파일 처리 중 오류: {e}") return { 'success': False, 'error': str(e), 'file_path': file_path, 'title_block': None, 'block_references': [], 'summary': {} }