# Created: 25.03.2011 # Copyright (c) 2011-2018, Manfred Moitzi # License: MIT License from typing import TYPE_CHECKING, Iterable, Tuple, Union, Optional, cast from ezdxf.lldxf.const import DXFValueError, DXFKeyError from .graphics import GraphicEntity, ExtendedTags, make_attribs, DXFAttr, XType if TYPE_CHECKING: from ezdxf.eztypes import Attrib, Attdef, Vertex _INSERT_TPL = """0 INSERT 5 0 8 0 2 BLOCKNAME 10 0.0 20 0.0 30 0.0 41 1.0 42 1.0 43 1.0 50 0.0 """ # IMPORTANT: Bug in AutoCAD 2010 # attribsfollow = 0, for NO attribsfollow, does not work with ACAD 2010 # if no attribs attached to the INSERT entity, omit attribsfollow tag class Insert(GraphicEntity): __slots__ = () TEMPLATE = ExtendedTags.from_text(_INSERT_TPL) DXFATTRIBS = make_attribs({ 'attribs_follow': DXFAttr(66, default=0), 'name': DXFAttr(2), 'insert': DXFAttr(10, xtype=XType.any_point), 'xscale': DXFAttr(41, default=1.0), 'yscale': DXFAttr(42, default=1.0), 'zscale': DXFAttr(43, default=1.0), 'rotation': DXFAttr(50, default=0.0), 'column_count': DXFAttr(70, default=1), 'row_count': DXFAttr(71, default=1), 'column_spacing': DXFAttr(44, default=0.0), 'row_spacing': DXFAttr(45, default=0.0), }) def attribs(self) -> Iterable['Attrib']: """ Iterate over all appended ATTRIB entities, yields Attrib() objects. """ if self.dxf.attribs_follow == 0: return dxffactory = self.dxffactory handle = self.tags.link while handle is not None: entity = dxffactory.wrap_handle(handle) next_entity = entity.tags.link if next_entity is None: # found SeqEnd return else: yield entity handle = next_entity def place(self, insert: 'Vertex' = None, scale: Tuple[float, float, float] = None, rotation: float = None) -> 'Insert': """ Set placing attributes of the INSERT entity. Args: insert: insert position as (x, y [,z]) tuple scale: (scale_x, scale_y, scale_z) tuple rotation (float): rotation angle in degrees Returns: Insert object (fluent interface) """ if insert is not None: self.dxf.insert = insert if scale is not None: if len(scale) != 3: raise DXFValueError("Parameter scale has to be a (x, y, z)-tuple.") x, y, z = scale self.dxf.xscale = x self.dxf.yscale = y self.dxf.zscale = z if rotation is not None: self.dxf.rotation = rotation return self def grid(self, size: Tuple[int, int] = (1, 1), spacing: Tuple[float, float] = (1, 1)) -> 'Insert': """ Set grid placing attributes of the INSERT entity. Args: size: grid size as (row_count, column_count) tuple spacing: distance between placing as (row_spacing, column_spacing) tuple Returns: Insert object (fluent interface) """ if len(size) != 2: raise DXFValueError("Parameter size has to be a (row_count, column_count)-tuple.") if len(spacing) != 2: raise DXFValueError("Parameter spacing has to be a (row_spacing, column_spacing)-tuple.") self.dxf.row_count = size[0] self.dxf.column_count = size[1] self.dxf.row_spacing = spacing[0] self.dxf.column_spacing = spacing[1] return self def get_attrib(self, tag: str, search_const: bool = False) -> Optional[Union['Attrib', 'Attdef']]: """ Get attached ATTRIB entity by *tag*. Args: tag: tag name search_const: search also const ATTDEF entities Returns: Attrib or Attdef object """ for attrib in self.attribs(): if tag == attrib.dxf.tag: return attrib if search_const and self.drawing is not None: block = self.drawing.blocks[self.dxf.name] # raises KeyError() if not found for attdef in block.get_const_attdefs(): if tag == attdef.dxf.tag: return attdef return None def get_attrib_text(self, tag: str, default: str = None, search_const: bool = False) -> str: """ Get content text of attached ATTRIB entity *tag*. Args: tag: tag name default: default value if tag is absent search_const: search also const ATTDEF entities Returns: content text as str """ attrib = self.get_attrib(tag, search_const) if attrib is None: return default return attrib.dxf.text def has_attrib(self, tag: str, search_const: bool = False) -> bool: """ Check if ATTRIB for *tag* exists. Args: tag: tag name search_const: search also const ATTDEF entities """ return self.get_attrib(tag, search_const) is not None def add_attrib(self, tag: str, text: str, insert: 'Vertex' = (0, 0), dxfattribs: dict = None) -> 'Attrib': """ Add new ATTRIB entity. Args: tag: tag name text: content text insert: insert position as tuple (x, y[, z]) dxfattribs: additional DXF attributes Returns: Attrib object """ dxfattribs = dxfattribs or {} dxfattribs['tag'] = tag dxfattribs['text'] = text dxfattribs['insert'] = insert attrib_entity = cast('Attrib', self._new_entity('ATTRIB', dxfattribs)) self._append_attrib_entity(attrib_entity) return attrib_entity def _append_attrib_entity(self, entity: 'Attrib') -> None: has_no_attribs_attached = self.tags.link is None if has_no_attribs_attached or self.dxf.attribs_follow == 0: prev = self seqend = self._new_entity('SEQEND', {}) else: attribs = list(self.attribs()) prev = attribs[-1] seqend = self.dxffactory.wrap_handle(prev.tags.link) prev.tags.link = entity.dxf.handle entity.tags.link = seqend.dxf.handle self.dxf.attribs_follow = 1 def delete_attrib(self, tag: str, ignore=False) -> None: """ Delete attached ATTRIB entity `tag`, raises a KeyError exception if `tag` does not exist, set `ignore` to True, to ignore not existing ATTRIB entities. Args: tag: ATTRIB name ignore: False -> raise KeyError exception if `tag` does not exist """ if self.dxf.attribs_follow == 0: if ignore: return else: raise DXFKeyError(tag) dxffactory = self.dxffactory handle = self.tags.link prev = self while handle is not None: entity = dxffactory.wrap_handle(handle) next_entity = entity.tags.link if next_entity is None: # found SeqEnd break else: if entity.dxf.tag == tag: prev.tags.link = next_entity # remove entity from linked list self.entitydb.delete_entity(entity) self._fix_attribs() return prev = entity handle = next_entity if not ignore: raise DXFKeyError(tag) def delete_all_attribs(self) -> None: """ Delete all ATTRIB entities attached to the INSERT entity and the following SEQEND entity. Ignores the value of dxf.attribs_follow. """ db = self.entitydb handle = self.tags.link while handle is not None: entity_tags = db[handle] db.delete_handle(handle) handle = entity_tags.link entity_tags.link = None self.tags.link = None self.dxf.attribs_follow = 0 def _fix_attribs(self) -> None: if self.dxf.attribs_follow == 0: self.delete_all_attribs() else: handle = self.tags.link if handle is None: self.dxf.attribs.follow = 0 return entity = self.dxffactory.wrap_handle(handle) if entity.dxftype() == 'SEQEND': # last attrib was deleted, only the SEQEND entity remains self.entitydb.delete_entity(entity) self.dxf.attribs_follow = 0 self.tags.link = None return def destroy(self) -> None: """ Delete all attached ATTRIB entities from entity database. Caution: this method is meant for internal usage. """ self.delete_all_attribs()