1# Copyright (c) 2019 Manfred Moitzi
2# License: MIT License
3import pytest
4from copy import deepcopy
5from ezdxf.math import Vec3
6from ezdxf.entities.dxfentity import (
7    base_class, DXFAttributes, DXFNamespace, SubclassProcessor
8)
9from ezdxf.lldxf.attributes import (
10    group_code_mapping, DefSubclass, DXFAttr, XType
11)
12from ezdxf.entities.dxfgfx import acdb_entity
13from ezdxf.entities.line import acdb_line
14from ezdxf.lldxf.extendedtags import ExtendedTags
15from ezdxf.lldxf.const import DXFAttributeError
16from ezdxf.lldxf.tagwriter import TagCollector
17from ezdxf.lldxf.tags import Tags, DXFTag
18
19
20class DXFEntity:
21    """ Mockup """
22    DXFTYPE = 'DXFENTITY'
23    DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_line)
24
25
26@pytest.fixture
27def entity():
28    return DXFEntity()
29
30
31@pytest.fixture
32def processor():
33    return SubclassProcessor(ExtendedTags.from_text(TEST_1))
34
35
36def test_handle_and_owner(entity, processor):
37    attribs = DXFNamespace(processor, entity)
38    assert attribs.handle == 'FFFF'
39    assert attribs.owner == 'ABBA'
40    assert attribs._entity is entity
41
42
43def test_default_values(entity, processor):
44    attribs = DXFNamespace(processor, entity)
45    assert attribs.layer == '0'
46    assert attribs.color == 256
47    assert attribs.linetype == 'BYLAYER'
48    # this attributes do not really exist
49    assert attribs.hasattr('layer') is False
50    assert attribs.hasattr('color') is False
51    assert attribs.hasattr('linetype') is False
52
53
54def test_get_value_with_default(entity, processor):
55    attribs = DXFNamespace(processor, entity)
56    # return existing values
57    assert attribs.get('handle', '0') == 'FFFF'
58    # return given default value not DXF default value, which would be '0'
59    assert attribs.get('layer', 'mozman') == 'mozman'
60    # attribute has to a valid DXF attribute
61    with pytest.raises(DXFAttributeError):
62        _ = attribs.get('hallo', 0)
63
64    # attribs without default returns None -> will not exported to DXF file
65    assert attribs.color_name is None
66
67
68def test_set_values(entity, processor):
69    attribs = DXFNamespace(processor, entity)
70    attribs.handle = 'CDEF'
71    assert attribs.handle == 'CDEF'
72    attribs.set('owner', 'DADA')
73    assert attribs.owner == 'DADA'
74    # set new attribute
75    attribs.color = 7
76    assert attribs.color == 7
77    attribs.set('linetype', 'DOT')
78    assert attribs.linetype == 'DOT'
79    # attribute has to a valid DXF attribute
80    with pytest.raises(DXFAttributeError):
81        attribs.hallo = 0
82    with pytest.raises(DXFAttributeError):
83        attribs.set('hallo', 0)
84
85
86def test_value_types(entity, processor):
87    attribs = DXFNamespace(processor, entity)
88    attribs.handle = None  # None is always accepted, attribute is ignored at export
89    assert attribs.handle is None
90    attribs.handle = 'XYZ'
91    assert attribs.handle == 'XYZ', 'handle is just a string'
92    attribs.handle = 123
93    assert attribs.handle == '123', 'handle is just a string'
94    with pytest.raises(ValueError):
95        attribs.color = 'xxx'
96
97    attribs.start = (1, 2, 3)  # type: Vec3
98    assert attribs.start == (1, 2, 3)
99    assert attribs.start.x == 1
100    assert attribs.start.y == 2
101    assert attribs.start.z == 3
102
103
104def test_delete_attribs(entity, processor):
105    attribs = DXFNamespace(processor, entity)
106    attribs.layer = 'mozman'
107    assert attribs.layer == 'mozman'
108    del attribs.layer
109
110    # default value
111    assert attribs.layer == '0'
112    with pytest.raises(DXFAttributeError):
113        del attribs.color
114    attribs.discard('color')  # delete silently if not exist
115    with pytest.raises(DXFAttributeError):
116        del attribs.unsupported_attribute
117
118
119def test_is_supported(entity, processor):
120    attribs = DXFNamespace(processor, entity)
121    assert attribs.is_supported('linetype') is True
122    assert attribs.is_supported('true_color') is True  # ezdxf does not care about DXF versions at runtime
123    assert attribs.is_supported('xxx_mozman_xxx') is False
124
125
126def test_dxftype(entity, processor):
127    attribs = DXFNamespace(processor, entity)
128    assert attribs.dxftype == 'DXFENTITY'
129
130
131def test_cloning(entity, processor):
132    attribs = DXFNamespace(processor, entity)
133    attribs.color = 77
134    attribs2 = attribs.copy(entity)
135    # clone everything
136    assert attribs2._entity is attribs._entity
137    assert attribs2.handle is attribs.handle
138    assert attribs2.owner is attribs.owner
139    assert attribs2.color == 77
140    # do not harm original entity
141    assert attribs._entity is entity
142    assert attribs.handle == 'FFFF'
143    assert attribs.owner == 'ABBA'
144    # change clone
145    attribs2.color = 13
146    assert attribs.color == 77
147    assert attribs2.color == 13
148
149
150def test_deepcopy_usage(entity, processor):
151    attribs = DXFNamespace(processor, entity)
152    attribs.color = 77
153
154    attribs2 = deepcopy(attribs)
155    # clone everything
156    assert attribs2._entity is attribs._entity
157    assert attribs2.handle is attribs.handle
158    assert attribs2.owner is attribs.owner
159    assert attribs2.color == 77
160    # do not harm original entity
161    assert attribs._entity is entity
162    assert attribs.handle == 'FFFF'
163    assert attribs.owner == 'ABBA'
164    # change clone
165    attribs2.color = 13
166    assert attribs.color == 77
167    assert attribs2.color == 13
168
169
170def test_dxf_export_one_attribute(entity, processor):
171    attribs = DXFNamespace(processor, entity)
172    tagwriter = TagCollector()
173    attribs.export_dxf_attribs(tagwriter, 'handle')
174    assert len(tagwriter.tags) == 1
175    assert tagwriter.tags[0] == (5, 'FFFF')
176    with pytest.raises(DXFAttributeError):
177        attribs.export_dxf_attribute(tagwriter, 'mozman')
178
179
180def test_dxf_export_two_attribute(entity, processor):
181    attribs = DXFNamespace(processor, entity)
182    tagwriter = TagCollector()
183    attribs.export_dxf_attribs(tagwriter, ['handle', 'owner'])
184    assert len(tagwriter.tags) == 2
185    assert tagwriter.tags[0] == (5, 'FFFF')
186    assert tagwriter.tags[1] == (330, 'ABBA')
187
188
189@pytest.fixture
190def subclass():
191    return DefSubclass('AcDbTest', {
192        'test1': DXFAttr(1),
193        'test2': DXFAttr(2),
194        'test3': DXFAttr(1),  # duplicate group code
195        'callback': DXFAttr(3, xtype=XType.callback),
196        'none_callback': DXFAttr(3),  # duplicate group code
197    })
198
199
200@pytest.fixture
201def cls(subclass):
202    class TestEntity(DXFEntity):
203        DXFATTRIBS = DXFAttributes(subclass)
204    return TestEntity
205
206
207def load_tags_fast(cls, subclass, data):
208    ns = DXFNamespace(entity=cls())
209    mapping = group_code_mapping(subclass)
210    proc = SubclassProcessor(ExtendedTags(tags=data))
211    unprocessed_tags = proc.fast_load_dxfattribs(ns, mapping, 0)
212    return ns, unprocessed_tags
213
214
215def test_if_fast_load_handles_duplicate_group_codes(cls, subclass):
216    data = Tags([
217        DXFTag(0, 'ENTITY'),  # First subclass tag should be ignored
218        DXFTag(1, '1'),  # duplicate group code
219        DXFTag(2, '2'),
220        DXFTag(1, '3'),  # duplicate group code
221        DXFTag(7, 'unprocessed tag'),
222    ])
223    ns, unprocessed_tags = load_tags_fast(cls, subclass, data)
224    assert ns.test1 == '1'
225    assert ns.test2 == '2'
226    assert ns.test3 == '3'
227    assert len(unprocessed_tags) == 1
228    assert unprocessed_tags[0] == (7, 'unprocessed tag')
229
230
231def test_if_fast_load_handles_callback_group_codes(cls, subclass):
232    data = Tags([
233        DXFTag(0, 'ENTITY'),  # First subclass tag should be ignored
234        DXFTag(3, 'X'),  # callback value
235        DXFTag(3, 'Y'),  # none callback value
236    ])
237    ns, unprocessed_tags = load_tags_fast(cls, subclass, data)
238    assert ns.none_callback == 'Y'
239    assert ns.hasattr('callback') is False
240
241
242def test_if_fast_load_handles_unprocessed_duplicate_group_codes(cls, subclass):
243    data = Tags([
244        DXFTag(0, 'ENTITY'),  # First subclass tag should be ignored
245        DXFTag(1, '1'),  # duplicate group code
246        DXFTag(1, '3'),  # duplicate group code
247        DXFTag(1, '5'),  # duplicate group code, but without an attribute definition
248    ])
249    ns, unprocessed_tags = load_tags_fast(cls, subclass, data)
250    assert ns.test1 == '1'
251    assert ns.test3 == '3'
252    # only two group code 1 attributes are defined:
253    assert len(unprocessed_tags) == 1
254    assert unprocessed_tags[0] == (1, '5')
255
256
257TEST_1 = """0
258DXFENTITY
2595
260FFFF
261330
262ABBA
263"""
264
265if __name__ == '__main__':
266    pytest.main([__file__])
267