# Copyright (c) 2019-2024 Manfred Moitzi # License: MIT License from __future__ import annotations from typing import TYPE_CHECKING, Optional from typing_extensions import Self from ezdxf.lldxf import validator from ezdxf.lldxf.attributes import ( DXFAttr, DXFAttributes, DefSubclass, XType, RETURN_DEFAULT, group_code_mapping, merge_group_code_mappings, ) from ezdxf.lldxf.const import ( SUBCLASS_MARKER, DXF12, DXF2000, MODEL_SPACE_R12, PAPER_SPACE_R12, MODEL_SPACE_R2000, PAPER_SPACE_R2000, ) from ezdxf.math import NULLVEC from .dxfentity import base_class, SubclassProcessor, DXFEntity from .factory import register_entity from ezdxf.audit import Auditor, AuditError if TYPE_CHECKING: from ezdxf.entities import DXFNamespace from ezdxf.lldxf.tagwriter import AbstractTagWriter from ezdxf import xref __all__ = ["Block", "EndBlk"] acdb_entity = DefSubclass( "AcDbEntity", { # No auto fix for invalid layer names! "layer": DXFAttr(8, default="0", validator=validator.is_valid_layer_name), "paperspace": DXFAttr( 67, default=0, optional=True, validator=validator.is_integer_bool, fixer=RETURN_DEFAULT, ), }, ) acdb_entity_group_codes = group_code_mapping(acdb_entity) acdb_block_begin = DefSubclass( "AcDbBlockBegin", { "name": DXFAttr(2, validator=validator.is_valid_block_name), # The 2nd name with group code 3 is handled internally, and is not an # explicit DXF attribute. "description": DXFAttr(4, default="", dxfversion=DXF2000, optional=True), # Flags: # 0 = Indicates none of the following flags apply # 1 = This is an anonymous block generated by hatching, associative # dimensioning, other internal operations, or an application # 2 = This block has non-constant attribute definitions (this bit is not set # if the block has any attribute definitions that are constant, or has # no attribute definitions at all) # 4 = This block is an external reference (xref) # 8 = This block is an xref overlay # 16 = This block is externally dependent # 32 = This is a resolved external reference, or dependent of an external # reference (ignored on input) # 64 = This definition is a referenced external reference (ignored on input) "flags": DXFAttr(70, default=0), "base_point": DXFAttr(10, xtype=XType.any_point, default=NULLVEC), "xref_path": DXFAttr(1, default=""), }, ) acdb_block_begin_group_codes = group_code_mapping(acdb_block_begin) merged_block_begin_group_codes = merge_group_code_mappings( acdb_entity_group_codes, acdb_block_begin_group_codes ) MODEL_SPACE_R2000_LOWER = MODEL_SPACE_R2000.lower() MODEL_SPACE_R12_LOWER = MODEL_SPACE_R12.lower() PAPER_SPACE_R2000_LOWER = PAPER_SPACE_R2000.lower() PAPER_SPACE_R12_LOWER = PAPER_SPACE_R12.lower() @register_entity class Block(DXFEntity): """DXF BLOCK entity""" DXFTYPE = "BLOCK" DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_block_begin) # Block entity flags: # This is an anonymous block generated by hatching, associative # dimensioning, other internal operations, or an application: ANONYMOUS = 1 # This block has non-constant attribute definitions (this bit is not set # if the block has any attribute definitions that are constant, or has no # attribute definitions at all): NON_CONSTANT_ATTRIBUTES = 2 # This block is an external reference: XREF = 4 # This block is an xref overlay: XREF_OVERLAY = 8 # This block is externally dependent: EXTERNAL = 16 # This is a resolved external reference, or dependent of an external reference: RESOLVED = 32 # This definition is a referenced external reference: REFERENCED = 64 def load_dxf_attribs( self, processor: Optional[SubclassProcessor] = None ) -> DXFNamespace: """Loading interface. (internal API)""" dxf = super().load_dxf_attribs(processor) if processor is None: return dxf processor.simple_dxfattribs_loader(dxf, merged_block_begin_group_codes) if processor.r12: if dxf.name is None: dxf.name = "" name = dxf.name.lower() if name == MODEL_SPACE_R12_LOWER: dxf.name = MODEL_SPACE_R2000 elif name == PAPER_SPACE_R12_LOWER: dxf.name = PAPER_SPACE_R2000 return dxf def export_entity(self, tagwriter: AbstractTagWriter) -> None: """Export entity specific data as DXF tags.""" super().export_entity(tagwriter) if tagwriter.dxfversion > DXF12: tagwriter.write_tag2(SUBCLASS_MARKER, acdb_entity.name) if self.dxf.hasattr("paperspace"): tagwriter.write_tag2(67, 1) self.dxf.export_dxf_attribs(tagwriter, "layer") if tagwriter.dxfversion > DXF12: tagwriter.write_tag2(SUBCLASS_MARKER, acdb_block_begin.name) name = self.dxf.name if tagwriter.dxfversion == DXF12: # export modelspace and paperspace with leading '$' instead of '*' if name.lower() == MODEL_SPACE_R2000_LOWER: name = MODEL_SPACE_R12 elif name.lower() == PAPER_SPACE_R2000_LOWER: name = PAPER_SPACE_R12 tagwriter.write_tag2(2, name) self.dxf.export_dxf_attribs(tagwriter, ["flags", "base_point"]) tagwriter.write_tag2(3, name) self.dxf.export_dxf_attribs(tagwriter, ["xref_path", "description"]) @property def is_layout_block(self) -> bool: """Returns ``True`` if this is a :class:`~ezdxf.layouts.Modelspace` or :class:`~ezdxf.layouts.Paperspace` block definition. """ name = self.dxf.name.lower() return name.startswith("*model_space") or name.startswith("*paper_space") @property def is_anonymous(self) -> bool: """Returns ``True`` if this is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application. """ return self.get_flag_state(Block.ANONYMOUS) @property def is_xref(self) -> bool: """Returns ``True`` if bock is an external referenced file.""" return self.get_flag_state(Block.XREF) @property def is_xref_overlay(self) -> bool: """Returns ``True`` if bock is an external referenced overlay file.""" return self.get_flag_state(Block.XREF_OVERLAY) def audit(self, auditor: Auditor): owner_handle = self.dxf.get("owner") if owner_handle is None: # invalid owner handle - IGNORE return owner = auditor.entitydb.get(owner_handle) if owner is None: # invalid owner entity - IGNORE return owner_name = owner.dxf.get("name", "").upper() block_name = self.dxf.get("name", "").upper() if owner_name != block_name: auditor.add_error( AuditError.BLOCK_NAME_MISMATCH, f"{str(self)} name '{block_name}' and {str(owner)} name '{owner_name}' mismatch", ) def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None: """Translate resources from self to the copied entity.""" assert isinstance(clone, Block) super().map_resources(clone, mapping) clone.dxf.name = mapping.get_block_name(self.dxf.name) acdb_block_end = DefSubclass("AcDbBlockEnd", {}) @register_entity class EndBlk(DXFEntity): """DXF ENDBLK entity""" DXFTYPE = "ENDBLK" DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_block_end) def load_dxf_attribs( self, processor: Optional[SubclassProcessor] = None ) -> DXFNamespace: """Loading interface. (internal API)""" dxf = super().load_dxf_attribs(processor) if processor: processor.simple_dxfattribs_loader(dxf, acdb_entity_group_codes) # type: ignore return dxf def export_entity(self, tagwriter: AbstractTagWriter) -> None: """Export entity specific data as DXF tags.""" super().export_entity(tagwriter) if tagwriter.dxfversion > DXF12: tagwriter.write_tag2(SUBCLASS_MARKER, acdb_entity.name) if self.dxf.hasattr("paperspace"): tagwriter.write_tag2(67, 1) self.dxf.export_dxf_attribs(tagwriter, "layer") if tagwriter.dxfversion > DXF12: tagwriter.write_tag2(SUBCLASS_MARKER, acdb_block_end.name)