insert.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. # Created: 25.03.2011
  2. # Copyright (c) 2011-2018, Manfred Moitzi
  3. # License: MIT License
  4. from typing import TYPE_CHECKING, Iterable, Tuple, Union, Optional, cast
  5. from ezdxf.lldxf.const import DXFValueError, DXFKeyError
  6. from .graphics import GraphicEntity, ExtendedTags, make_attribs, DXFAttr, XType
  7. if TYPE_CHECKING:
  8. from ezdxf.eztypes import Attrib, Attdef, Vertex
  9. _INSERT_TPL = """0
  10. INSERT
  11. 5
  12. 0
  13. 8
  14. 0
  15. 2
  16. BLOCKNAME
  17. 10
  18. 0.0
  19. 20
  20. 0.0
  21. 30
  22. 0.0
  23. 41
  24. 1.0
  25. 42
  26. 1.0
  27. 43
  28. 1.0
  29. 50
  30. 0.0
  31. """
  32. # IMPORTANT: Bug in AutoCAD 2010
  33. # attribsfollow = 0, for NO attribsfollow, does not work with ACAD 2010
  34. # if no attribs attached to the INSERT entity, omit attribsfollow tag
  35. class Insert(GraphicEntity):
  36. __slots__ = ()
  37. TEMPLATE = ExtendedTags.from_text(_INSERT_TPL)
  38. DXFATTRIBS = make_attribs({
  39. 'attribs_follow': DXFAttr(66, default=0),
  40. 'name': DXFAttr(2),
  41. 'insert': DXFAttr(10, xtype=XType.any_point),
  42. 'xscale': DXFAttr(41, default=1.0),
  43. 'yscale': DXFAttr(42, default=1.0),
  44. 'zscale': DXFAttr(43, default=1.0),
  45. 'rotation': DXFAttr(50, default=0.0),
  46. 'column_count': DXFAttr(70, default=1),
  47. 'row_count': DXFAttr(71, default=1),
  48. 'column_spacing': DXFAttr(44, default=0.0),
  49. 'row_spacing': DXFAttr(45, default=0.0),
  50. })
  51. def attribs(self) -> Iterable['Attrib']:
  52. """
  53. Iterate over all appended ATTRIB entities, yields Attrib() objects.
  54. """
  55. if self.dxf.attribs_follow == 0:
  56. return
  57. dxffactory = self.dxffactory
  58. handle = self.tags.link
  59. while handle is not None:
  60. entity = dxffactory.wrap_handle(handle)
  61. next_entity = entity.tags.link
  62. if next_entity is None: # found SeqEnd
  63. return
  64. else:
  65. yield entity
  66. handle = next_entity
  67. def place(self, insert: 'Vertex' = None,
  68. scale: Tuple[float, float, float] = None,
  69. rotation: float = None) -> 'Insert':
  70. """
  71. Set placing attributes of the INSERT entity.
  72. Args:
  73. insert: insert position as (x, y [,z]) tuple
  74. scale: (scale_x, scale_y, scale_z) tuple
  75. rotation (float): rotation angle in degrees
  76. Returns:
  77. Insert object (fluent interface)
  78. """
  79. if insert is not None:
  80. self.dxf.insert = insert
  81. if scale is not None:
  82. if len(scale) != 3:
  83. raise DXFValueError("Parameter scale has to be a (x, y, z)-tuple.")
  84. x, y, z = scale
  85. self.dxf.xscale = x
  86. self.dxf.yscale = y
  87. self.dxf.zscale = z
  88. if rotation is not None:
  89. self.dxf.rotation = rotation
  90. return self
  91. def grid(self, size: Tuple[int, int] = (1, 1), spacing: Tuple[float, float] = (1, 1)) -> 'Insert':
  92. """
  93. Set grid placing attributes of the INSERT entity.
  94. Args:
  95. size: grid size as (row_count, column_count) tuple
  96. spacing: distance between placing as (row_spacing, column_spacing) tuple
  97. Returns:
  98. Insert object (fluent interface)
  99. """
  100. if len(size) != 2:
  101. raise DXFValueError("Parameter size has to be a (row_count, column_count)-tuple.")
  102. if len(spacing) != 2:
  103. raise DXFValueError("Parameter spacing has to be a (row_spacing, column_spacing)-tuple.")
  104. self.dxf.row_count = size[0]
  105. self.dxf.column_count = size[1]
  106. self.dxf.row_spacing = spacing[0]
  107. self.dxf.column_spacing = spacing[1]
  108. return self
  109. def get_attrib(self, tag: str, search_const: bool = False) -> Optional[Union['Attrib', 'Attdef']]:
  110. """
  111. Get attached ATTRIB entity by *tag*.
  112. Args:
  113. tag: tag name
  114. search_const: search also const ATTDEF entities
  115. Returns:
  116. Attrib or Attdef object
  117. """
  118. for attrib in self.attribs():
  119. if tag == attrib.dxf.tag:
  120. return attrib
  121. if search_const and self.drawing is not None:
  122. block = self.drawing.blocks[self.dxf.name] # raises KeyError() if not found
  123. for attdef in block.get_const_attdefs():
  124. if tag == attdef.dxf.tag:
  125. return attdef
  126. return None
  127. def get_attrib_text(self, tag: str, default: str = None, search_const: bool = False) -> str:
  128. """
  129. Get content text of attached ATTRIB entity *tag*.
  130. Args:
  131. tag: tag name
  132. default: default value if tag is absent
  133. search_const: search also const ATTDEF entities
  134. Returns:
  135. content text as str
  136. """
  137. attrib = self.get_attrib(tag, search_const)
  138. if attrib is None:
  139. return default
  140. return attrib.dxf.text
  141. def has_attrib(self, tag: str, search_const: bool = False) -> bool:
  142. """
  143. Check if ATTRIB for *tag* exists.
  144. Args:
  145. tag: tag name
  146. search_const: search also const ATTDEF entities
  147. """
  148. return self.get_attrib(tag, search_const) is not None
  149. def add_attrib(self, tag: str, text: str, insert: 'Vertex' = (0, 0), dxfattribs: dict = None) -> 'Attrib':
  150. """
  151. Add new ATTRIB entity.
  152. Args:
  153. tag: tag name
  154. text: content text
  155. insert: insert position as tuple (x, y[, z])
  156. dxfattribs: additional DXF attributes
  157. Returns:
  158. Attrib object
  159. """
  160. dxfattribs = dxfattribs or {}
  161. dxfattribs['tag'] = tag
  162. dxfattribs['text'] = text
  163. dxfattribs['insert'] = insert
  164. attrib_entity = cast('Attrib', self._new_entity('ATTRIB', dxfattribs))
  165. self._append_attrib_entity(attrib_entity)
  166. return attrib_entity
  167. def _append_attrib_entity(self, entity: 'Attrib') -> None:
  168. has_no_attribs_attached = self.tags.link is None
  169. if has_no_attribs_attached or self.dxf.attribs_follow == 0:
  170. prev = self
  171. seqend = self._new_entity('SEQEND', {})
  172. else:
  173. attribs = list(self.attribs())
  174. prev = attribs[-1]
  175. seqend = self.dxffactory.wrap_handle(prev.tags.link)
  176. prev.tags.link = entity.dxf.handle
  177. entity.tags.link = seqend.dxf.handle
  178. self.dxf.attribs_follow = 1
  179. def delete_attrib(self, tag: str, ignore=False) -> None:
  180. """
  181. Delete attached ATTRIB entity `tag`, raises a KeyError exception if `tag` does not exist, set `ignore` to True,
  182. to ignore not existing ATTRIB entities.
  183. Args:
  184. tag: ATTRIB name
  185. ignore: False -> raise KeyError exception if `tag` does not exist
  186. """
  187. if self.dxf.attribs_follow == 0:
  188. if ignore:
  189. return
  190. else:
  191. raise DXFKeyError(tag)
  192. dxffactory = self.dxffactory
  193. handle = self.tags.link
  194. prev = self
  195. while handle is not None:
  196. entity = dxffactory.wrap_handle(handle)
  197. next_entity = entity.tags.link
  198. if next_entity is None: # found SeqEnd
  199. break
  200. else:
  201. if entity.dxf.tag == tag:
  202. prev.tags.link = next_entity # remove entity from linked list
  203. self.entitydb.delete_entity(entity)
  204. self._fix_attribs()
  205. return
  206. prev = entity
  207. handle = next_entity
  208. if not ignore:
  209. raise DXFKeyError(tag)
  210. def delete_all_attribs(self) -> None:
  211. """
  212. Delete all ATTRIB entities attached to the INSERT entity and the following SEQEND entity. Ignores the value
  213. of dxf.attribs_follow.
  214. """
  215. db = self.entitydb
  216. handle = self.tags.link
  217. while handle is not None:
  218. entity_tags = db[handle]
  219. db.delete_handle(handle)
  220. handle = entity_tags.link
  221. entity_tags.link = None
  222. self.tags.link = None
  223. self.dxf.attribs_follow = 0
  224. def _fix_attribs(self) -> None:
  225. if self.dxf.attribs_follow == 0:
  226. self.delete_all_attribs()
  227. else:
  228. handle = self.tags.link
  229. if handle is None:
  230. self.dxf.attribs.follow = 0
  231. return
  232. entity = self.dxffactory.wrap_handle(handle)
  233. if entity.dxftype() == 'SEQEND':
  234. # last attrib was deleted, only the SEQEND entity remains
  235. self.entitydb.delete_entity(entity)
  236. self.dxf.attribs_follow = 0
  237. self.tags.link = None
  238. return
  239. def destroy(self) -> None:
  240. """
  241. Delete all attached ATTRIB entities from entity database.
  242. Caution: this method is meant for internal usage.
  243. """
  244. self.delete_all_attribs()