loader.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # Purpose: DXF structure loader and validator
  2. # Created: 25.01.2018
  3. # Copyright (c) 2018, Manfred Moitzi
  4. # License: MIT License
  5. import logging
  6. from typing import Callable, Dict, Iterable, List, Union, TYPE_CHECKING
  7. from .const import DXFStructureError
  8. from .tags import group_tags, DXFTag, Tags
  9. from .extendedtags import ExtendedTags
  10. from .validator import entity_structure_validator
  11. from ezdxf.options import options
  12. if TYPE_CHECKING: # import forward declarations
  13. from ezdxf.eztypes import EntityDB
  14. logger = logging.getLogger('ezdxf')
  15. TagProcessor = Callable[[ExtendedTags], ExtendedTags]
  16. modern_post_load_tag_processors = {} # type: Dict[str, TagProcessor]
  17. legacy_post_load_tag_processors = {} # type: Dict[str, TagProcessor]
  18. SectionDict = Dict[str, List[Union[Tags, ExtendedTags]]]
  19. def is_registered(entity: str, legacy: bool = False):
  20. # just for testing
  21. processors = legacy_post_load_tag_processors if legacy else modern_post_load_tag_processors
  22. return entity in processors
  23. def register(entity: str, legacy: bool = False) -> Callable:
  24. """
  25. Register (decorator) functions to process from DXF file loaded tags.
  26. Args:
  27. entity: DXF type like 'LINE' or 'VERTEX'
  28. legacy: use for legacy tag structure (DXF version <= AC1009) or modern tag structures
  29. """
  30. logger.debug('Register post load tag processor for DXF type: {}; legacy: {}'.format(entity, legacy))
  31. def decorator(processor: TagProcessor) -> TagProcessor:
  32. """
  33. Args:
  34. processor: function with one parameter 'tags'
  35. Returns: processor
  36. """
  37. processors = legacy_post_load_tag_processors if legacy else modern_post_load_tag_processors
  38. processors[entity] = processor
  39. return processor
  40. return decorator
  41. def load_dxf_structure(tagger: Iterable[DXFTag], ignore_missing_eof: bool = False) -> SectionDict:
  42. """
  43. Divide input tag stream from tagger into DXF structure entities. Each DXF structure entity starts with a DXF
  44. structure (0, ...) tag, and ends before the next DXF structure tag.
  45. Generated structure:
  46. each entity is a Tags() object
  47. {
  48. 'HEADER': [entity], # 1. section, HEADER section contains only the SECTION head tag
  49. 'CLASSES': [entity, entity, ...], # 2. section
  50. 'TABLES': [entity, entity, ...], # 3. section
  51. ...
  52. 'OBJECTS': [entity, entity, ...],
  53. }
  54. {
  55. 'HEADER': [(0, 'SECTION'), (2, 'HEADER'), .... ], # HEADER section contains only the SECTION head tag
  56. 'CLASSES': [[(0, 'SECTION'), (2, 'CLASSES')], [(0, 'CLASS'), ...], [(0, 'CLASS'), ...]],
  57. 'TABLES': [[(0, 'SECTION'), (2, 'TABLES')], [(0, 'TABLE'), (2, 'VPORT')], [(0, 'VPORT'), ...], ... , [(0, 'ENDTAB')]],
  58. ...
  59. 'OBJECTS': [[(0, 'SECTION'), (2, 'OBJECTS')], ...]
  60. }
  61. Args:
  62. tagger: generates DXFTag() entities from input data
  63. ignore_missing_eof: raises DXFStructureError() if False and EOF tag is not present, set to True only in tests
  64. Returns:
  65. dict of sections, each section is a list of DXF structure entities as Tags() objects
  66. """
  67. def inside_section() -> bool:
  68. if len(section):
  69. return section[0][0] == (0, 'SECTION') # first entity, first tag
  70. return False
  71. def outside_section() -> bool:
  72. if len(section):
  73. return section[0][0] != (0, 'SECTION') # first entity, first tag
  74. return True
  75. sections = {} # type: SectionDict
  76. section = [] # type: List[Tags]
  77. eof = False
  78. for entity in group_tags(tagger):
  79. tag = entity[0]
  80. if tag == (0, 'SECTION'):
  81. if inside_section():
  82. raise DXFStructureError("DXFStructureError: missing ENDSEC tag.")
  83. if len(section):
  84. logger.warning('DXF Structure Warning: found tags outside a SECTION, ignored by ezdxf.')
  85. section = [entity]
  86. elif tag == (0, 'ENDSEC'): # not collected
  87. if outside_section():
  88. raise DXFStructureError("DXFStructureError: found ENDSEC tag without previous SECTION tag.")
  89. section_header = section[0]
  90. if len(section_header) < 2 or section_header[1].code != 2:
  91. raise DXFStructureError(
  92. 'DXFStructureError: missing required section NAME tag (2, name) at start of section.')
  93. name_tag = section_header[1]
  94. sections[name_tag.value] = section
  95. section = [] # collect tags outside of sections, but ignore it
  96. elif tag == (0, 'EOF'): # not collected
  97. if eof:
  98. logger.warning('DXF Structure Warning: found more than one EOF tags.')
  99. eof = True
  100. else:
  101. section.append(entity)
  102. if inside_section():
  103. raise DXFStructureError("DXFStructureError: missing ENDSEC tag.")
  104. if not eof and not ignore_missing_eof:
  105. raise DXFStructureError('DXFStructureError: missing EOF tag.')
  106. return sections
  107. DATABASE_EXCLUDE = frozenset(['SECTION', 'ENDSEC', 'EOF', 'TABLE', 'ENDTAB', 'CLASS', 'ACDSRECORD', 'ACDSSCHEMA'])
  108. def load_dxf_entities_into_database(database: 'EntityDB', dxf_entities: List[Tags]) -> Iterable[ExtendedTags]:
  109. check_tag_structure = options.check_entity_tag_structures
  110. for entity in dxf_entities:
  111. if len(entity) == 0:
  112. raise DXFStructureError('Invalid empty DXF entity.')
  113. code, dxftype = entity[0]
  114. if code != 0:
  115. raise DXFStructureError('Invalid first tag in DXF entity, group code={} .'.format(code))
  116. if dxftype not in DATABASE_EXCLUDE:
  117. if check_tag_structure:
  118. entity = entity_structure_validator(entity)
  119. entity = ExtendedTags(entity)
  120. database.add_tags(entity)
  121. yield entity
  122. def fill_database(database: 'EntityDB', sections: SectionDict, dxfversion: str = 'AC1009') -> None:
  123. post_processors = legacy_post_load_tag_processors if dxfversion <= 'AC1009' else modern_post_load_tag_processors
  124. for name in ['TABLES', 'ENTITIES', 'BLOCKS', 'OBJECTS']:
  125. if name in sections:
  126. section = sections[name]
  127. # entities stored in the database are converted from Tags() to ExtendedTags()
  128. for index, entity in enumerate(load_dxf_entities_into_database(database, section)):
  129. # entities not stored in database are still Tags() e.g. CLASS
  130. processor = post_processors.get(entity.dxftype())
  131. if processor:
  132. processor(entity)
  133. section[index] = entity