1# Copyright (c) 2019-2020 Manfred Moitzi
2# License: MIT License
3from typing import TYPE_CHECKING
4from ezdxf.lldxf import validator
5from ezdxf.lldxf.attributes import (
6    DXFAttr, DXFAttributes, DefSubclass, XType, RETURN_DEFAULT,
7    group_code_mapping,
8)
9from ezdxf.lldxf.const import (
10    SUBCLASS_MARKER, DXF12, MODEL_SPACE_R12, PAPER_SPACE_R12, MODEL_SPACE_R2000,
11    PAPER_SPACE_R2000,
12)
13from ezdxf.math import NULLVEC
14from .dxfentity import base_class, SubclassProcessor, DXFEntity
15from .factory import register_entity
16
17if TYPE_CHECKING:
18    from ezdxf.eztypes import TagWriter, DXFNamespace
19
20__all__ = ['Block', 'EndBlk']
21
22acdb_entity = DefSubclass('AcDbEntity', {
23    # No auto fix for invalid layer names!
24    'layer': DXFAttr(8, default='0', validator=validator.is_valid_layer_name),
25    'paperspace': DXFAttr(
26        67, default=0, optional=True,
27        validator=validator.is_integer_bool,
28        fixer=RETURN_DEFAULT,
29    ),
30
31})
32acdb_entity_group_codes = group_code_mapping(acdb_entity)
33
34acdb_block_begin = DefSubclass('AcDbBlockBegin', {
35    'name': DXFAttr(2, validator=validator.is_valid_block_name),
36    # The 2nd name with group code 3 is handles internally, and is not an
37    # explict DXF attribute.
38    'description': DXFAttr(4, default='', optional=True),
39
40    # Flags:
41    # 0 = Indicates none of the following flags apply
42    # 1 = This is an anonymous block generated by hatching, associative
43    #     dimensioning, other internal operations, or an application
44    # 2 = This block has non-constant attribute definitions (this bit is not set
45    #     if the block has any attribute definitions that are constant, or has
46    #     no attribute definitions at all)
47    # 4 = This block is an external reference (xref)
48    # 8 = This block is an xref overlay
49    # 16 = This block is externally dependent
50    # 32 = This is a resolved external reference, or dependent of an external
51    #      reference (ignored on input)
52    # 64 = This definition is a referenced external reference (ignored on input)
53    'flags': DXFAttr(70, default=0),
54    'base_point': DXFAttr(10, xtype=XType.any_point, default=NULLVEC),
55    'xref_path': DXFAttr(1, default=''),
56})
57acdb_block_begin_group_codes = group_code_mapping(acdb_block_begin)
58
59MODEL_SPACE_R2000_LOWER = MODEL_SPACE_R2000.lower()
60MODEL_SPACE_R12_LOWER = MODEL_SPACE_R12.lower()
61PAPER_SPACE_R2000_LOWER = PAPER_SPACE_R2000.lower()
62PAPER_SPACE_R12_LOWER = PAPER_SPACE_R12.lower()
63
64
65@register_entity
66class Block(DXFEntity):
67    """ DXF BLOCK entity """
68    DXFTYPE = 'BLOCK'
69    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_block_begin)
70
71    # Block entity flags:
72    # This is an anonymous block generated by hatching, associative
73    # dimensioning, other internal operations, or an application:
74    ANONYMOUS = 1
75
76    # This block has non-constant attribute definitions (this bit is not set
77    # if the block has any attribute definitions that are constant, or has no
78    # attribute definitions at all):
79    NON_CONSTANT_ATTRIBUTES = 2
80
81    # This block is an external reference:
82    XREF = 4
83
84    # This block is an xref overlay:
85    XREF_OVERLAY = 8
86
87    # This block is externally dependent:
88    EXTERNAL = 16
89
90    # This is a resolved external reference, or dependent of an external reference:
91    RESOLVED = 32
92
93    # This definition is a referenced external reference:
94    REFERENCED = 64
95
96    def load_dxf_attribs(
97            self, processor: SubclassProcessor = None) -> 'DXFNamespace':
98        dxf = super().load_dxf_attribs(processor)
99        if processor is None:
100            return dxf
101        processor.fast_load_dxfattribs(dxf, acdb_entity_group_codes, 1)
102        processor.fast_load_dxfattribs(dxf, acdb_block_begin_group_codes, 2)
103        if processor.r12:
104            name = dxf.name.lower()
105            if name == MODEL_SPACE_R12_LOWER:
106                dxf.name = MODEL_SPACE_R2000
107            elif name == PAPER_SPACE_R12_LOWER:
108                dxf.name = PAPER_SPACE_R2000
109        return dxf
110
111    def export_entity(self, tagwriter: 'TagWriter') -> None:
112        """ Export entity specific data as DXF tags. """
113        super().export_entity(tagwriter)
114
115        if tagwriter.dxfversion > DXF12:
116            tagwriter.write_tag2(SUBCLASS_MARKER, acdb_entity.name)
117        if self.dxf.hasattr('paperspace'):
118            tagwriter.write_tag2(67, 1)
119        self.dxf.export_dxf_attribs(tagwriter, 'layer')
120        if tagwriter.dxfversion > DXF12:
121            tagwriter.write_tag2(SUBCLASS_MARKER, acdb_block_begin.name)
122
123        name = self.dxf.name
124        if tagwriter.dxfversion == DXF12:
125            # export modelspace and paperspace with leading '$' instead of '*'
126            if name.lower() == MODEL_SPACE_R2000_LOWER:
127                name = MODEL_SPACE_R12
128            elif name.lower() == PAPER_SPACE_R2000_LOWER:
129                name = PAPER_SPACE_R12
130
131        tagwriter.write_tag2(2, name)
132        self.dxf.export_dxf_attribs(tagwriter, ['flags', 'base_point'])
133        tagwriter.write_tag2(3, name)
134        self.dxf.export_dxf_attribs(tagwriter, ['xref_path', 'description'])
135
136    @property
137    def is_layout_block(self) -> bool:
138        """ Returns ``True`` if this is a :class:`~ezdxf.layouts.Modelspace` or
139        :class:`~ezdxf.layouts.Paperspace` block definition.
140        """
141        name = self.dxf.name.lower()
142        return name.startswith('*model_space') or name.startswith(
143            '*paper_space')
144
145    @property
146    def is_anonymous(self) -> bool:
147        """ Returns ``True`` if this is an anonymous block generated by
148        hatching, associative dimensioning, other internal operations, or an
149        application.
150
151        """
152        return self.get_flag_state(Block.ANONYMOUS)
153
154    @property
155    def is_xref(self) -> bool:
156        """ Returns ``True`` if bock is an external referenced file."""
157        return self.get_flag_state(Block.XREF)
158
159    @property
160    def is_xref_overlay(self) -> bool:
161        """ Returns ``True`` if bock is an external referenced overlay file. """
162        return self.get_flag_state(Block.XREF_OVERLAY)
163
164
165acdb_block_end = DefSubclass('AcDbBlockEnd', {})
166
167
168@register_entity
169class EndBlk(DXFEntity):
170    """ DXF ENDBLK entity """
171    DXFTYPE = 'ENDBLK'
172    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_block_end)
173
174    def load_dxf_attribs(self,
175                         processor: SubclassProcessor = None) -> 'DXFNamespace':
176        dxf = super().load_dxf_attribs(processor)
177        if processor:
178            processor.fast_load_dxfattribs(dxf, acdb_entity_group_codes, 1)
179        return dxf
180
181    def export_entity(self, tagwriter: 'TagWriter') -> None:
182        """ Export entity specific data as DXF tags. """
183        super().export_entity(tagwriter)
184
185        if tagwriter.dxfversion > DXF12:
186            tagwriter.write_tag2(SUBCLASS_MARKER, acdb_entity.name)
187        if self.dxf.hasattr('paperspace'):
188            tagwriter.write_tag2(67, 1)
189        self.dxf.export_dxf_attribs(tagwriter, 'layer')
190        if tagwriter.dxfversion > DXF12:
191            tagwriter.write_tag2(SUBCLASS_MARKER, acdb_block_end.name)
192