123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- # Purpose: repair/setup required DXF structures in existing DXF files (created by other DXF libs)
- # Created: 05.03.2016
- # Copyright (C) 2016, Manfred Moitzi
- # License: MIT License
- # --------------------------------------------------- #
- # Welcome to the place, where it gets dirty and ugly! #
- # --------------------------------------------------- #
- from typing import TYPE_CHECKING, Iterable, Optional, List
- from functools import partial
- import logging
- from .extendedtags import ExtendedTags
- from .tags import DXFTag, Tags
- from .const import DXFInternalEzdxfError, DXFValueError, DXFKeyError, SUBCLASS_MARKER
- logger = logging.getLogger('ezdxf')
- if TYPE_CHECKING: # import forward declarations
- from ezdxf.eztypes import Drawing
- def setup_layouts(dwg: 'Drawing'):
- layout_dict = dwg.rootdict.get_required_dict('ACAD_LAYOUT')
- if 'Model' not in layout_dict: # do it only if model space is not defined
- setup_model_space(dwg)
- setup_paper_space(dwg)
- def setup_model_space(dwg: 'Drawing'):
- setup_layout_space(dwg, 'Model', '*Model_Space', _MODEL_SPACE_LAYOUT_TPL)
- def setup_paper_space(dwg: 'Drawing'):
- setup_layout_space(dwg, 'Layout1', '*Paper_Space', _PAPER_SPACE_LAYOUT_TPL)
- def setup_layout_space(dwg: 'Drawing', layout_name: str, block_name: str, tag_string: str):
- # This is just necessary for existing DXF drawings without properly setup management structures.
- # Layout structure is not initialized at this runtime phase
- logger.info('creating LAYOUT structure for: {}'.format(layout_name))
- def get_block_record_by_alt_names(names: Iterable[str]):
- for name in names:
- try:
- brecord = dwg.block_records.get(name)
- except DXFValueError:
- pass
- else:
- return brecord
- raise DXFKeyError
- layout_dict = dwg.rootdict.get_required_dict('ACAD_LAYOUT')
- if layout_name in layout_dict:
- return
- try:
- block_record = get_block_record_by_alt_names((block_name, block_name.upper()))
- except DXFKeyError:
- raise NotImplementedError("'%s' block record setup not implemented, send an email to "
- "<ezdxf@mozman.at> with your DXF file." % block_name)
- real_block_name = block_record.dxf.name # can be *Model_Space or *MODEL_SPACE
- block_record_handle = block_record.dxf.handle
- logger.debug('found {}: {}'.format(str(block_record), real_block_name))
- try:
- block_layout = dwg.blocks.get(real_block_name)
- logger.debug('found {}: {}'.format(str(block_layout.block), real_block_name))
- except DXFKeyError:
- logger.debug('expected BLOCK: {} not found.'.format(real_block_name))
- raise NotImplementedError("'%s' block setup not implemented, send an email to "
- "<ezdxf@mozman.at> with your DXF file." % real_block_name)
- else:
- block_layout.set_block_record_handle(block_record_handle) # grant valid linking
- layout_handle = create_layout_tags(dwg, block_record_handle, owner=layout_dict.dxf.handle, tag_string=tag_string)
- logger.debug('creating entry in ACAD_LAYOUT dictionary for {}'.format(layout_name))
- layout_dict[layout_name] = layout_handle # insert layout into the layout management table
- block_record.dxf.layout = layout_handle # link model space block record to layout
- # rename block to standard format (*Model_Space or *Paper_Space)
- if real_block_name != block_name:
- logger.debug('renaming BLOCK form {} to {}'.format(block_name, real_block_name))
- dwg.blocks.rename_block(real_block_name, block_name)
- def create_layout_tags(dwg: 'Drawing', block_record_handle: str, owner: str, tag_string: str):
- # Problem: ezdxf was not designed to handle the absence of model/paper space LAYOUT entities
- # Layout structure is not initialized at this runtime phase
- logger.debug('creating LAYOUT entity for BLOCK_RECORD(#{})'.format(block_record_handle))
- object_section = dwg.objects
- entitydb = dwg.entitydb
- tags = ExtendedTags.from_text(tag_string)
- layout_handle = entitydb.get_unique_handle() # create new unique handle
- tags.replace_handle(layout_handle) # set entity handle
- entitydb.add_tags(tags) # add layout entity to entity database
- object_section.add_handle(layout_handle) # add layout entity to objects section
- tags.noclass.set_first(DXFTag(330, owner)) # set owner tag
- acdblayout = tags.get_subclass('AcDbLayout')
- acdblayout.set_first(DXFTag(330, block_record_handle)) # link to block record
- return layout_handle
- def upgrade_to_ac1015(dwg: 'Drawing'):
- """
- Upgrade DXF versions AC1012 and AC1014 to AC1015.
- """
- def upgrade_layout_table():
- if 'ACAD_LAYOUT' in dwg.rootdict:
- setup_model_space(dwg) # setup layout entity and link to proper block and block_record entities
- setup_paper_space(dwg) # setup layout entity and link to proper block and block_record entities
- else:
- raise DXFInternalEzdxfError("Table ACAD_LAYOUT should already exist in root dict.")
- def upgrade_layer_table():
- logger.debug('upgrading LAYERS table')
- try:
- plot_style_name_handle = dwg.rootdict.get('ACAD_PLOTSTYLENAME') # DXFDictionaryWithDefault
- except DXFKeyError:
- raise DXFInternalEzdxfError("Table ACAD_PLOTSTYLENAME should already exist in root dict.")
- set_plot_style_name_in_layers(plot_style_name_handle)
- try: # do not plot DEFPOINTS layer or AutoCAD is yelling
- defpoints_layer = dwg.layers.get('DEFPOINTS')
- except DXFValueError:
- pass
- else:
- defpoints_layer.dxf.plot = 0
- def set_plot_style_name_in_layers(plot_style_name_handle):
- logger.debug('setting layers "plot_style_name" attribute')
- for layer in dwg.layers:
- layer.dxf.plot_style_name = plot_style_name_handle
- def upgrade_dim_style_table():
- logger.debug('upgrading DIMSTYLES table')
- dim_styles = dwg.dimstyles
- header = dim_styles._table_header
- dim_style_table = Tags([
- DXFTag(100, 'AcDbDimStyleTable'),
- DXFTag(71, len(dim_styles))
- ])
- for entry in dim_styles:
- dim_style_table.append(DXFTag(340, entry.dxf.handle))
- header.subclasses.append(dim_style_table)
- def upgrade_objects():
- logger.debug('upgrading ACDBPLACEHOLDER entities in the OBJECTS section')
- upgrade_acdbplaceholder(dwg.objects.query('ACDBPLACEHOLDER'))
- def upgrade_acdbplaceholder(entities):
- for entity in entities:
- entity.tags.subclasses = entity.tags.subclasses[0:1] # remove subclass AcDbPlaceHolder
- # calling order is important!
- logger.info('Upgrading drawing to DXF R2000.')
- upgrade_layout_table()
- upgrade_layer_table()
- upgrade_dim_style_table()
- upgrade_objects()
- logger.debug('Setting DXF version to AC1015.')
- dwg.dxfversion = 'AC1015'
- dwg.header['$ACADVER'] = 'AC1015'
- def upgrade_to_ac1009(dwg: 'Drawing'):
- """
- Upgrade DXF versions prior to AC1009 (R12) to AC1009.
- """
- logger.info('Upgrading drawing to DXF R12.')
- logger.debug('Setting DXF version to AC1009.')
- dwg.dxfversion = 'AC1009'
- dwg.header['$ACADVER'] = 'AC1009'
- # as far I know, nothing else to do
- def cleanup_r12(dwg: 'Drawing'):
- """
- Remove unsupported sections and tables, repair tag structure.
- Args:
- dwg: Drawing() object
- """
- logger.info('Cleanup DXF R12 drawing.')
- if dwg.dxfversion > 'AC1009':
- return
- for section_name in ('CLASSES', 'OBJECTS', 'THUMBNAILIMAGE', 'ACDSDATA'): # unsupported sections for DXF R12
- if section_name in dwg.sections:
- logger.debug('Deleting {} section.'.format(section_name))
- dwg.sections.delete_section(section_name)
- if 'BLOCK_RECORDS' in dwg.sections.tables:
- logger.debug('Deleting BLOCK_RECORDS table.')
- del dwg.sections.tables['BLOCK_RECORDS']
- def filter_subclass_marker(tagger: Iterable[DXFTag]) -> Iterable[DXFTag]:
- """
- Filter subclass marker from malformed DXF R12 files. (like from Leica Disto Units)
- Subclass markers in R12 files, creates subclasses in ExtendedTags(), which does not work with the DXF R12 attribute
- definitions. Other unsupported tags are not problematic, they are just ignored.
- Args:
- tagger: low level tagger
- """
- found = 0
- for tag in tagger:
- if tag.code == SUBCLASS_MARKER and tag.value.startswith('AcDb'):
- found += 1
- else:
- yield tag
- if found:
- logger.debug('Filtered {} SUBCLASS marker from DXF R12 tag stream.'.format(found))
- def tag_reorder_layer(tagger: Iterable[DXFTag]) -> Iterable[DXFTag]:
- """
- Reorder coordinates of legacy DXF Entities, for now only LINE.
- Args:
- tagger: low level tagger
- """
- logger.debug('Reordering coordinate tags for LINE entity.')
- collector = None # type: Optional[List]
- for tag in tagger:
- if tag.code == 0:
- if collector is not None: # stop collecting if inside of an supported entity
- entity = collector[0].value
- yield from COORDINATE_FIXING_TOOLBOX[entity](collector)
- collector = None
- if tag.value in COORDINATE_FIXING_TOOLBOX:
- collector = [tag]
- tag = None # do not yield collected tag yet
- else: # tag.code != 0
- if collector is not None:
- collector.append(tag)
- tag = None # do not yield collected tag yet
- if tag is not None:
- yield tag
- def fix_coordinate_order(tags, codes=(10, 11)):
- def extend_codes():
- for code in codes:
- yield code # x tag
- yield code + 10 # y tag
- yield code + 20 # z tag
- def get_coords(code):
- # if x or y coordinate is missing, it is a DXFStructureError
- # but here is not the location to validate the DXF structure
- try:
- yield coordinates[code]
- except KeyError:
- pass
- try:
- yield coordinates[code + 10]
- except KeyError:
- pass
- try:
- yield coordinates[code + 20]
- except KeyError:
- pass
- coordinate_codes = frozenset(extend_codes())
- coordinates = {}
- remaining_tags = []
- insert_pos = None
- for tag in tags:
- # separate tags
- if tag.code in coordinate_codes:
- coordinates[tag.code] = tag
- if insert_pos is None:
- insert_pos = tags.index(tag)
- else:
- remaining_tags.append(tag)
- if len(coordinates) == 0:
- # no coordinates found, this is probably a DXFStructureError,
- # but here is not the location to validate the DXF structure,
- # just do nothing.
- return tags
- ordered_coords = []
- for code in codes:
- ordered_coords.extend(get_coords(code))
- remaining_tags[insert_pos:insert_pos] = ordered_coords
- return remaining_tags
- COORDINATE_FIXING_TOOLBOX = {
- 'LINE': partial(fix_coordinate_order, codes=(10, 11)),
- }
- def fix_classes(dwg):
- def remove_group_code_91():
- logger.debug('Deleting group code 91 tags from CLASS entities for DXF Versions prior AC1018.')
- for cls in dwg.sections.classes:
- xtags = cls.tags
- xtags.noclass.remove_tags((91,))
- if dwg.dxfversion <= 'AC1009': # DXF R12 and prior has no CLASSES
- return
- if dwg.dxfversion < 'AC1018':
- # remove group code 91, which is not supported prior to AC1018
- remove_group_code_91()
- _MODEL_SPACE_LAYOUT_TPL = """ 0
- LAYOUT
- 5
- 0
- 330
- 0
- 100
- AcDbPlotSettings
- 1
- 2
- DWFx ePlot (XPS Compatible).pc3
- 4
- ANSI_A_(8.50_x_11.00_Inches)
- 6
- 40
- 5.8
- 41
- 17.8
- 42
- 5.8
- 43
- 17.8
- 44
- 215.9
- 45
- 279.4
- 46
- 0.0
- 47
- 0.0
- 48
- 0.0
- 49
- 0.0
- 140
- 0.0
- 141
- 0.0
- 142
- 1.0
- 143
- 14.53
- 70
- 11952
- 72
- 0
- 73
- 1
- 74
- 0
- 7
- 75
- 0
- 147
- 0.069
- 148
- 114.98
- 149
- 300.29
- 100
- AcDbLayout
- 1
- Model
- 70
- 1
- 71
- 0
- 10
- 0.0
- 20
- 0.0
- 11
- 12.0
- 21
- 9.0
- 12
- 0.0
- 22
- 0.0
- 32
- 0.0
- 14
- 0.0
- 24
- 0.0
- 34
- 0.0
- 15
- 0.0
- 25
- 0.0
- 35
- 0.0
- 146
- 0.0
- 13
- 0.0
- 23
- 0.0
- 33
- 0.0
- 16
- 1.0
- 26
- 0.0
- 36
- 0.0
- 17
- 0.0
- 27
- 1.0
- 37
- 0.0
- 76
- 0
- 330
- 0
- """
- _PAPER_SPACE_LAYOUT_TPL = """ 0
- LAYOUT
- 5
- DEAD
- 330
- DEAD
- 100
- AcDbPlotSettings
- 1
- 2
- DWFx ePlot (XPS Compatible).pc3
- 4
- ANSI_A_(8.50_x_11.00_Inches)
- 6
- 40
- 5.8
- 41
- 17.8
- 42
- 5.8
- 43
- 17.8
- 44
- 215.9
- 45
- 279.4
- 46
- 0.0
- 47
- 0.0
- 48
- 0.0
- 49
- 0.0
- 140
- 0.0
- 141
- 0.0
- 142
- 1.0
- 143
- 1.0
- 70
- 688
- 72
- 0
- 73
- 1
- 74
- 5
- 7
- acad.ctb
- 75
- 16
- 147
- 1.0
- 148
- 0.0
- 149
- 0.0
- 100
- AcDbLayout
- 1
- Layout1
- 70
- 1
- 71
- 1
- 10
- -0.7
- 20
- -0.23
- 11
- 10.3
- 21
- 8.27
- 12
- 0.0
- 22
- 0.0
- 32
- 0.0
- 14
- 0.63
- 24
- 0.8
- 34
- 0.0
- 15
- 9.0
- 25
- 7.2
- 35
- 0.0
- 146
- 0.0
- 13
- 0.0
- 23
- 0.0
- 33
- 0.0
- 16
- 1.0
- 26
- 0.0
- 36
- 0.0
- 17
- 0.0
- 27
- 1.0
- 37
- 0.0
- 76
- 0
- 330
- DEAD
- """
|