1# Purpose: acdsdata section manager
2# Created: 05.05.2014
3# Copyright (c) 2014-2019, Manfred Moitzi
4# License: MIT License
5"""
6ACDSDATA entities have NO handles, therefore they can not be stored in the drawing entity database.
7every routine written until now (2014-05-05), expects entities with valid handle
8
9section structure (work in progress):
100 <str> SECTION
112 <str> ACDSDATA
1270 <int> 2 # flag?
1371 <int> 6 # count of following ACDSSCHEMA entities ??? no, just another flag
14
150 <str> ACDSSCHEMA           # dxftype: schema definition
1690 <int> 0                   # schema number 0, 1, 2, 3 ...
171 <str> AcDb3DSolid_ASM_Data # schema name
18
192 <str> AcDbDs::ID           # subsection name
20280 <int> 10                 # subsection type 10 = ???
2191 <int> 8                   # data ???
22
232 <str> ASM_Data             # subsection name
24280 <int> 15                 # subsection type
2591 <int> 0                   # data ???
26101 <str> ACDSRECORD         # data
2795 <int> 0
2890 <int> 2
29...
30
310 <str> ACDSSCHEMA
3290 <int> 1
331 <str> AcDb_Thumbnail_Schema
34...
35
360 <str> ACDSSCHEMA
3790 <int> 2
381 <str> AcDbDs::TreatedAsObjectDataSchema
39...
40
410 <str> ACDSSCHEMA
4290 <int> 3
431 <str> AcDbDs::LegacySchema
442 <str> AcDbDs::Legacy
45280 <int> 1
4691 <int> 0
47
480 <str> ACDSSCHEMA
4990 <int> 4
501 <str> AcDbDs::IndexedPropertySchema
512 <str> AcDs:Indexable
52280 <int> 1
5391 <int> 0
54
550 <str> ACDSSCHEMA
5690 <int> 5
571 <str> AcDbDs::HandleAttributeSchema
582 <str> AcDbDs::HandleAttribute
59280 <int> 7
6091 <int> 1
61284 <int> 1
62
630 <str> ACDSRECORD               # dxftype: data record
6490 <int> 0                       # ??? flag
652 <str> AcDbDs::ID               # subsection name
66280 <int> 10                     # subsection type 10 = handle to owner entity, 3DSOLID/REGION
67320 <str> 339                    # handle
682 <str> ASM_Data                 # subsection name
69280 <int> 15                     # subsection type 15 = binary data
7094 <int> 1088                    # size of data
71310 <binary encoded data>        # data
72310 <binary encoded data>        # data
73...
74
750 <str> ENDSEC
76"""
77from typing import TYPE_CHECKING, Iterator, Iterable, List, Any
78from itertools import islice
79
80from ezdxf.lldxf.tags import group_tags, Tags
81from ezdxf.lldxf.const import DXFKeyError, DXFStructureError
82
83if TYPE_CHECKING:  # import forward declarations
84    from ezdxf.eztypes import TagWriter, Drawing
85
86
87class AcDsDataSection:
88    name = 'ACDSDATA'
89
90    def __init__(self, doc: 'Drawing', entities: Iterable[Tags] = None):
91        self.doc = doc
92        self.entities = []  # type: List[AcDsData]
93        self.section_info = []  # type: Tags
94        if entities is not None:
95            self.load_tags(iter(entities))
96
97    @property
98    def is_valid(self):
99        return len(self.section_info)
100
101    def load_tags(self, entities: Iterator[Tags]) -> None:
102        section_head = next(entities)
103        if section_head[0] != (0, 'SECTION') or section_head[1] != (2, 'ACDSDATA'):
104            raise DXFStructureError("Critical structure error in ACDSDATA section.")
105
106        self.section_info = section_head
107        for entity in entities:
108            self.append(AcDsData(entity))  # tags have no subclasses
109
110    def append(self, entity: 'AcDsData') -> None:
111        cls = ACDSDATA_TYPES.get(entity.dxftype(), AcDsData)
112        entity = cls(entity.tags)
113        self.entities.append(entity)
114
115    def export_dxf(self, tagwriter: 'TagWriter') -> None:
116        if not self.is_valid:
117            return
118        tagwriter.write_tags(self.section_info)
119        for entity in self.entities:
120            entity.export_dxf(tagwriter)
121        tagwriter.write_tag2(0, 'ENDSEC')
122
123    @property
124    def acdsrecords(self) -> Iterable['AcDsRecord']:
125        return (entity for entity in self.entities if entity.dxftype() == 'ACDSRECORD')
126
127    def get_acis_data(self, handle: str) -> List[str]:
128        for record in self.acdsrecords:
129            try:
130                section = record.get_section('AcDbDs::ID')
131            except DXFKeyError:  # not present
132                continue
133            asm_handle = section.get_first_value(320, None)
134            if asm_handle == handle:
135                try:
136                    asm_data = record.get_section('ASM_Data')
137                except DXFKeyError:  # no data stored
138                    break
139                return [tag.value for tag in asm_data if tag.code == 310]
140        return []
141
142
143class AcDsData:
144    def __init__(self, tags: Tags):
145        self.tags = tags
146
147    def export_dxf(self, tagwriter: 'TagWriter'):
148        tagwriter.write_tags(self.tags)
149
150    def dxftype(self) -> str:
151        return self.tags[0].value
152
153
154class Section(Tags):
155    @property
156    def name(self) -> str:
157        return self[0].value
158
159    @property
160    def type(self) -> str:
161        return self[1].value
162
163    @property
164    def data(self) -> Tags:
165        return self[2:]
166
167
168class AcDsRecord:
169    def __init__(self, tags: Tags):
170        self._dxftype = tags[0]
171        self.flags = tags[1]
172        self.sections = [Section(group) for group in group_tags(islice(tags, 2, None), splitcode=2)]
173
174    def dxftype(self) -> str:
175        return 'ACDSRECORD'
176
177    def has_section(self, name: str) -> bool:
178        return self.get_section(name, default=None) is not None
179
180    def get_section(self, name: str, default: Any = DXFKeyError) -> Section:
181        for section in self.sections:
182            if section.name == name:
183                return section
184        if default is DXFKeyError:
185            raise DXFKeyError(name)
186        else:
187            return default
188
189    def __len__(self):
190        return len(self.sections)
191
192    def __getitem__(self, item) -> Section:
193        return self.sections[item]
194
195    def _write_header(self, tagwriter: 'TagWriter') -> None:
196        tagwriter.write_tags(Tags([self._dxftype, self.flags]))
197
198    def export_dxf(self, tagwriter: 'TagWriter') -> None:
199        self._write_header(tagwriter)
200        for section in self.sections:
201            tagwriter.write_tags(section)
202
203
204ACDSDATA_TYPES = {
205    'ACDSRECORD': AcDsRecord,
206}
207