1# Copyright (c) 2011-2021, Manfred Moitzi
2# License: MIT License
3from typing import (
4    TYPE_CHECKING, TextIO, BinaryIO, Iterable, Union, Tuple, Callable,
5    cast, Optional, List,
6)
7from datetime import datetime
8import io
9import base64
10import logging
11from itertools import chain
12
13from ezdxf.layouts import Modelspace
14from ezdxf.lldxf import const
15from ezdxf.lldxf.const import (
16    BLK_XREF, BLK_EXTERNAL, DXF13, DXF14, DXF2000, DXF2007, DXF12, DXF2013,
17)
18from ezdxf.lldxf import loader
19from ezdxf.lldxf.tagwriter import TagWriter, BinaryTagWriter
20
21from ezdxf.entitydb import EntityDB
22from ezdxf.layouts.layouts import Layouts
23from ezdxf.tools.codepage import tocodepage, toencoding
24from ezdxf.tools.juliandate import juliandate
25from ezdxf.options import options
26
27from ezdxf.tools import guid
28from ezdxf.query import EntityQuery
29from ezdxf.groupby import groupby
30from ezdxf.render.dimension import DimensionRenderer
31
32from ezdxf.sections.header import HeaderSection
33from ezdxf.sections.classes import ClassesSection
34from ezdxf.sections.tables import TablesSection
35from ezdxf.sections.blocks import BlocksSection
36from ezdxf.sections.entities import EntitySection, StoredSection
37from ezdxf.sections.objects import ObjectsSection
38from ezdxf.sections.acdsdata import AcDsDataSection
39
40from ezdxf.entities.dxfgroups import GroupCollection
41from ezdxf.entities.material import MaterialCollection
42from ezdxf.entities.mleader import MLeaderStyleCollection
43from ezdxf.entities.mline import MLineStyleCollection
44
45logger = logging.getLogger('ezdxf')
46
47if TYPE_CHECKING:
48    from ezdxf.eztypes import (
49        DXFTag, Table, ViewportTable, VPort, Dictionary, Layout,
50        DXFEntity, Layer, Auditor, GenericLayoutType,
51    )
52    from pathlib import Path
53
54FIXED_GUID = '{00000000-0000-0000-0000-000000000000}'
55
56def _validate_handle_seed(seed: str) -> str:
57    from ezdxf.tools.handle import START_HANDLE
58    if seed is None:
59        seed = START_HANDLE
60    try:
61        v = int(seed, 16)
62        if v < 1:
63            seed = START_HANDLE
64    except ValueError:
65        seed = START_HANDLE
66    return seed
67
68
69class Drawing:
70    def __init__(self, dxfversion=DXF2013):
71        self.entitydb = EntityDB()
72        target_dxfversion = dxfversion.upper()
73        self._dxfversion: str = const.acad_release_to_dxf_version.get(
74            target_dxfversion, target_dxfversion)
75        if self._dxfversion not in const.versions_supported_by_new:
76            raise const.DXFVersionError(
77                f'Unsupported DXF version "{self.dxfversion}".')
78        # Store original dxf version if loaded (and maybe converted R13/14)
79        # from file.
80        self._loaded_dxfversion: Optional[str] = None
81
82        # Status flag which is True while loading content from a DXF file:
83        self.is_loading = False
84        self.encoding: str = 'cp1252'  # read/write
85        self.filename: Optional[str] = None
86
87        # named objects dictionary
88        self.rootdict: 'Dictionary' = None
89
90        # DXF sections
91        self.header: HeaderSection = None
92        self.classes: ClassesSection = None
93        self.tables: TablesSection = None
94        self.blocks: BlocksSection = None
95        self.entities: EntitySection = None
96        self.objects: ObjectsSection = None
97
98        # DXF R2013 and later
99        self.acdsdata: AcDsDataSection = None
100
101        self.stored_sections = []
102        self.layouts: Layouts = None
103        self.groups: GroupCollection = None
104        self.materials: MaterialCollection = None
105        self.mleader_styles: MLeaderStyleCollection = None
106        self.mline_styles: MLineStyleCollection = None
107
108        # Set to False if the generated DXF file will be incompatible to AutoCAD
109        self._acad_compatible = True
110        # Store reasons for AutoCAD incompatibility:
111        self._acad_incompatibility_reason = set()
112
113        # DIMENSION rendering engine can be replaced by a custom Dimension
114        # render: see property Drawing.dimension_renderer
115        self._dimension_renderer = DimensionRenderer()
116
117        # Some fixes can't be applied while the DXF document is not fully
118        # initialized, store this fixes as callable object:
119        self._post_init_commands: List[Callable] = []
120        # Don't create any new entities here:
121        # New created handles could collide with handles loaded from DXF file.
122        assert len(self.entitydb) == 0
123
124    @classmethod
125    def new(cls, dxfversion: str = DXF2013) -> 'Drawing':
126        """ Create new drawing. Package users should use the factory function
127        :func:`ezdxf.new`. (internal API)
128        """
129        doc = cls(dxfversion)
130        doc._setup()
131        return doc
132
133    def _setup(self):
134        self.header = HeaderSection.new()
135        self.classes = ClassesSection(self)
136        self.tables = TablesSection(self)
137        self.blocks = BlocksSection(self)
138        self.entities = EntitySection(self)
139        self.objects = ObjectsSection(self)
140        # AcDSData section is not supported for new drawings
141        self.acdsdata = AcDsDataSection(self)
142        self.rootdict = self.objects.rootdict
143        # Create missing tables:
144        self.objects.setup_objects_management_tables(self.rootdict)
145        self.layouts = Layouts.setup(self)
146        self._finalize_setup()
147
148    def _finalize_setup(self):
149        """ Common setup tasks for new and loaded DXF drawings. """
150        self.groups = GroupCollection(self)
151        self.materials = MaterialCollection(self)
152
153        self.mline_styles = MLineStyleCollection(self)
154        # all required internal structures are ready
155        # now do the stuff to please AutoCAD
156        self._create_required_table_entries()
157
158        # mleader_styles requires text styles
159        self.mleader_styles = MLeaderStyleCollection(self)
160        self._set_required_layer_attributes()
161        self._setup_metadata()
162        self._execute_post_init_commands()
163
164    def _execute_post_init_commands(self):
165        for cmd in self._post_init_commands:
166            cmd()
167        del self._post_init_commands
168
169    def _create_required_table_entries(self):
170        self._create_required_vports()
171        self._create_required_linetypes()
172        self._create_required_layers()
173        self._create_required_styles()
174        self._create_required_appids()
175        self._create_required_dimstyles()
176
177    def _set_required_layer_attributes(self):
178        for layer in self.layers:  # type: Layer
179            layer.set_required_attributes()
180
181    def _create_required_vports(self):
182        if '*Active' not in self.viewports:
183            self.viewports.new('*Active')
184
185    def _create_required_appids(self):
186        if 'ACAD' not in self.appids:
187            self.appids.new('ACAD')
188
189    def _create_required_linetypes(self):
190        linetypes = self.linetypes
191        for name in ('ByBlock', 'ByLayer', 'Continuous'):
192            if name not in linetypes:
193                linetypes.new(name)
194
195    def _create_required_dimstyles(self):
196        if 'Standard' not in self.dimstyles:
197            self.dimstyles.new('Standard')
198
199    def _create_required_styles(self):
200        if 'Standard' not in self.styles:
201            self.styles.new('Standard')
202
203    def _create_required_layers(self):
204        layers = self.layers
205        if '0' not in layers:
206            layers.new('0')
207        if 'Defpoints' not in layers:
208            layers.new('Defpoints', dxfattribs={'plot': 0})  # do not plot
209        else:
210            # AutoCAD requires a plot flag = 0
211            layers.get('Defpoints').dxf.plot = 0
212
213    def _setup_metadata(self):
214        self.header['$ACADVER'] = self.dxfversion
215        self.header['$TDCREATE'] = juliandate(datetime.now())
216        self.reset_fingerprint_guid()
217        self.reset_version_guid()
218
219    @property
220    def dxfversion(self) -> str:
221        """ Get current DXF version. """
222        return self._dxfversion
223
224    @dxfversion.setter
225    def dxfversion(self, version) -> None:
226        """ Set current DXF version. """
227        self._dxfversion = self._validate_dxf_version(version)
228        self.header['$ACADVER'] = version
229
230    @property
231    def output_encoding(self):
232        """ Returns required output encoding for writing document to a text
233        streams.
234        """
235        return 'utf-8' if self.dxfversion >= DXF2007 else self.encoding
236
237    @property
238    def units(self) -> int:
239        """ Get and set the document/modelspace base units as enum, for more
240        information read this: :ref:`dxf units`.
241
242        """
243        return self.header.get('$INSUNITS', 0)
244
245    @units.setter
246    def units(self, unit_enum: int) -> None:
247        if 0 <= unit_enum < 25:
248            self.header['$INSUNITS'] = unit_enum
249        else:
250            raise ValueError(f'Invalid units enum: {unit_enum}')
251
252    def _validate_dxf_version(self, version: str) -> str:
253        version = version.upper()
254        # translates 'R12' -> 'AC1009'
255        version = const.acad_release_to_dxf_version.get(version, version)
256        if version not in const.versions_supported_by_save:
257            raise const.DXFVersionError(f'Unsupported DXF version "{version}".')
258        if version == DXF12:
259            if self._dxfversion > DXF12:
260                logger.warning(
261                    f'Downgrade from DXF {self.acad_release} to R12 may create '
262                    f'an invalid DXF file.')
263        elif version < self._dxfversion:
264            logger.info(
265                f'Downgrade from DXF {self.acad_release} to '
266                f'{const.acad_release[version]} can cause lost of features.')
267        return version
268
269    @classmethod
270    def read(cls, stream: TextIO) -> 'Drawing':
271        """ Open an existing drawing. Package users should use the factory
272        function :func:`ezdxf.read`. To preserve possible binary data in
273        XRECORD entities use :code:`errors='surrogateescape'` as error handler
274        for the import stream.
275
276        Args:
277             stream: text stream yielding text (unicode) strings by readline()
278
279        """
280        from .lldxf.tagger import ascii_tags_loader
281        tag_loader = ascii_tags_loader(stream)
282        return cls.load(tag_loader)
283
284    @classmethod
285    def load(cls, tag_loader: Iterable['DXFTag']) -> 'Drawing':
286        """ Load DXF document from a DXF tag loader, in general an external
287        untrusted source.
288
289        Args:
290            tag_loader: DXF tag loader
291
292        """
293        from .lldxf.tagger import tag_compiler
294        tag_loader = tag_compiler(tag_loader)
295        doc = cls()
296        doc._load(tag_loader)
297        return doc
298
299    @classmethod
300    def from_tags(cls, compiled_tags: Iterable['DXFTag']) -> 'Drawing':
301        """ Create new drawing from compiled tags. (internal API)"""
302        doc = cls()
303        doc._load(tagger=compiled_tags)
304        return doc
305
306    def _load(self, tagger: Optional[Iterable['DXFTag']]) -> None:
307        # 1st Loading stage: load complete DXF entity structure
308        self.is_loading = True
309        sections = loader.load_dxf_structure(tagger)
310        if 'THUMBNAILIMAGE' in sections:
311            del sections['THUMBNAILIMAGE']
312        self._load_section_dict(sections)
313
314    def _load_section_dict(self, sections: loader.SectionDict) -> None:
315        """ Internal API to load a DXF document from a section dict. """
316        self.is_loading = True
317        # Create header section:
318        # All header tags are the first DXF structure entity
319        header_entities = sections.get('HEADER', [None])[0]
320        if header_entities is None:
321            # Create default header, files without header are by default DXF R12
322            self.header = HeaderSection.new(dxfversion=DXF12)
323        else:
324            self.header = HeaderSection.load(header_entities)
325
326        self._dxfversion: str = self.header.get('$ACADVER', DXF12)
327
328        # Store original DXF version of loaded file.
329        self._loaded_dxfversion = self._dxfversion
330
331        # Content encoding:
332        self.encoding = toencoding(self.header.get('$DWGCODEPAGE', 'ANSI_1252'))
333
334        # Set handle seed:
335        seed: str = self.header.get('$HANDSEED', str(self.entitydb.handles))
336        self.entitydb.handles.reset(_validate_handle_seed(seed))
337
338        # Store all necessary DXF entities in the entity database:
339        loader.load_and_bind_dxf_content(sections, self)
340
341        # End of 1. loading stage, all entities of the DXF file are
342        # stored in the entity database.
343
344        # Create sections:
345        self.classes = ClassesSection(self, sections.get('CLASSES', None))
346        self.tables = TablesSection(self, sections.get('TABLES', None))
347
348        # Create *Model_Space and *Paper_Space BLOCK_RECORDS
349        # BlockSection setup takes care about the rest:
350        self._create_required_block_records()
351
352        # At this point all table entries are required:
353        self.blocks = BlocksSection(self, sections.get('BLOCKS', None))
354        self.entities = EntitySection(self, sections.get('ENTITIES', None))
355        self.objects = ObjectsSection(self, sections.get('OBJECTS', None))
356
357        # only DXF R2013+
358        self.acdsdata = AcDsDataSection(self, sections.get('ACDSDATA', None))
359
360        # Store unmanaged sections as raw tags:
361        for name, data in sections.items():
362            if name not in const.MANAGED_SECTIONS:
363                self.stored_sections.append(StoredSection(data))
364
365        # Objects section is not initialized!
366        self._2nd_loading_stage()
367
368        # DXF version upgrades:
369        if self.dxfversion < DXF12:
370            logger.info('DXF version upgrade to DXF R12.')
371            self.dxfversion = DXF12
372
373        if self.dxfversion == DXF12:
374            self.tables.create_table_handles()
375
376        if self.dxfversion in (DXF13, DXF14):
377            logger.info('DXF version upgrade to DXF R2000.')
378            self.dxfversion = DXF2000
379            self.create_all_arrow_blocks()
380
381        # Objects section setup:
382        self.rootdict = self.objects.rootdict
383        # Create missing management tables (DICTIONARY):
384        self.objects.setup_objects_management_tables(self.rootdict)
385
386        # Setup modelspace- and paperspace layouts:
387        self.layouts = Layouts.load(self)
388
389        # Additional work is common to the new and load process:
390        self.is_loading = False
391        self._finalize_setup()
392
393    def _2nd_loading_stage(self):
394        """ Load additional resources from entity database into DXF entities.
395
396        e.g. convert handles into DXFEntity() objects
397
398        """
399        db = self.entitydb
400        for entity in db.values():
401            # The post_load_hook() can return a callable, which should be
402            # executed, when the DXF document is fully initialized.
403            cmd = entity.post_load_hook(self)
404            if cmd is not None:
405                self._post_init_commands.append(cmd)
406
407    def create_all_arrow_blocks(self):
408        """ For upgrading DXF R12/13/14 files to R2000, it is necessary to
409        create all used arrow blocks before saving the DXF file, else $HANDSEED
410        is not the next available handle, which is a problem for AutoCAD.
411        To be save create all known AutoCAD arrows, because references to arrow
412        blocks can be in DIMSTYLE, DIMENSION override, LEADER override and maybe
413        other places.
414
415        """
416        from ezdxf.render.arrows import ARROWS
417        for arrow_name in ARROWS.__acad__:
418            ARROWS.create_block(self.blocks, arrow_name)
419
420    def _create_required_block_records(self):
421        if '*Model_Space' not in self.block_records:
422            self.block_records.new('*Model_Space')
423        if '*Paper_Space' not in self.block_records:
424            self.block_records.new('*Paper_Space')
425
426    def saveas(self, filename: Union[str, 'Path'], encoding: str = None,
427               fmt: str = 'asc') -> None:
428        """ Set :class:`Drawing` attribute :attr:`filename` to `filename` and
429        write drawing to the file system. Override file encoding by argument
430        `encoding`, handle with care, but this option allows you to create DXF
431        files for applications that handles file encoding different than
432        AutoCAD.
433
434        Args:
435            filename: file name as string
436            encoding: override default encoding as Python encoding string like ``'utf-8'``
437            fmt: ``'asc'`` for ASCII DXF (default) or ``'bin'`` for Binary DXF
438
439        """
440        self.filename = str(filename)
441        self.save(encoding=encoding, fmt=fmt)
442
443    def save(self, encoding: str = None, fmt: str = 'asc') -> None:
444        """ Write drawing to file-system by using the :attr:`filename` attribute
445        as filename. Override file encoding by argument `encoding`, handle with
446        care, but this option allows you to create DXF files for applications
447        that handles file encoding different than AutoCAD.
448
449        Args:
450            encoding: override default encoding as Python encoding string like ``'utf-8'``
451            fmt: ``'asc'`` for ASCII DXF (default) or ``'bin'`` for Binary DXF
452
453        """
454        # DXF R12, R2000, R2004 - ASCII encoding
455        # DXF R2007 and newer - UTF-8 encoding
456        # in ASCII mode, unknown characters will be escaped as \U+nnnn unicode
457        # characters.
458
459        if encoding is None:
460            enc = self.output_encoding
461        else:
462            # override default encoding, for applications that handle encoding
463            # different than AutoCAD
464            enc = encoding
465
466        if fmt.startswith('asc'):
467            fp = io.open(self.filename, mode='wt', encoding=enc,
468                         errors='dxfreplace')
469        elif fmt.startswith('bin'):
470            fp = open(self.filename, 'wb')
471        else:
472            raise ValueError(f"Unknown output format: '{fmt}'.")
473        try:
474            self.write(fp, fmt=fmt)
475        finally:
476            fp.close()
477
478    def encode(self, s: str) -> bytes:
479        """ Encode string `s` with correct encoding and error handler. """
480        return s.encode(encoding=self.output_encoding, errors='dxfreplace')
481
482    def write(self, stream: Union[TextIO, BinaryIO], fmt: str = 'asc') -> None:
483        """ Write drawing as ASCII DXF to a text stream or as Binary DXF to a
484        binary stream. For DXF R2004 (AC1018) and prior open stream with
485        drawing :attr:`encoding` and :code:`mode='wt'`. For DXF R2007 (AC1021)
486        and later use :code:`encoding='utf-8'`, or better use the later added
487        :class:`Drawing` property :attr:`output_encoding` which returns the
488        correct encoding automatically. The correct and required error handler
489        is :code:`errors='dxfreplace'`!
490
491        If writing to a :class:`StringIO` stream, use :meth:`Drawing.encode` to
492        encode the result string from :meth:`StringIO.get_value`::
493
494            binary = doc.encode(stream.get_value())
495
496        Args:
497            stream: output text stream or binary stream
498            fmt: ``'asc'`` for ASCII DXF (default) or ``'bin'`` for binary DXF
499
500        """
501        dxfversion = self.dxfversion
502        if dxfversion == DXF12:
503            handles = bool(self.header.get('$HANDLING', 0))
504        else:
505            handles = True
506        if dxfversion > DXF12:
507            self.classes.add_required_classes(dxfversion)
508
509        self._create_appids()
510        self._update_header_vars()
511        self.update_extents()
512        self.update_limits()
513        self._update_metadata()
514
515        if fmt.startswith('asc'):
516            tagwriter = TagWriter(stream, write_handles=handles,
517                                  dxfversion=dxfversion)
518        elif fmt.startswith('bin'):
519            tagwriter = BinaryTagWriter(
520                stream, write_handles=handles, dxfversion=dxfversion,
521                encoding=self.output_encoding,
522            )
523            tagwriter.write_signature()
524        else:
525            raise ValueError(f"Unknown output format: '{fmt}'.")
526
527        self.export_sections(tagwriter)
528
529    def encode_base64(self) -> bytes:
530        """ Returns DXF document as base64 encoded binary data. """
531        stream = io.StringIO()
532        self.write(stream)
533        # Create binary data:
534        binary_data = self.encode(stream.getvalue())
535        # Create Windows line endings and do base64 encoding:
536        return base64.encodebytes(binary_data.replace(b'\n', b'\r\n'))
537
538    def export_sections(self, tagwriter: 'TagWriter') -> None:
539        """ DXF export sections. (internal API) """
540        dxfversion = tagwriter.dxfversion
541        self.header.export_dxf(tagwriter)
542        if dxfversion > DXF12:
543            self.classes.export_dxf(tagwriter)
544        self.tables.export_dxf(tagwriter)
545        self.blocks.export_dxf(tagwriter)
546        self.entities.export_dxf(tagwriter)
547        if dxfversion > DXF12:
548            self.objects.export_dxf(tagwriter)
549        if self.acdsdata.is_valid:
550            self.acdsdata.export_dxf(tagwriter)
551        for section in self.stored_sections:
552            section.export_dxf(tagwriter)
553
554        tagwriter.write_tag2(0, 'EOF')
555
556    def update_extents(self):
557        msp = self.modelspace()
558        self.header['$EXTMIN'] = msp.dxf.extmin
559        self.header['$EXTMAX'] = msp.dxf.extmax
560        active_layout = self.active_layout()
561        self.header['$PEXTMIN'] = active_layout.dxf.extmin
562        self.header['$PEXTMAX'] = active_layout.dxf.extmax
563
564    def update_limits(self):
565        msp = self.modelspace()
566        self.header['$LIMMIN'] = msp.dxf.limmin
567        self.header['$LIMMAX'] = msp.dxf.limmax
568        active_layout = self.active_layout()
569        self.header['$PLIMMIN'] = active_layout.dxf.limmin
570        self.header['$PLIMMAX'] = active_layout.dxf.limmax
571
572    def _update_header_vars(self):
573        from ezdxf.lldxf.const import acad_maint_ver
574
575        # set or correct $CMATERIAL handle
576        material = self.entitydb.get(self.header.get('$CMATERIAL', None))
577        if material is None or material.dxftype() != 'MATERIAL':
578            if 'ByLayer' in self.materials:
579                self.header['$CMATERIAL'] = self.materials.get(
580                    'ByLayer').dxf.handle
581            else:  # set any handle, except '0' which crashes BricsCAD
582                self.header['$CMATERIAL'] = '45'
583
584        # set ACAD maintenance version - same values as used by BricsCAD
585        self.header['$ACADMAINTVER'] = acad_maint_ver.get(self.dxfversion, 0)
586
587    def _update_metadata(self):
588        if options.write_fixed_meta_data_for_testing:
589            fixed_date = juliandate(datetime(2000, 1, 1, 0, 0))
590            self.header['$TDCREATE'] = fixed_date
591            self.header['$TDUCREATE'] = fixed_date
592            self.header['$TDUPDATE'] = fixed_date
593            self.header['$TDUUPDATE'] = fixed_date
594            self.header['$VERSIONGUID'] = FIXED_GUID
595            self.header['$FINGERPRINTGUID'] = FIXED_GUID
596        else:
597            now = datetime.now()
598            self.header['$TDUPDATE'] = juliandate(now)
599            self.reset_version_guid()
600
601        self.header['$HANDSEED'] = str(self.entitydb.handles)  # next handle
602        self.header['$DWGCODEPAGE'] = tocodepage(self.encoding)
603
604    def _create_appid_if_not_exist(self, name: str, flags: int = 0) -> None:
605        if name not in self.appids:
606            self.appids.new(name, {'flags': flags})
607
608    def _create_appids(self):
609        self._create_appid_if_not_exist('HATCHBACKGROUNDCOLOR', 0)
610
611    @property
612    def acad_release(self) -> str:
613        """ Returns the AutoCAD release number like ``'R12'`` or ``'R2000'``.
614        """
615        return const.acad_release.get(self.dxfversion, "unknown")
616
617    @property
618    def layers(self) -> 'Table':
619        return self.tables.layers
620
621    @property
622    def linetypes(self) -> 'Table':
623        return self.tables.linetypes
624
625    @property
626    def styles(self) -> 'Table':
627        return self.tables.styles
628
629    @property
630    def dimstyles(self) -> 'Table':
631        return self.tables.dimstyles
632
633    @property
634    def ucs(self) -> 'Table':
635        return self.tables.ucs
636
637    @property
638    def appids(self) -> 'Table':
639        return self.tables.appids
640
641    @property
642    def views(self) -> 'Table':
643        return self.tables.views
644
645    @property
646    def block_records(self) -> 'Table':
647        return self.tables.block_records
648
649    @property
650    def viewports(self) -> 'ViewportTable':
651        return self.tables.viewports
652
653    @property
654    def plotstyles(self) -> 'Dictionary':
655        return self.rootdict['ACAD_PLOTSTYLENAME']
656
657    @property
658    def dimension_renderer(self) -> DimensionRenderer:
659        return self._dimension_renderer
660
661    @dimension_renderer.setter
662    def dimension_renderer(self, renderer: DimensionRenderer) -> None:
663        """
664        Set your own dimension line renderer if needed.
665
666        see also: ezdxf.render.dimension
667
668        """
669        self._dimension_renderer = renderer
670
671    def modelspace(self) -> 'Modelspace':
672        """ Returns the modelspace layout, displayed as ``'Model'`` tab in CAD
673        applications, defined by block record named ``'*Model_Space'``.
674        """
675        return self.layouts.modelspace()
676
677    def layout(self, name: str = None) -> 'Layout':
678        """ Returns paperspace layout `name` or returns first layout in tab
679        order if `name` is ``None``.
680        """
681        return self.layouts.get(name)
682
683    def active_layout(self) -> 'Layout':
684        """ Returns the active paperspace layout, defined by block record
685        name ``'*Paper_Space'``.
686        """
687        return self.layouts.active_layout()
688
689    def layout_names(self) -> Iterable[str]:
690        """ Returns all layout names (modelspace ``'Model'`` included) in
691        arbitrary order.
692        """
693        return list(self.layouts.names())
694
695    def layout_names_in_taborder(self) -> Iterable[str]:
696        """ Returns all layout names (modelspace included, always first name)
697        in tab order.
698        """
699        return list(self.layouts.names_in_taborder())
700
701    def reset_fingerprint_guid(self):
702        """ Reset fingerprint GUID. """
703        self.header['$FINGERPRINTGUID'] = guid()
704
705    def reset_version_guid(self):
706        """ Reset version GUID. """
707        self.header['$VERSIONGUID'] = guid()
708
709    @property
710    def acad_compatible(self) -> bool:
711        """ Returns ``True`` if drawing is AutoCAD compatible. """
712        return self._acad_compatible
713
714    def add_acad_incompatibility_message(self, msg: str):
715        """ Add AutoCAD incompatibility message. (internal API) """
716        self._acad_compatible = False
717        if msg not in self._acad_incompatibility_reason:
718            self._acad_incompatibility_reason.add(msg)
719            logger.warning(
720                f'Drawing is incompatible to AutoCAD, because {msg}.')
721
722    def query(self, query: str = '*') -> EntityQuery:
723        """
724        Entity query over all layouts and blocks, excluding the OBJECTS section.
725
726        Args:
727            query: query string
728
729        .. seealso::
730
731            :ref:`entity query string` and :ref:`entity queries`
732
733        """
734        return EntityQuery(self.chain_layouts_and_blocks(), query)
735
736    def groupby(self, dxfattrib="", key=None) -> dict:
737        """ Groups DXF entities of all layouts and blocks (excluding the
738        OBJECTS section) by a DXF attribute or a key function.
739
740        Args:
741            dxfattrib: grouping DXF attribute like ``'layer'``
742            key: key function, which accepts a :class:`DXFEntity` as argument
743                and returns a hashable grouping key or ``None`` to ignore
744                this entity.
745
746        .. seealso::
747
748            :func:`~ezdxf.groupby.groupby` documentation
749
750        """
751        return groupby(self.chain_layouts_and_blocks(), dxfattrib, key)
752
753    def chain_layouts_and_blocks(self) -> Iterable['DXFEntity']:
754        """ Chain entity spaces of all layouts and blocks. Yields an iterator
755        for all entities in all layouts and blocks.
756
757        """
758        layouts = list(self.layouts_and_blocks())
759        return chain.from_iterable(layouts)
760
761    def layouts_and_blocks(self) -> Iterable['GenericLayoutType']:
762        """ Iterate over all layouts (modelspace and paperspace) and all
763        block definitions.
764
765        """
766        return iter(self.blocks)
767
768    def delete_layout(self, name: str) -> None:
769        """
770        Delete paper space layout `name` and all entities owned by this layout.
771        Available only for DXF R2000 or later, DXF R12 supports only one
772        paperspace and it can't be deleted.
773
774        """
775        if name not in self.layouts:
776            raise const.DXFValueError(f"Layout '{name}' does not exist.")
777        else:
778            self.layouts.delete(name)
779
780    def new_layout(self, name, dxfattribs=None) -> 'Layout':
781        """
782        Create a new paperspace layout `name`. Returns a
783        :class:`~ezdxf.layouts.Layout` object.
784        DXF R12 (AC1009) supports only one paperspace layout, only the active
785        paperspace layout is saved, other layouts are dismissed.
786
787        Args:
788            name: unique layout name
789            dxfattribs: additional DXF attributes for the
790                :class:`~ezdxf.entities.layout.DXFLayout` entity
791
792        Raises:
793            DXFValueError: :class:`~ezdxf.layouts.Layout` `name` already exist
794
795        """
796        if name in self.layouts:
797            raise const.DXFValueError(f"Layout '{name}' already exists.")
798        else:
799            return self.layouts.new(name, dxfattribs)
800
801    def acquire_arrow(self, name: str):
802        """ For standard AutoCAD and ezdxf arrows create block definitions if
803        required, otherwise check if block `name` exist. (internal API)
804
805        """
806        from ezdxf.render.arrows import ARROWS
807        if ARROWS.is_acad_arrow(name) or ARROWS.is_ezdxf_arrow(name):
808            ARROWS.create_block(self.blocks, name)
809        elif name not in self.blocks:
810            raise const.DXFValueError(f'Arrow block "{name}" does not exist.')
811
812    def add_image_def(self, filename: str, size_in_pixel: Tuple[int, int],
813                      name=None):
814        """ Add an image definition to the objects section.
815
816        Add an :class:`~ezdxf.entities.image.ImageDef` entity to the drawing
817        (objects section). `filename` is the image file name as relative or
818        absolute path and `size_in_pixel` is the image size in pixel as (x, y)
819        tuple. To avoid dependencies to external packages, `ezdxf` can not
820        determine the image size by itself. Returns a
821        :class:`~ezdxf.entities.image.ImageDef` entity which is needed to
822        create an image reference. `name` is the internal image name, if set to
823        ``None``, name is auto-generated.
824
825        Absolute image paths works best for AutoCAD but not really good, you
826        have to update external references manually in AutoCAD, which is not
827        possible in TrueView. If the drawing units differ from 1 meter, you
828        also have to use: :meth:`set_raster_variables`.
829
830        Args:
831            filename: image file name (absolute path works best for AutoCAD)
832            size_in_pixel: image size in pixel as (x, y) tuple
833            name: image name for internal use, None for using filename as name
834                (best for AutoCAD)
835
836        .. seealso::
837
838            :ref:`tut_image`
839
840        """
841        if 'ACAD_IMAGE_VARS' not in self.rootdict:
842            self.objects.set_raster_variables(frame=0, quality=1, units='m')
843        if name is None:
844            name = filename
845        return self.objects.add_image_def(filename, size_in_pixel, name)
846
847    def set_raster_variables(self, frame: int = 0, quality: int = 1,
848                             units: str = 'm'):
849        """
850        Set raster variables.
851
852        Args:
853            frame: ``0`` = do not show image frame; ``1`` = show image frame
854            quality: ``0`` = draft; ``1`` = high
855            units: units for inserting images. This defines the real world unit
856                for one drawing unit for the purpose of inserting and scaling
857                images with an associated resolution.
858
859                ===== ===========================
860                mm    Millimeter
861                cm    Centimeter
862                m     Meter (ezdxf default)
863                km    Kilometer
864                in    Inch
865                ft    Foot
866                yd    Yard
867                mi    Mile
868                ===== ===========================
869
870        """
871        self.objects.set_raster_variables(frame=frame, quality=quality,
872                                          units=units)
873
874    def set_wipeout_variables(self, frame=0):
875        """
876        Set wipeout variables.
877
878        Args:
879            frame: ``0`` = do not show image frame; ``1`` = show image frame
880
881        """
882        self.objects.set_wipeout_variables(frame=frame)
883        var_dict = self.rootdict.get_required_dict('AcDbVariableDictionary')
884        var_dict.set_or_add_dict_var('WIPEOUTFRAME', str(frame))
885
886    def add_underlay_def(self, filename: str, format: str = 'ext',
887                         name: str = None):
888        """ Add an :class:`~ezdxf.entities.underlay.UnderlayDef` entity to the
889        drawing (OBJECTS section).
890        `filename` is the underlay file name as relative or absolute path and
891        `format` as string (pdf, dwf, dgn).
892        The underlay definition is required to create an underlay reference.
893
894        Args:
895            filename: underlay file name
896            format: file format as string ``'pdf'|'dwf'|'dgn'`` or ``'ext'``
897                for getting file format from filename extension
898            name: pdf format = page number to display; dgn format =
899                ``'default'``; dwf: ????
900
901        .. seealso::
902
903            :ref:`tut_underlay`
904
905        """
906        if format == 'ext':
907            format = filename[-3:]
908        return self.objects.add_underlay_def(filename, format, name)
909
910    def add_xref_def(self, filename: str, name: str,
911                     flags: int = BLK_XREF | BLK_EXTERNAL):
912        """
913        Add an external reference (xref) definition to the blocks section.
914
915        Args:
916            filename: external reference filename
917            name: name of the xref block
918            flags: block flags
919
920        """
921        self.blocks.new(name=name, dxfattribs={
922            'flags': flags,
923            'xref_path': filename
924        })
925
926    def audit(self) -> 'Auditor':
927        """ Checks document integrity and fixes all fixable problems, not
928        fixable problems are stored in :attr:`Auditor.errors`.
929
930        If you are messing around with internal structures, call this method
931        before saving to be sure to export valid DXF documents, but be aware
932        this is a long running task.
933
934        """
935        from ezdxf.audit import Auditor
936        auditor = Auditor(self)
937        auditor.run()
938        return auditor
939
940    def validate(self, print_report=True) -> bool:
941        """ Simple way to run an audit process. Fixes all fixable problems,
942        return ``False`` if not fixable errors occurs, to get more information
943        about not fixable errors use :meth:`audit` method instead.
944
945        Args:
946            print_report: print report to stdout
947
948        Returns: ``True`` if no errors occurred
949
950        """
951        auditor = self.audit()
952        if len(auditor):
953            if print_report:
954                auditor.print_error_report()
955            return False
956        else:
957            return True
958
959    def set_modelspace_vport(self, height, center=(0, 0)) -> 'VPort':
960        r""" Set initial view/zoom location for the modelspace, this replaces
961        the current "\*Active" viewport configuration.
962
963        Args:
964             height: modelspace area to view
965             center: modelspace location to view in the center of the CAD
966                application window.
967
968        """
969        self.viewports.delete_config('*Active')
970        vport = cast('VPort', self.viewports.new('*Active'))
971        vport.dxf.center = center
972        vport.dxf.height = height
973        return vport
974