1# Copyright (c) 2019-2020 Manfred Moitzi 2# License: MIT License 3from typing import TYPE_CHECKING, Iterable, Dict, Tuple 4import logging 5import array 6from ezdxf.lldxf import validator 7from ezdxf.lldxf.const import DXF2000, DXFStructureError, SUBCLASS_MARKER 8from ezdxf.lldxf.tags import Tags 9from ezdxf.lldxf.types import dxftag, DXFTag, DXFBinaryTag 10from ezdxf.lldxf.attributes import ( 11 DXFAttr, DXFAttributes, DefSubclass, RETURN_DEFAULT, group_code_mapping 12) 13from ezdxf.tools import take2 14from .dxfentity import DXFEntity, base_class, SubclassProcessor 15from .factory import register_entity 16 17logger = logging.getLogger('ezdxf') 18if TYPE_CHECKING: 19 from ezdxf.eztypes import Auditor, DXFNamespace, TagWriter 20 21__all__ = [ 22 'DXFObject', 'Placeholder', 'XRecord', 'VBAProject', 'SortEntsTable', 23 'Field' 24] 25 26 27class DXFObject(DXFEntity): 28 """ Non graphical entities stored in the OBJECTS section. """ 29 MIN_DXF_VERSION_FOR_EXPORT = DXF2000 30 31 def audit(self, auditor: 'Auditor') -> None: 32 """ Validity check. (internal API) """ 33 super().audit(auditor) 34 auditor.check_owner_exist(self) 35 36 37@register_entity 38class Placeholder(DXFObject): 39 DXFTYPE = 'ACDBPLACEHOLDER' 40 41 42acdb_xrecord = DefSubclass('AcDbXrecord', { 43 # 0 = not applicable 44 # 1 = keep existing 45 # 2 = use clone 46 # 3 = <xref>$0$<name> 47 # 4 = $0$<name> 48 # 5 = Unmangle name 49 'cloning': DXFAttr( 50 280, default=1, 51 validator=validator.is_in_integer_range(0, 6), 52 fixer=RETURN_DEFAULT, 53 ), 54}) 55 56 57def totags(tags: Iterable) -> Iterable[DXFTag]: 58 for tag in tags: 59 if isinstance(tag, DXFTag): 60 yield tag 61 else: 62 yield dxftag(tag[0], tag[1]) 63 64 65@register_entity 66class XRecord(DXFObject): 67 """ DXF XRECORD entity """ 68 DXFTYPE = 'XRECORD' 69 DXFATTRIBS = DXFAttributes(base_class, acdb_xrecord) 70 71 def __init__(self): 72 super().__init__() 73 self.tags = Tags() 74 75 def _copy_data(self, entity: 'XRecord') -> None: 76 entity.tags = Tags(entity.tags) 77 78 def load_dxf_attribs( 79 self, processor: SubclassProcessor = None) -> 'DXFNamespace': 80 dxf = super().load_dxf_attribs(processor) 81 if processor: 82 try: 83 tags = processor.subclasses[1] 84 except IndexError: 85 raise DXFStructureError( 86 f'Missing subclass AcDbXrecord in XRecord (#{dxf.handle})') 87 start_index = 1 88 if len(tags) > 1: 89 # First tag is group code 280, but not for DXF R13/R14. 90 # SUT: doc may be None, but then doc also can not 91 # be R13/R14 - ezdxf does not create R13/R14 92 if self.doc is None or self.doc.dxfversion >= DXF2000: 93 code, value = tags[1] 94 if code == 280: 95 dxf.cloning = value 96 start_index = 2 97 else: # just log recoverable error 98 logger.info( 99 f'XRecord (#{dxf.handle}): expected group code 280 ' 100 f'as first tag in AcDbXrecord' 101 ) 102 self.tags = Tags(tags[start_index:]) 103 return dxf 104 105 def export_entity(self, tagwriter: 'TagWriter') -> None: 106 super().export_entity(tagwriter) 107 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_xrecord.name) 108 tagwriter.write_tag2(280, self.dxf.cloning) 109 tagwriter.write_tags(Tags(totags(self.tags))) 110 111 112acdb_vba_project = DefSubclass('AcDbVbaProject', { 113 # 90: Number of bytes of binary chunk data (contained in the group code 114 # 310 records that follow) 115 # 310: DXF: Binary object data (multiple entries containing VBA project 116 # data) 117}) 118 119 120@register_entity 121class VBAProject(DXFObject): 122 """ DXF VBA_PROJECT entity """ 123 DXFTYPE = 'VBA_PROJECT' 124 DXFATTRIBS = DXFAttributes(base_class, acdb_vba_project) 125 126 def __init__(self): 127 super().__init__() 128 self.data = b'' 129 130 def _copy_data(self, entity: 'VBAProject') -> None: 131 entity.tags = Tags(entity.tags) 132 133 def load_dxf_attribs( 134 self, processor: SubclassProcessor = None) -> 'DXFNamespace': 135 dxf = super().load_dxf_attribs(processor) 136 if processor: 137 self.load_byte_data(processor.subclasses[1]) 138 return dxf 139 140 def load_byte_data(self, tags: 'Tags') -> None: 141 byte_array = array.array('B') 142 # Translation from String to binary data happens in tag_compiler(): 143 for byte_data in (tag.value for tag in tags if tag.code == 310): 144 byte_array.extend(byte_data) 145 self.data = byte_array.tobytes() 146 147 def export_entity(self, tagwriter: 'TagWriter') -> None: 148 super().export_entity(tagwriter) 149 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_vba_project.name) 150 tagwriter.write_tag2(90, len(self.data)) 151 self.export_data(tagwriter) 152 153 def export_data(self, tagwriter: 'TagWriter'): 154 data = self.data 155 while data: 156 tagwriter.write_tag(DXFBinaryTag(310, data[:127])) 157 data = data[127:] 158 159 def clear(self) -> None: 160 self.data = b'' 161 162 163acdb_sort_ents_table = DefSubclass('AcDbSortentsTable', { 164 # Soft-pointer ID/handle to owner (currently only the *MODEL_SPACE or 165 # *PAPER_SPACE blocks) in ezdxf the block_record handle for a layout is 166 # also called layout_key: 167 'block_record_handle': DXFAttr(330), 168 # 331: Soft-pointer ID/handle to an entity (zero or more entries may exist) 169 # 5: Sort handle (zero or more entries may exist) 170}) 171acdb_sort_ents_table_group_codes = group_code_mapping(acdb_sort_ents_table) 172 173 174@register_entity 175class SortEntsTable(DXFObject): 176 """ DXF SORTENTSTABLE entity - sort entities table """ 177 # should work with AC1015/R2000 but causes problems with TrueView/AutoCAD 178 # LT 2019: "expected was-a-zombie-flag" 179 # No problems with AC1018/R2004 and later 180 # 181 # If the header variable $SORTENTS Regen flag (bit-code value 16) is set, 182 # AutoCAD regenerates entities in ascending handle order. 183 # 184 # When the DRAWORDER command is used, a SORTENTSTABLE object is attached to 185 # the *Model_Space or *Paper_Space block's extension dictionary under the 186 # name ACAD_SORTENTS. The SORTENTSTABLE object related to this dictionary 187 # associates a different handle with each entity, which redefines the order 188 # in which the entities are regenerated. 189 # 190 # $SORTENTS (280): Controls the object sorting methods (bitcode): 191 # 0 = Disables SORTENTS 192 # 1 = Sorts for object selection 193 # 2 = Sorts for object snap 194 # 4 = Sorts for redraws; obsolete 195 # 8 = Sorts for MSLIDE command slide creation; obsolete 196 # 16 = Sorts for REGEN commands 197 # 32 = Sorts for plotting 198 # 64 = Sorts for PostScript output; obsolete 199 200 DXFTYPE = 'SORTENTSTABLE' 201 DXFATTRIBS = DXFAttributes(base_class, acdb_sort_ents_table) 202 203 def __init__(self): 204 super().__init__() 205 self.table: Dict[str, str] = dict() 206 207 def _copy_data(self, entity: 'SortEntsTable') -> None: 208 entity.tags = dict(entity.table) 209 210 def load_dxf_attribs( 211 self, processor: SubclassProcessor = None) -> 'DXFNamespace': 212 dxf = super().load_dxf_attribs(processor) 213 if processor: 214 tags = processor.fast_load_dxfattribs( 215 dxf, acdb_sort_ents_table_group_codes, 1, log=False) 216 self.load_table(tags) 217 return dxf 218 219 def load_table(self, tags: 'Tags') -> None: 220 for handle, sort_handle in take2(tags): 221 if handle.code != 331: 222 raise DXFStructureError( 223 f'Invalid handle code {handle.code}, expected 331') 224 if sort_handle.code != 5: 225 raise DXFStructureError( 226 f'Invalid sort handle code {handle.code}, expected 5') 227 self.table[handle.value] = sort_handle.value 228 229 def export_entity(self, tagwriter: 'TagWriter') -> None: 230 super().export_entity(tagwriter) 231 tagwriter.write_tag2(SUBCLASS_MARKER, acdb_sort_ents_table.name) 232 tagwriter.write_tag2(330, self.dxf.block_record_handle) 233 self.export_table(tagwriter) 234 235 def export_table(self, tagwriter: 'TagWriter'): 236 for handle, sort_handle in self.table.items(): 237 tagwriter.write_tag2(331, handle) 238 tagwriter.write_tag2(5, sort_handle) 239 240 def __len__(self) -> int: 241 return len(self.table) 242 243 def __iter__(self) -> Iterable: 244 """ Yields all redraw associations as (object_handle, sort_handle) 245 tuples. 246 247 """ 248 return iter(self.table.items()) 249 250 def append(self, handle: str, sort_handle: str) -> None: 251 """ Append redraw association (handle, sort_handle). 252 253 Args: 254 handle: DXF entity handle (uppercase hex value without leading '0x') 255 sort_handle: sort handle (uppercase hex value without leading '0x') 256 257 """ 258 self.table[handle] = sort_handle 259 260 def clear(self): 261 """ Remove all handles from redraw order table. """ 262 self.table = dict() 263 264 def set_handles(self, handles: Iterable[Tuple[str, str]]) -> None: 265 """ Set all redraw associations from iterable `handles`, after removing 266 all existing associations. 267 268 Args: 269 handles: iterable yielding (object_handle, sort_handle) tuples 270 271 """ 272 # The sort_handle doesn't have to be unique, same or all handles can 273 # share the same sort_handle and sort_handles can use existing handles 274 # too. 275 # 276 # The '0' handle can be used, but this sort_handle will be drawn as 277 # latest (on top of all other entities) and not as first as expected. 278 # Invalid entity handles will be ignored by AutoCAD. 279 self.table = dict(handles) 280 281 def remove_invalid_handles(self) -> None: 282 """ Remove all handles which do not exists in the drawing database. """ 283 entitydb = self.doc.entitydb 284 self.table = { 285 handle: sort_handle for handle, sort_handle in self.table.items() 286 if handle in entitydb 287 } 288 289 def remove_handle(self, handle: str) -> None: 290 """ Remove handle of DXF entity from redraw order table. 291 292 Args: 293 handle: DXF entity handle (uppercase hex value without leading '0x') 294 295 """ 296 try: 297 del self.table[handle] 298 except KeyError: 299 pass 300 301 302acdb_field = DefSubclass('AcDbField', { 303 'evaluator_id': DXFAttr(1), 304 'field_code': DXFAttr(2), 305 306 # Overflow of field code string 307 'field_code_overflow': DXFAttr(3), 308 309 # Number of child fields 310 'n_child_fields': DXFAttr(90), 311 312 # 360: Child field ID (AcDbHardOwnershipId); repeats for number of children 313 # 97: Number of object IDs used in the field code 314 # 331: Object ID used in the field code (AcDbSoftPointerId); repeats for 315 # the number of object IDs used in the field code 316 # 93: Number of the data set in the field 317 # 6: Key string for the field data; a key-field pair is repeated for the 318 # number of data sets in the field 319 # 7: Key string for the evaluated cache; this key is hard-coded 320 # as ACFD_FIELD_VALUE 321 # 90: Data type of field value 322 # 91: Long value (if data type of field value is long) 323 # 140: Double value (if data type of field value is double) 324 # 330: ID value, AcDbSoftPointerId (if data type of field value is ID) 325 # 92: Binary data buffer size (if data type of field value is binary) 326 # 310: Binary data (if data type of field value is binary) 327 # 301: Format string 328 # 9: Overflow of Format string 329 # 98: Length of format string 330 331}) 332 333 334# todo: implement FIELD 335# register when done 336class Field(DXFObject): 337 """ DXF FIELD entity """ 338 DXFTYPE = 'FIELD' 339 DXFATTRIBS = DXFAttributes(base_class, acdb_field) 340