123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- # Purpose: blocks section
- # Created: 14.03.2011
- # Copyright (c) 2011-2018, Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Iterable, Union, Iterator, Sequence
- import logging
- from ezdxf.lldxf.const import DXFStructureError, DXFAttributeError, DXFBlockInUseError
- from ezdxf.lldxf import const
- from ezdxf.lldxf.extendedtags import get_xtags_linker
- logger = logging.getLogger('ezdxf')
- if TYPE_CHECKING:
- from ezdxf.eztypes import Drawing, BlockLayout, ExtendedTags, DXFFactoryType, EntityDB, TagWriter
- class BlocksSection:
- """
- Manages BLOCK definitions in a dict(). Since v0.8.5 ezdxf uses a lower case key. 'Test' == 'TEST', to behave
- like AutoCAD.
- """
- name = 'BLOCKS'
- def __init__(self, entities: Iterable['ExtendedTags'], drawing: 'Drawing'):
- # Mapping of BlockLayouts, for dict() order of blocks is random,
- # if turns out later, that blocks order is important: use an OrderedDict().
- self._block_layouts = dict()
- self.drawing = drawing
- if entities is not None:
- self._build(iter(entities))
- self._anonymous_block_counter = 0
- def __len__(self):
- return len(self._block_layouts)
- @staticmethod
- def key(entity: Union[str, 'BlockLayout']) -> str:
- if not isinstance(entity, str):
- entity = entity.name
- return entity.lower() # block key is lower case
- @property
- def entitydb(self) -> 'EntityDB':
- return self.drawing.entitydb
- @property
- def dxffactory(self) -> 'DXFFactoryType':
- return self.drawing.dxffactory
- def _build(self, entities: Iterator['ExtendedTags']) -> None:
- def build_block_layout(handles: Sequence[str]) -> 'BlockLayout':
- block = self.dxffactory.new_block_layout(
- block_handle=handles[0],
- endblk_handle=handles[-1],
- )
- for handle in handles[1:-1]:
- block.add_handle(handle)
- return block
- def link_entities() -> Iterable['ExtendedTags']:
- linked_tags = get_xtags_linker()
- for entity in entities:
- if not linked_tags(entity): # don't store linked entities (VERTEX, ATTRIB, SEQEND) in block layout
- yield entity
- section_head = next(entities)
- if section_head[0] != (0, 'SECTION') or section_head[1] != (2, 'BLOCKS'):
- raise DXFStructureError("Critical structure error in BLOCKS section.")
- handles = []
- for xtags in link_entities():
- handles.append(xtags.get_handle())
- if xtags.dxftype() == 'ENDBLK':
- block_layout = build_block_layout(handles)
- try:
- name = block_layout.name
- except DXFAttributeError:
- raise
- if block_layout.name in self:
- logger.warning(
- 'Warning! Multiple block definitions with name "{}", replacing previous definition'.format(
- block_layout.name))
- self.add(block_layout)
- handles = []
- def add(self, block_layout: 'BlockLayout') -> None:
- """
- Add or replace a block object.
- Args:
- block_layout: BlockLayout() object
- """
- self._block_layouts[self.key(block_layout.name)] = block_layout
- def __iter__(self) -> Iterable['BlockLayout']:
- return iter(self._block_layouts.values())
- def __contains__(self, name: str) -> bool:
- return self.key(name) in self._block_layouts
- def __getitem__(self, name: str) -> 'BlockLayout':
- return self._block_layouts[self.key(name)]
- def __delitem__(self, name: str) -> None:
- del self._block_layouts[self.key(name)]
- def get(self, name: str, default=None) -> 'BlockLayout':
- try:
- return self.__getitem__(name)
- except KeyError: # internal exception
- return default
- def new(self, name: str, base_point: Sequence[float] = (0, 0), dxfattribs: dict = None) -> 'BlockLayout':
- """
- Create a new named block.
- """
- dxfattribs = dxfattribs or {}
- dxfattribs['name'] = name
- dxfattribs['name2'] = name
- dxfattribs['base_point'] = base_point
- head = self.dxffactory.create_db_entry('BLOCK', dxfattribs)
- tail = self.dxffactory.create_db_entry('ENDBLK', {})
- block_layout = self.dxffactory.new_block_layout(head.dxf.handle, tail.dxf.handle)
- self.dxffactory.create_block_entry_in_block_records_table(block_layout)
- self.add(block_layout)
- return block_layout
- def new_anonymous_block(self, type_char: str = 'U', base_point: Sequence[float] = (0, 0)) -> 'BlockLayout':
- blockname = self.anonymous_blockname(type_char)
- block = self.new(blockname, base_point, {'flags': const.BLK_ANONYMOUS})
- return block
- def anonymous_blockname(self, type_char: str) -> str:
- """
- Create name for an anonymous block.
- Args:
- type_char: letter
- U = *U### anonymous blocks
- E = *E### anonymous non-uniformly scaled blocks
- X = *X### anonymous hatches
- D = *D### anonymous dimensions
- A = *A### anonymous groups
- T = *T### anonymous ACAD_TABLE content
- """
- while True:
- self._anonymous_block_counter += 1
- blockname = "*%s%d" % (type_char, self._anonymous_block_counter)
- if not self.__contains__(blockname):
- return blockname
- def rename_block(self, old_name: str, new_name: str) -> None:
- """
- Renames the block and the associated block record.
- """
- block_layout = self.get(old_name) # block key is lower case
- block_layout.name = new_name
- if self.drawing.dxfversion > 'AC1009':
- block_record = self.drawing.block_records.get(old_name)
- block_record.dxf.name = new_name
- self.__delitem__(old_name)
- self.add(block_layout) # add new dict entry
- def delete_block(self, name: str, safe: bool = True) -> None:
- """
- Delete block. If save is True, check if block is still referenced.
- Args:
- name: block name (case insensitive)
- safe: check if block is still referenced
- Raises:
- DXFKeyError() if block not exists
- DXFValueError() if block is still referenced, and save is True
- """
- if safe:
- block_refs = self.drawing.query("INSERT[name=='{}']i".format(name)) # ignore case
- if len(block_refs):
- raise DXFBlockInUseError(
- 'Block "{}" is still in use and can not deleted. (Hint: block name is case insensitive!)'.format(
- name))
- block_layout = self[name]
- block_layout.destroy()
- self.__delitem__(name)
- def delete_all_blocks(self, safe: bool = True) -> None:
- """
- Delete all blocks except layout blocks (model space or paper space).
- Args:
- safe: check if block is still referenced and ignore them if so
- """
- if safe:
- # block names are case insensitive
- references = set(entity.dxf.name.lower() for entity in self.drawing.query('INSERT'))
- def is_save(name: str) -> bool:
- return name.lower() not in references if safe else True
- # do not delete blocks defined for layouts
- if self.drawing.dxfversion > 'AC1009':
- layout_keys = set(layout.layout_key for layout in self.drawing.layouts)
- for block in list(self):
- name = block.name
- if block.block_record_handle not in layout_keys and is_save(name):
- # safety check is already done
- self.delete_block(name, safe=False)
- else:
- for block_name in list(self._block_layouts.keys()):
- if block_name not in ('$model_space', '$paper_space') and is_save(block_name):
- # safety check is already done
- self.delete_block(block_name, safe=False)
- def write(self, tagwriter: 'TagWriter') -> None:
- tagwriter.write_str(" 0\nSECTION\n 2\nBLOCKS\n")
- for block in self._block_layouts.values():
- block.write(tagwriter)
- tagwriter.write_tag2(0, "ENDSEC")
- def new_layout_block(self) -> 'BlockLayout':
- def block_name(_count):
- return "*Paper_Space%d" % _count
- count = 0
- while block_name(count) in self:
- count += 1
- block_layout = self.new(block_name(count))
- return block_layout
|