geodata.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # Created: 17.03.2018
  2. # Copyright (c) 2018, Manfred Moitzi
  3. # License: MIT-License
  4. from typing import TYPE_CHECKING, Optional, List, Tuple
  5. from ezdxf.lldxf.const import DXFStructureError, DXFValueError
  6. from ezdxf.lldxf.types import DXFTag, DXFVertex
  7. from ezdxf.lldxf.tags import multi_tags_to_text, text_to_multi_tags
  8. from .dxfobjects import DXFEntity, none_subclass, DXFAttr, DXFAttributes, DefSubclass, ExtendedTags, XType
  9. if TYPE_CHECKING:
  10. from ezdxf.eztypes import Tags, Vertex
  11. _GEODATA_CLS = """0
  12. CLASS
  13. 1
  14. GEODATA
  15. 2
  16. AcDbGeoData
  17. 3
  18. ObjectDBX Classes
  19. 90
  20. 4095
  21. 91
  22. 0
  23. 280
  24. 0
  25. 281
  26. 0
  27. """
  28. _GEODATA_TPL = """0
  29. GEODATA
  30. 5
  31. 0
  32. 102
  33. {ACAD_REACTORS
  34. 330
  35. 0
  36. 102
  37. }
  38. 330
  39. DEAD
  40. 100
  41. AcDbGeoData
  42. 90
  43. 3
  44. 330
  45. 70
  46. 70
  47. 2
  48. 10
  49. 0.0
  50. 20
  51. 0.0
  52. 30
  53. 0.0
  54. 11
  55. 0.0
  56. 21
  57. 0.0
  58. 31
  59. 0.0
  60. 40
  61. 1.0
  62. 91
  63. 1
  64. 41
  65. 1.0
  66. 92
  67. 1
  68. 210
  69. 0.0
  70. 220
  71. 0.0
  72. 230
  73. 1.0
  74. 12
  75. 0
  76. 22
  77. 1
  78. 95
  79. 3
  80. 141
  81. 1.0
  82. 294
  83. 0
  84. 142
  85. 0.0
  86. 143
  87. 0.0
  88. 301
  89. COORDINATE_SYSTEM_DEFINITION
  90. """
  91. class GeoData(DXFEntity):
  92. # works in R2009 but this release release has not a new DXF version, so official required DXF version is:
  93. # AC1024/R2010
  94. # new entity not supported yet
  95. # DXF structure:
  96. # BLOCK_RECORD (e.g. Model Space) has an (102, ACAD_XDICTIONARY) with an entry ACAD_GEOGRAPHICDATA which points to
  97. # a GEODATA entity, GEODATA ACAD_REACTORS and owner points to this ACAD_XDICTIONARY, block_record points
  98. # to BLOCK_RECORD entry
  99. __slots__ = ()
  100. TEMPLATE = ExtendedTags.from_text(_GEODATA_TPL)
  101. CLASS = ExtendedTags.from_text(_GEODATA_CLS)
  102. DXFATTRIBS = DXFAttributes(
  103. none_subclass,
  104. DefSubclass('AcDbGeoData', {
  105. 'version': DXFAttr(90, default=2), # works in R2009=1 but this release has no DXF version, R2010=2
  106. 'coordinate_type': DXFAttr(70, default=3),
  107. # 0=unknown; 1=local grid; 2= projected grid; 3=geographic (latitude/longitude)
  108. 'block_record': DXFAttr(330), # handle to host block table record
  109. 'design_point': DXFAttr(10, xtype=XType.point3d), # Design point, reference point in WCS coordinates
  110. 'reference_point': DXFAttr(11, xtype=XType.point3d),
  111. # Reference point in coordinate system coordinates, valid only when coordinate type is Local Grid.
  112. 'north_direction': DXFAttr(12, xtype=XType.point2d), # North direction vector (2D)
  113. 'horizontal_unit_scale': DXFAttr(40),
  114. # Horizontal unit scale, factor which converts horizontal design coordinates to meters by multiplication.
  115. 'vertical_unit_scale': DXFAttr(41),
  116. # Vertical unit scale, factor which converts vertical design coordinates to meters by multiplication.
  117. 'horizontal_units': DXFAttr(91),
  118. # Horizontal units per UnitsValue enumeration. Will be kUnitsUndefined if units specified by horizontal unit scale is not supported by AutoCAD enumeration.
  119. 'vertical_units': DXFAttr(92),
  120. # Vertical units per UnitsValue enumeration. Will be kUnitsUndefined if units specified by vertical unit scale is not supported by AutoCAD enumeration.
  121. 'up_direction': DXFAttr(210, xtype=XType.point3d), # Up direction
  122. 'scale_estimation_method': DXFAttr(95, default=1),
  123. # 1=None; 2=User specified scale factor; 3=Grid scale at reference point; 4=Prismoidal
  124. 'sea_level_correction': DXFAttr(294, default=0), # Bool flag specifying whether to do sea level correction
  125. 'user_scale_factor': DXFAttr(141, default=1), # User specified scale factor
  126. 'sea_level_elevation': DXFAttr(142, default=0), # Sea level elevation
  127. 'coordinate_projection_radius': DXFAttr(143, default=0), # Coordinate projection radius
  128. 'geo_rss_tag': DXFAttr(302, default=''), # GeoRSS tag
  129. 'observation_from_tag': DXFAttr(305, default=''), # Observation from tag
  130. 'observation_to_tag': DXFAttr(306, default=''), # Observation to tag
  131. 'mesh_point_count': DXFAttr(93), # Number of Geo-Mesh points
  132. # mesh definition:
  133. # source mesh point (13, 23) repeat, mesh_point_count?
  134. # target mesh point (14, 24) repeat, mesh_point_count?
  135. 'mesh_faces_count': DXFAttr(96), # Number of faces
  136. # face index 97 repeat, faces_count
  137. # face index 98 repeat, faces_count
  138. # face index 99 repeat, faces_count
  139. }),
  140. )
  141. # coordinate_type const
  142. UNKNOWN = 0
  143. LOCAL_GRID = 1
  144. PROJECTED_GRID = 2
  145. GEOGRAPHIC = 3
  146. # scale_estimation_method const
  147. NONE = 1
  148. USER_SCALE = 2
  149. GRID_SCALE = 3
  150. PRISMOIDEAL = 4
  151. @property
  152. def AcDbGeoData(self) -> 'Tags':
  153. return self.tags.get_subclass('AcDbGeoData')
  154. def get_coordinate_system_definition(self) -> str:
  155. # 303, 303, 301, Coordinate system definition string, always XML?
  156. start = self._get_start_of_coordinate_system_definition()
  157. if start is not None:
  158. tags = self.AcDbGeoData.collect_consecutive_tags((303, 301), start=start)
  159. return multi_tags_to_text(tags, line_ending='^J')
  160. else:
  161. return ""
  162. def set_coordinate_system_definition(self, text: str) -> None:
  163. tags = text_to_multi_tags(text, code=303, size=255, line_ending='^J')
  164. if len(tags):
  165. last_value = tags[-1].value
  166. tags[-1] = DXFTag(code=301, value=last_value) # change group code of last tag 303 -> 301
  167. insert_pos = self._get_start_of_coordinate_system_definition()
  168. geodata = self.AcDbGeoData
  169. if insert_pos is None:
  170. insert_pos = geodata.tag_index(330) + 1 # 330 host block record is required
  171. self.AcDbGeoData.remove_tags((301, 303))
  172. geodata[insert_pos:insert_pos] = tags
  173. def _get_start_of_coordinate_system_definition(self) -> Optional[int]:
  174. try:
  175. start = self.AcDbGeoData.tag_index(303)
  176. except DXFValueError:
  177. try:
  178. start = self.AcDbGeoData.tag_index(301)
  179. except DXFValueError:
  180. return None
  181. return start
  182. def get_mesh_data(self) -> Tuple[List[Tuple['Vertex', 'Vertex']], List[List['Vertex']]]:
  183. geo_data = self.AcDbGeoData
  184. def get_vertices() -> List[Tuple['Vertex', 'Vertex']]:
  185. try:
  186. start = geo_data.tag_index(93)
  187. except DXFValueError:
  188. return []
  189. vertex_tags = geo_data.collect_consecutive_tags((13, 14), start=start + 1)
  190. source_vertices = [] # type: List[Vertex]
  191. target_vertices = [] # type: List[Vertex]
  192. for vertex in vertex_tags:
  193. if vertex.code == 13:
  194. source_vertices.append(vertex.value)
  195. else:
  196. target_vertices.append(vertex.value)
  197. if len(source_vertices) != len(target_vertices):
  198. raise DXFStructureError(
  199. "GEODATA(#{}) mesh definition error: source and target point count does not match ().".format(
  200. self.dxf.handle))
  201. return list(zip(source_vertices, target_vertices))
  202. def get_faces() -> List[List['Vertex']]:
  203. try:
  204. start = geo_data.tag_index(96)
  205. except DXFValueError:
  206. return []
  207. face_tags = geo_data.collect_consecutive_tags((97, 98, 99), start=start + 1)
  208. faces = [] # type: List[List[Vertex]]
  209. face = [] # type: List[Vertex]
  210. for face_tag in face_tags:
  211. if face_tag.code == 97:
  212. if len(face):
  213. faces.append(face)
  214. face = []
  215. face.append(face_tag.value)
  216. if len(face): # add last face
  217. faces.append(face)
  218. return faces
  219. return get_vertices(), get_faces()
  220. def set_mesh_data(self,
  221. vertices: List[Tuple['Vertex', 'Vertex']] = None,
  222. faces: List[List['Vertex']] = None) -> None:
  223. if vertices is None:
  224. vertices = []
  225. else:
  226. vertices = list(vertices)
  227. if faces is None:
  228. faces = []
  229. else:
  230. faces = list(faces)
  231. self._remove_mesh_data()
  232. geodata = self.AcDbGeoData
  233. geodata.append(DXFTag(93, len(vertices)))
  234. for source, target in vertices:
  235. geodata.append(DXFVertex(13, (source[0], source[1])))
  236. geodata.append(DXFVertex(14, (target[0], target[1])))
  237. geodata.append(DXFTag(96, len(faces)))
  238. for face in faces:
  239. geodata.extend(DXFTag(code, index) for code, index in zip((97, 98, 99), face))
  240. def _remove_mesh_data(self) -> None:
  241. self.AcDbGeoData.remove_tags(codes=(93, 13, 14, 96, 97, 98, 99))