polyline.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # Created: 25.03.2011
  2. # Copyright (c) 2011-2018, Manfred Moitzi
  3. # License: MIT License
  4. from typing import TYPE_CHECKING, Iterable, List, cast, Sequence, Union
  5. from ezdxf.lldxf.const import DXFValueError, DXFIndexError
  6. from ezdxf.lldxf import const
  7. from .graphics import GraphicEntity, ExtendedTags, make_attribs, DXFAttr, XType
  8. from .facemixins import PolyfaceMixin, PolymeshMixin
  9. from .trace import QuadrilateralMixin
  10. if TYPE_CHECKING:
  11. from ezdxf.eztypes import Vertex, TagValue, Vertex
  12. _POLYLINE_TPL = """0
  13. POLYLINE
  14. 5
  15. 0
  16. 8
  17. 0
  18. 66
  19. 1
  20. 70
  21. 0
  22. 10
  23. 0.0
  24. 20
  25. 0.0
  26. 30
  27. 0.0
  28. """
  29. class Polyline(GraphicEntity):
  30. __slots__ = ()
  31. TEMPLATE = ExtendedTags.from_text(_POLYLINE_TPL)
  32. DXFATTRIBS = make_attribs({
  33. 'elevation': DXFAttr(10, xtype=XType.any_point),
  34. 'flags': DXFAttr(70, default=0),
  35. 'default_start_width': DXFAttr(40, default=0.0),
  36. 'default_end_width': DXFAttr(41, default=0.0),
  37. 'm_count': DXFAttr(71, default=0),
  38. 'n_count': DXFAttr(72, default=0),
  39. 'm_smooth_density': DXFAttr(73, default=0),
  40. 'n_smooth_density': DXFAttr(74, default=0),
  41. 'smooth_type': DXFAttr(75, default=0),
  42. })
  43. # polyline flags (70)
  44. CLOSED = 1
  45. MESH_CLOSED_M_DIRECTION = CLOSED
  46. CURVE_FIT_VERTICES_ADDED = 2
  47. SPLINE_FIT_VERTICES_ADDED = 4
  48. POLYLINE_3D = 8
  49. POLYMESH = 16
  50. MESH_CLOSED_N_DIRECTION = 32
  51. POLYFACE = 64
  52. GENERATE_LINETYPE_PATTERN = 128
  53. # polymesh smooth type (75)
  54. NO_SMOOTH = 0
  55. QUADRATIC_BSPLINE = 5
  56. CUBIC_BSPLINE = 6
  57. BEZIER_SURFACE = 8
  58. ANY3D = POLYLINE_3D | POLYMESH | POLYFACE
  59. def post_new_hook(self) -> None:
  60. seqend = self._new_entity('SEQEND', {})
  61. self.tags.link = seqend.dxf.handle
  62. def set_dxf_attrib(self, key: str, value: 'TagValue') -> None:
  63. super(Polyline, self).set_dxf_attrib(key, value)
  64. if key == 'layer': # if layer of POLYLINE changed, also change layer of VERTEX entities
  65. self._set_vertices_layer(value)
  66. def _set_vertices_layer(self, layer_name: str) -> None:
  67. for vertex in self.vertices():
  68. vertex.dxf.layer = layer_name
  69. def get_vertex_flags(self) -> int:
  70. return const.VERTEX_FLAGS[self.get_mode()]
  71. def get_mode(self) -> str:
  72. if self.is_3d_polyline:
  73. return 'AcDb3dPolyline'
  74. elif self.is_polygon_mesh:
  75. return 'AcDbPolygonMesh'
  76. elif self.is_poly_face_mesh:
  77. return 'AcDbPolyFaceMesh'
  78. else:
  79. return 'AcDb2dPolyline'
  80. @property
  81. def is_2d_polyline(self) -> bool:
  82. return self.dxf.flags & self.ANY3D == 0
  83. @property
  84. def is_3d_polyline(self) -> bool:
  85. return bool(self.dxf.flags & self.POLYLINE_3D)
  86. @property
  87. def is_polygon_mesh(self) -> bool:
  88. return bool(self.dxf.flags & self.POLYMESH)
  89. @property
  90. def is_poly_face_mesh(self) -> bool:
  91. return bool(self.dxf.flags & self.POLYFACE)
  92. @property
  93. def is_closed(self) -> bool:
  94. return bool(self.dxf.flags & self.CLOSED)
  95. @property
  96. def is_m_closed(self) -> bool:
  97. return bool(self.dxf.flags & self.MESH_CLOSED_M_DIRECTION)
  98. @property
  99. def is_n_closed(self) -> bool:
  100. return bool(self.dxf.flags & self.MESH_CLOSED_N_DIRECTION)
  101. def m_close(self) -> None:
  102. self.dxf.flags = self.dxf.flags | self.MESH_CLOSED_M_DIRECTION
  103. def n_close(self) -> None:
  104. self.dxf.flags = self.dxf.flags | self.MESH_CLOSED_N_DIRECTION
  105. def close(self, m_close, n_close=False) -> None:
  106. if m_close:
  107. self.m_close()
  108. if n_close:
  109. self.n_close()
  110. def __len__(self) -> int:
  111. count = 0
  112. db = self.entitydb
  113. tags = db[self.tags.link]
  114. while tags.link is not None:
  115. count += 1
  116. tags = db[tags.link]
  117. return count
  118. def __getitem__(self, pos) -> 'DXFVertex':
  119. count = 0
  120. db = self.entitydb
  121. tags = db[self.tags.link]
  122. while tags.link is not None:
  123. if count == pos:
  124. return self.dxffactory.wrap_entity(tags) # type: ignore
  125. count += 1
  126. tags = db[tags.link]
  127. raise DXFIndexError("vertex index out of range")
  128. def vertices(self) -> Iterable['DXFVertex']:
  129. return (entity for entity in self.linked_entities() if entity.dxftype() == 'VERTEX') # type: ignore
  130. def points(self) -> Iterable['Vertex']:
  131. return (vertex.dxf.location for vertex in self.vertices())
  132. def append_vertices(self, points: Iterable['Vertex'], dxfattribs: dict = None) -> None:
  133. dxfattribs = dxfattribs or {}
  134. points = list(points)
  135. if len(points) > 0:
  136. last_vertex = self._get_last_vertex()
  137. for new_vertex in self._points_to_dxf_vertices(points, dxfattribs):
  138. self._insert_after(last_vertex, new_vertex)
  139. last_vertex = new_vertex
  140. @staticmethod
  141. def _insert_after(prev_vertex: 'DXFVertex', new_vertex: 'DXFVertex') -> None:
  142. succ = prev_vertex.tags.link
  143. prev_vertex.tags.link = new_vertex.dxf.handle
  144. new_vertex.tags.link = succ
  145. def _get_last_vertex(self) -> 'DXFVertex':
  146. db = self.entitydb
  147. tags = self.tags
  148. handle = self.dxf.handle
  149. while tags.link is not None: # while not SEQEND
  150. prev_handle = handle
  151. handle = tags.link
  152. tags = db[handle]
  153. return self.dxffactory.wrap_handle(prev_handle) # type: DXFVertex
  154. def insert_vertices(self, pos: int, points: Iterable['Vertex'], dxfattribs: dict = None) -> None:
  155. """ Insert *points* at position *pos*.
  156. :param pos: insertion position
  157. :param points: list of (x, y, z)-tuples
  158. :param dxfattribs: dict of DXF attributes
  159. """
  160. dxfattribs = dxfattribs or {}
  161. if pos > 0:
  162. insert_vertex = self.__getitem__(pos - 1)
  163. else:
  164. insert_vertex = self
  165. for new_vertex in self._points_to_dxf_vertices(points, dxfattribs):
  166. self._insert_after(insert_vertex, new_vertex)
  167. insert_vertex = new_vertex
  168. def _append_vertices(self, vertices: Iterable['DXFVertex']) -> None:
  169. """ Append DXFVertex objects.
  170. :param vertices: iterable of DXFVertex objects
  171. """
  172. last_vertex = self._get_last_vertex()
  173. for vertex in vertices:
  174. self._insert_after(last_vertex, vertex)
  175. last_vertex = vertex
  176. def _points_to_dxf_vertices(self, points: Iterable['Vertex'], dxfattribs: dict) -> List['DXFVertex']:
  177. """ Converts point (x,y, z)-tuples into DXFVertex objects.
  178. :param points: list of (x, y,z)-tuples
  179. :param dxfattribs: dict of DXF attributes
  180. """
  181. dxfattribs['flags'] = dxfattribs.get('flags', 0) | self.get_vertex_flags()
  182. dxfattribs['layer'] = self.get_dxf_attrib('layer', '0') # all vertices on the same layer as the POLYLINE entity
  183. vertices = [] # type: List[DXFVertex]
  184. for point in points:
  185. dxfattribs['location'] = point
  186. vertices.append(cast('DXFVertex', self._new_entity('VERTEX', dxfattribs)))
  187. return vertices
  188. def delete_vertices(self, pos: int, count=1) -> None:
  189. db = self.entitydb
  190. prev_vertex = self.__getitem__(pos - 1).tags if pos > 0 else self.tags
  191. vertex = db[prev_vertex.link]
  192. while vertex.dxftype() == 'VERTEX':
  193. db.delete_handle(prev_vertex.link) # remove from database
  194. prev_vertex.link = vertex.link # remove vertex from list
  195. count -= 1
  196. if count == 0:
  197. return
  198. vertex = db[prev_vertex.link]
  199. raise DXFValueError("invalid count")
  200. def _unlink_all_vertices(self) -> None:
  201. # but don't delete it from database
  202. last_vertex = self._get_last_vertex()
  203. self.tags.link = last_vertex.tags.link # link POLYLINE -> SEQEND
  204. def cast(self) -> Union['Polyline', 'Polymesh', 'Polyface']:
  205. mode = self.get_mode()
  206. if mode == 'AcDbPolyFaceMesh':
  207. return Polyface.convert(self)
  208. elif mode == 'AcDbPolygonMesh':
  209. return Polymesh.convert(self)
  210. else:
  211. return self
  212. def destroy(self) -> None:
  213. db = self.entitydb
  214. handle = self.tags.link
  215. while handle is not None:
  216. tags = db[handle]
  217. db.delete_handle(handle)
  218. handle = tags.link
  219. self.tags.link = None
  220. class Polyface(Polyline, PolyfaceMixin):
  221. @staticmethod
  222. def convert(polyline: Polyline) -> 'Polyface':
  223. return Polyface(polyline.tags, polyline.drawing)
  224. class Polymesh(Polyline, PolymeshMixin):
  225. @staticmethod
  226. def convert(polyline: Polyline) -> 'Polymesh':
  227. return Polymesh(polyline.tags, polyline.drawing)
  228. _VERTEX_TPL = """0
  229. VERTEX
  230. 5
  231. 0
  232. 8
  233. 0
  234. 10
  235. 0.0
  236. 20
  237. 0.0
  238. 30
  239. 0.0
  240. 70
  241. 0
  242. """
  243. class DXFVertex(GraphicEntity, QuadrilateralMixin):
  244. __slots__ = ()
  245. TEMPLATE = ExtendedTags.from_text(_VERTEX_TPL)
  246. DXFATTRIBS = make_attribs({
  247. 'location': DXFAttr(10, xtype=XType.any_point),
  248. 'start_width': DXFAttr(40, default=0.),
  249. 'end_width': DXFAttr(41, default=0.),
  250. 'bulge': DXFAttr(42, default=0.),
  251. 'flags': DXFAttr(70, default=0),
  252. 'tangent': DXFAttr(50),
  253. 'vtx0': DXFAttr(71),
  254. 'vtx1': DXFAttr(72),
  255. 'vtx2': DXFAttr(73),
  256. 'vtx3': DXFAttr(74),
  257. })
  258. EXTRA_VERTEX_CREATED = 1 # Extra vertex created by curve-fitting
  259. CURVE_FIT_TANGENT = 2 # Curve-fit tangent defined for this vertex.
  260. # A curve-fit tangent direction of 0 may be omitted from the DXF output, but is
  261. # significant if this bit is set.
  262. # 4 = unused, never set in dxf files
  263. SPLINE_VERTEX_CREATED = 8 # Spline vertex created by spline-fitting
  264. SPLINE_FRAME_CONTROL_POINT = 16
  265. POLYLINE_3D_VERTEX = 32
  266. POLYGON_MESH_VERTEX = 64
  267. POLYFACE_MESH_VERTEX = 128
  268. FACE_FLAGS = POLYGON_MESH_VERTEX + POLYFACE_MESH_VERTEX
  269. VTX3D = POLYLINE_3D_VERTEX + POLYGON_MESH_VERTEX + POLYFACE_MESH_VERTEX
  270. @property
  271. def is_2d_polyline_vertex(self) -> bool:
  272. return self.dxf.flags & self.VTX3D == 0
  273. @property
  274. def is_3d_polyline_vertex(self) -> bool:
  275. return self.dxf.flags & self.POLYLINE_3D_VERTEX
  276. @property
  277. def is_polygon_mesh_vertex(self) -> bool:
  278. return self.dxf.flags & self.POLYGON_MESH_VERTEX
  279. @property
  280. def is_poly_face_mesh_vertex(self) -> bool:
  281. return self.dxf.flags & self.FACE_FLAGS == self.FACE_FLAGS
  282. @property
  283. def is_face_record(self) -> bool:
  284. return (self.dxf.flags & self.FACE_FLAGS) == self.POLYFACE_MESH_VERTEX