123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- # Purpose: DXF structure loader and validator
- # Created: 25.01.2018
- # Copyright (c) 2018, Manfred Moitzi
- # License: MIT License
- import logging
- from typing import Callable, Dict, Iterable, List, Union, TYPE_CHECKING
- from .const import DXFStructureError
- from .tags import group_tags, DXFTag, Tags
- from .extendedtags import ExtendedTags
- from .validator import entity_structure_validator
- from ezdxf.options import options
- if TYPE_CHECKING: # import forward declarations
- from ezdxf.eztypes import EntityDB
- logger = logging.getLogger('ezdxf')
- TagProcessor = Callable[[ExtendedTags], ExtendedTags]
- modern_post_load_tag_processors = {} # type: Dict[str, TagProcessor]
- legacy_post_load_tag_processors = {} # type: Dict[str, TagProcessor]
- SectionDict = Dict[str, List[Union[Tags, ExtendedTags]]]
- def is_registered(entity: str, legacy: bool = False):
- # just for testing
- processors = legacy_post_load_tag_processors if legacy else modern_post_load_tag_processors
- return entity in processors
- def register(entity: str, legacy: bool = False) -> Callable:
- """
- Register (decorator) functions to process from DXF file loaded tags.
- Args:
- entity: DXF type like 'LINE' or 'VERTEX'
- legacy: use for legacy tag structure (DXF version <= AC1009) or modern tag structures
- """
- logger.debug('Register post load tag processor for DXF type: {}; legacy: {}'.format(entity, legacy))
- def decorator(processor: TagProcessor) -> TagProcessor:
- """
- Args:
- processor: function with one parameter 'tags'
- Returns: processor
- """
- processors = legacy_post_load_tag_processors if legacy else modern_post_load_tag_processors
- processors[entity] = processor
- return processor
- return decorator
- def load_dxf_structure(tagger: Iterable[DXFTag], ignore_missing_eof: bool = False) -> SectionDict:
- """
- Divide input tag stream from tagger into DXF structure entities. Each DXF structure entity starts with a DXF
- structure (0, ...) tag, and ends before the next DXF structure tag.
- Generated structure:
- each entity is a Tags() object
- {
- 'HEADER': [entity], # 1. section, HEADER section contains only the SECTION head tag
- 'CLASSES': [entity, entity, ...], # 2. section
- 'TABLES': [entity, entity, ...], # 3. section
- ...
- 'OBJECTS': [entity, entity, ...],
- }
- {
- 'HEADER': [(0, 'SECTION'), (2, 'HEADER'), .... ], # HEADER section contains only the SECTION head tag
- 'CLASSES': [[(0, 'SECTION'), (2, 'CLASSES')], [(0, 'CLASS'), ...], [(0, 'CLASS'), ...]],
- 'TABLES': [[(0, 'SECTION'), (2, 'TABLES')], [(0, 'TABLE'), (2, 'VPORT')], [(0, 'VPORT'), ...], ... , [(0, 'ENDTAB')]],
- ...
- 'OBJECTS': [[(0, 'SECTION'), (2, 'OBJECTS')], ...]
- }
- Args:
- tagger: generates DXFTag() entities from input data
- ignore_missing_eof: raises DXFStructureError() if False and EOF tag is not present, set to True only in tests
- Returns:
- dict of sections, each section is a list of DXF structure entities as Tags() objects
- """
- def inside_section() -> bool:
- if len(section):
- return section[0][0] == (0, 'SECTION') # first entity, first tag
- return False
- def outside_section() -> bool:
- if len(section):
- return section[0][0] != (0, 'SECTION') # first entity, first tag
- return True
- sections = {} # type: SectionDict
- section = [] # type: List[Tags]
- eof = False
- for entity in group_tags(tagger):
- tag = entity[0]
- if tag == (0, 'SECTION'):
- if inside_section():
- raise DXFStructureError("DXFStructureError: missing ENDSEC tag.")
- if len(section):
- logger.warning('DXF Structure Warning: found tags outside a SECTION, ignored by ezdxf.')
- section = [entity]
- elif tag == (0, 'ENDSEC'): # not collected
- if outside_section():
- raise DXFStructureError("DXFStructureError: found ENDSEC tag without previous SECTION tag.")
- section_header = section[0]
- if len(section_header) < 2 or section_header[1].code != 2:
- raise DXFStructureError(
- 'DXFStructureError: missing required section NAME tag (2, name) at start of section.')
- name_tag = section_header[1]
- sections[name_tag.value] = section
- section = [] # collect tags outside of sections, but ignore it
- elif tag == (0, 'EOF'): # not collected
- if eof:
- logger.warning('DXF Structure Warning: found more than one EOF tags.')
- eof = True
- else:
- section.append(entity)
- if inside_section():
- raise DXFStructureError("DXFStructureError: missing ENDSEC tag.")
- if not eof and not ignore_missing_eof:
- raise DXFStructureError('DXFStructureError: missing EOF tag.')
- return sections
- DATABASE_EXCLUDE = frozenset(['SECTION', 'ENDSEC', 'EOF', 'TABLE', 'ENDTAB', 'CLASS', 'ACDSRECORD', 'ACDSSCHEMA'])
- def load_dxf_entities_into_database(database: 'EntityDB', dxf_entities: List[Tags]) -> Iterable[ExtendedTags]:
- check_tag_structure = options.check_entity_tag_structures
- for entity in dxf_entities:
- if len(entity) == 0:
- raise DXFStructureError('Invalid empty DXF entity.')
- code, dxftype = entity[0]
- if code != 0:
- raise DXFStructureError('Invalid first tag in DXF entity, group code={} .'.format(code))
- if dxftype not in DATABASE_EXCLUDE:
- if check_tag_structure:
- entity = entity_structure_validator(entity)
- entity = ExtendedTags(entity)
- database.add_tags(entity)
- yield entity
- def fill_database(database: 'EntityDB', sections: SectionDict, dxfversion: str = 'AC1009') -> None:
- post_processors = legacy_post_load_tag_processors if dxfversion <= 'AC1009' else modern_post_load_tag_processors
- for name in ['TABLES', 'ENTITIES', 'BLOCKS', 'OBJECTS']:
- if name in sections:
- section = sections[name]
- # entities stored in the database are converted from Tags() to ExtendedTags()
- for index, entity in enumerate(load_dxf_entities_into_database(database, section)):
- # entities not stored in database are still Tags() e.g. CLASS
- processor = post_processors.get(entity.dxftype())
- if processor:
- processor(entity)
- section[index] = entity
|