1# Copyright (c) 2019-2020, Manfred Moitzi
2# License: MIT License
3from typing import TYPE_CHECKING
4
5if TYPE_CHECKING:
6    from ezdxf.eztypes import Drawing, DXFEntity, ExtendedTags
7
8__all__ = [
9    'register_entity', 'ENTITY_CLASSES', 'replace_entity',
10    'new', 'cls', 'is_bound', 'create_db_entry', 'load', 'bind'
11]
12# Stores all registered classes:
13ENTITY_CLASSES = {}
14# use @set_default_class to register the default entity class:
15DEFAULT_CLASS = None
16
17
18def set_default_class(cls):
19    global DEFAULT_CLASS
20    DEFAULT_CLASS = cls
21    return cls
22
23
24def replace_entity(cls):
25    name = cls.DXFTYPE
26    ENTITY_CLASSES[name] = cls
27    return cls
28
29
30def register_entity(cls):
31    name = cls.DXFTYPE
32    if name in ENTITY_CLASSES:
33        raise TypeError(f'Double registration for DXF type {name}.')
34    ENTITY_CLASSES[name] = cls
35    return cls
36
37
38def new(dxftype: str, dxfattribs: dict = None,
39        doc: 'Drawing' = None) -> 'DXFEntity':
40    """ Create a new entity, does not require an instantiated DXF document. """
41    entity = cls(dxftype).new(
42        handle=None,
43        owner=None,
44        dxfattribs=dxfattribs,
45        doc=doc,
46    )
47    return entity.cast() if hasattr(entity, 'cast') else entity
48
49
50def create_db_entry(dxftype, dxfattribs: dict, doc: 'Drawing') -> 'DXFEntity':
51    entity = new(dxftype=dxftype, dxfattribs=dxfattribs)
52    bind(entity, doc)
53    return entity
54
55
56def load(tags: 'ExtendedTags') -> 'DXFEntity':
57    entity = cls(tags.dxftype()).load(tags)
58    return entity.cast() if hasattr(entity, 'cast') else entity
59
60
61def cls(dxftype: str) -> 'DXFEntity':
62    """ Returns registered class for `dxftype`. """
63    return ENTITY_CLASSES.get(dxftype, DEFAULT_CLASS)
64
65
66def bind(entity: 'DXFEntity', doc: 'Drawing') -> None:
67    """ Bind `entity` to the DXF document `doc`.
68
69    The bind process stores the DXF `entity` in the entity database of the DXF
70    document.
71
72    """
73    assert entity.is_alive, 'Can not bind destroyed entity.'
74    assert doc.entitydb is not None, 'Missing entity database.'
75    entity.doc = doc
76    doc.entitydb.add(entity)
77
78    # Do not call the post_bind_hook() while loading from external sources,
79    # not all entities and resources are loaded at this point of time!
80    if not doc.is_loading:
81        entity.post_bind_hook()
82
83
84def unbind(entity: 'DXFEntity'):
85    """ Unbind `entity` from document and layout, but does not destroy the
86    entity.
87
88    Turns `entity` into a virtual entity: no handle, no owner, no document.
89    """
90    if entity.is_alive and not entity.is_virtual:
91        doc = entity.doc
92        if entity.dxf.owner is not None:
93            try:
94                layout = doc.layouts.get_layout_for_entity(entity)
95            except KeyError:
96                pass
97            else:
98                layout.unlink_entity(entity)
99
100        process_sub_entities = getattr(entity, 'process_sub_entities', None)
101        if process_sub_entities:
102            process_sub_entities(lambda e: unbind(e))
103
104        doc.entitydb.discard(entity)
105        entity.doc = None
106
107
108def is_bound(entity: 'DXFEntity', doc: 'Drawing') -> bool:
109    """ Returns ``True`` if `entity`is bound to DXF document `doc`.
110    """
111    if not entity.is_alive:
112        return False
113    if entity.is_virtual or entity.doc is not doc:
114        return False
115    assert doc.entitydb, 'Missing entity database.'
116    return entity.dxf.handle in doc.entitydb
117