ParseDXF.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. ############################################################
  2. # FlatCAM: 2D Post-processing for Manufacturing #
  3. # http://flatcam.org #
  4. # File Author: Marius Adrian Stanciu (c) #
  5. # Date: 3/10/2019 #
  6. # MIT Licence #
  7. ############################################################
  8. import re
  9. import itertools
  10. import math
  11. import ezdxf
  12. from shapely.geometry import LinearRing, LineString, Point, Polygon
  13. from shapely.affinity import translate, rotate, scale, skew, affine_transform
  14. import numpy
  15. import logging
  16. log = logging.getLogger('base2')
  17. import FlatCAMApp
  18. from ParseFont import *
  19. from ParseDXF_Spline import *
  20. def distance(pt1, pt2):
  21. return math.sqrt((pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2)
  22. def dxfpoint2shapely(point):
  23. geo = Point(point.dxf.location).buffer(0.01)
  24. return geo
  25. def dxfline2shapely(line):
  26. try:
  27. start = (line.dxf.start[0], line.dxf.start[1])
  28. stop = (line.dxf.end[0], line.dxf.end[1])
  29. except Exception as e:
  30. log.debug(str(e))
  31. return None
  32. geo = LineString([start, stop])
  33. return geo
  34. def dxfcircle2shapely(circle, n_points=100):
  35. ocs = circle.ocs()
  36. # if the extrusion attribute is not (0, 0, 1) then we have to change the coordinate system from OCS to WCS
  37. if circle.dxf.extrusion != (0, 0, 1):
  38. center_pt = ocs.to_wcs(circle.dxf.center)
  39. else:
  40. center_pt = circle.dxf.center
  41. radius = circle.dxf.radius
  42. geo = Point(center_pt).buffer(radius, int(n_points / 4))
  43. return geo
  44. def dxfarc2shapely(arc, n_points=100):
  45. # ocs = arc.ocs()
  46. # # if the extrusion attribute is not (0, 0, 1) then we have to change the coordinate system from OCS to WCS
  47. # if arc.dxf.extrusion != (0, 0, 1):
  48. # arc_center = ocs.to_wcs(arc.dxf.center)
  49. # start_angle = math.radians(arc.dxf.start_angle) + math.pi
  50. # end_angle = math.radians(arc.dxf.end_angle) + math.pi
  51. # dir = 'CW'
  52. # else:
  53. # arc_center = arc.dxf.center
  54. # start_angle = math.radians(arc.dxf.start_angle)
  55. # end_angle = math.radians(arc.dxf.end_angle)
  56. # dir = 'CCW'
  57. #
  58. # center_x = arc_center[0]
  59. # center_y = arc_center[1]
  60. # radius = arc.dxf.radius
  61. #
  62. # point_list = []
  63. #
  64. # if start_angle > end_angle:
  65. # start_angle += 2 * math.pi
  66. #
  67. # line_seg = int((n_points * (end_angle - start_angle)) / math.pi)
  68. # step_angle = (end_angle - start_angle) / float(line_seg)
  69. #
  70. # angle = start_angle
  71. # for step in range(line_seg + 1):
  72. # if dir == 'CCW':
  73. # x = center_x + radius * math.cos(angle)
  74. # y = center_y + radius * math.sin(angle)
  75. # else:
  76. # x = center_x + radius * math.cos(-angle)
  77. # y = center_y + radius * math.sin(-angle)
  78. # point_list.append((x, y))
  79. # angle += step_angle
  80. #
  81. #
  82. # log.debug("X = %.3f, Y = %.3f, Radius = %.3f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.3f, dir=%s" %
  83. # (center_x, center_y, radius, start_angle, end_angle, step_angle, dir))
  84. #
  85. # geo = LineString(point_list)
  86. # return geo
  87. ocs = arc.ocs()
  88. # if the extrusion attribute is not (0, 0, 1) then we have to change the coordinate system from OCS to WCS
  89. if arc.dxf.extrusion != (0, 0, 1):
  90. arc_center = ocs.to_wcs(arc.dxf.center)
  91. start_angle = arc.dxf.start_angle + 180
  92. end_angle = arc.dxf.end_angle + 180
  93. dir = 'CW'
  94. else:
  95. arc_center = arc.dxf.center
  96. start_angle = arc.dxf.start_angle
  97. end_angle = arc.dxf.end_angle
  98. dir = 'CCW'
  99. center_x = arc_center[0]
  100. center_y = arc_center[1]
  101. radius = arc.dxf.radius
  102. point_list = []
  103. if start_angle > end_angle:
  104. start_angle = start_angle - 360
  105. angle = start_angle
  106. step_angle = float(abs(end_angle - start_angle) / n_points)
  107. while angle <= end_angle:
  108. if dir == 'CCW':
  109. x = center_x + radius * math.cos(math.radians(angle))
  110. y = center_y + radius * math.sin(math.radians(angle))
  111. else:
  112. x = center_x + radius * math.cos(math.radians(-angle))
  113. y = center_y + radius * math.sin(math.radians(-angle))
  114. point_list.append((x, y))
  115. angle += abs(step_angle)
  116. # in case the number of segments do not cover everything until the end of the arc
  117. if angle != end_angle:
  118. if dir == 'CCW':
  119. x = center_x + radius * math.cos(math.radians(end_angle))
  120. y = center_y + radius * math.sin(math.radians(end_angle))
  121. else:
  122. x = center_x + radius * math.cos(math.radians(- end_angle))
  123. y = center_y + radius * math.sin(math.radians(- end_angle))
  124. point_list.append((x, y))
  125. # log.debug("X = %.3f, Y = %.3f, Radius = %.3f, start_angle = %.1f, stop_angle = %.1f, step_angle = %.3f" %
  126. # (center_x, center_y, radius, start_angle, end_angle, step_angle))
  127. geo = LineString(point_list)
  128. return geo
  129. def dxfellipse2shapely(ellipse, ellipse_segments=100):
  130. # center = ellipse.dxf.center
  131. # start_angle = ellipse.dxf.start_param
  132. # end_angle = ellipse.dxf.end_param
  133. ocs = ellipse.ocs()
  134. # if the extrusion attribute is not (0, 0, 1) then we have to change the coordinate system from OCS to WCS
  135. if ellipse.dxf.extrusion != (0, 0, 1):
  136. center = ocs.to_wcs(ellipse.dxf.center)
  137. start_angle = ocs.to_wcs(ellipse.dxf.start_param)
  138. end_angle = ocs.to_wcs(ellipse.dxf.end_param)
  139. dir = 'CW'
  140. else:
  141. center = ellipse.dxf.center
  142. start_angle = ellipse.dxf.start_param
  143. end_angle = ellipse.dxf.end_param
  144. dir = 'CCW'
  145. # print("Dir = %s" % dir)
  146. major_axis = ellipse.dxf.major_axis
  147. ratio = ellipse.dxf.ratio
  148. points_list = []
  149. major_axis = Vector(major_axis)
  150. major_x = major_axis[0]
  151. major_y = major_axis[1]
  152. if start_angle >= end_angle:
  153. end_angle += 2.0 * math.pi
  154. line_seg = int((ellipse_segments * (end_angle - start_angle)) / math.pi)
  155. step_angle = abs(end_angle - start_angle) / float(line_seg)
  156. angle = start_angle
  157. for step in range(line_seg + 1):
  158. if dir == 'CW':
  159. major_dim = normalize_2(major_axis)
  160. minor_dim = normalize_2(Vector([ratio * k for k in major_axis]))
  161. vx = (major_dim[0] + major_dim[1]) * math.cos(angle)
  162. vy = (minor_dim[0] - minor_dim[1]) * math.sin(angle)
  163. x = center[0] + major_x * vx - major_y * vy
  164. y = center[1] + major_y * vx + major_x * vy
  165. angle += step_angle
  166. else:
  167. major_dim = normalize_2(major_axis)
  168. minor_dim = (Vector([ratio * k for k in major_dim]))
  169. vx = (major_dim[0] + major_dim[1]) * math.cos(angle)
  170. vy = (minor_dim[0] + minor_dim[1]) * math.sin(angle)
  171. x = center[0] + major_x * vx + major_y * vy
  172. y = center[1] + major_y * vx + major_x * vy
  173. angle += step_angle
  174. points_list.append((x, y))
  175. geo = LineString(points_list)
  176. return geo
  177. def dxfpolyline2shapely(polyline):
  178. final_pts = []
  179. pts = polyline.points()
  180. for i in pts:
  181. final_pts.append((i[0], i[1]))
  182. if polyline.is_closed:
  183. final_pts.append(final_pts[0])
  184. geo = LineString(final_pts)
  185. return geo
  186. def dxflwpolyline2shapely(lwpolyline):
  187. final_pts = []
  188. for point in lwpolyline:
  189. x, y, _, _, _ = point
  190. final_pts.append((x, y))
  191. if lwpolyline.closed:
  192. final_pts.append(final_pts[0])
  193. geo = LineString(final_pts)
  194. return geo
  195. def dxfsolid2shapely(solid):
  196. iterator = 0
  197. corner_list = []
  198. try:
  199. corner_list.append(solid[iterator])
  200. iterator += 1
  201. except:
  202. return Polygon(corner_list)
  203. def dxfspline2shapely(spline):
  204. with spline.edit_data() as spline_data:
  205. ctrl_points = spline_data.control_points
  206. knot_values = spline_data.knot_values
  207. is_closed = spline.closed
  208. degree = spline.dxf.degree
  209. x_list, y_list, _ = spline2Polyline(ctrl_points, degree=degree, closed=is_closed, segments=20, knots=knot_values)
  210. points_list = zip(x_list, y_list)
  211. geo = LineString(points_list)
  212. return geo
  213. def dxftrace2shapely(trace):
  214. iterator = 0
  215. corner_list = []
  216. try:
  217. corner_list.append(trace[iterator])
  218. iterator += 1
  219. except:
  220. return Polygon(corner_list)
  221. def getdxfgeo(dxf_object):
  222. msp = dxf_object.modelspace()
  223. geos = get_geo(dxf_object, msp)
  224. # geo_block = get_geo_from_block(dxf_object)
  225. return geos
  226. def get_geo_from_insert(dxf_object, insert):
  227. geo_block_transformed = []
  228. phi = insert.dxf.rotation
  229. tr = insert.dxf.insert
  230. sx = insert.dxf.xscale
  231. sy = insert.dxf.yscale
  232. r_count = insert.dxf.row_count
  233. r_spacing = insert.dxf.row_spacing
  234. c_count = insert.dxf.column_count
  235. c_spacing = insert.dxf.column_spacing
  236. # print(phi, tr)
  237. # identify the block given the 'INSERT' type entity name
  238. block = dxf_object.blocks[insert.dxf.name]
  239. block_coords = (block.block.dxf.base_point[0], block.block.dxf.base_point[1])
  240. # get a list of geometries found in the block
  241. geo_block = get_geo(dxf_object, block)
  242. # iterate over the geometries found and apply any transformation found in the 'INSERT' entity attributes
  243. for geo in geo_block:
  244. # get the bounds of the geometry
  245. # minx, miny, maxx, maxy = geo.bounds
  246. if tr[0] != 0 or tr[1] != 0:
  247. geo = translate(geo, (tr[0] - block_coords[0]), (tr[1] - block_coords[1]))
  248. # support for array block insertions
  249. if r_count > 1:
  250. for r in range(r_count):
  251. geo_block_transformed.append(translate(geo, (tr[0] + (r * r_spacing) - block_coords[0]), 0))
  252. if c_count > 1:
  253. for c in range(c_count):
  254. geo_block_transformed.append(translate(geo, 0, (tr[1] + (c * c_spacing) - block_coords[1])))
  255. if sx != 1 or sy != 1:
  256. geo = scale(geo, sx, sy)
  257. if phi != 0:
  258. geo = rotate(geo, phi, origin=tr)
  259. geo_block_transformed.append(geo)
  260. return geo_block_transformed
  261. def get_geo(dxf_object, container):
  262. # store shapely geometry here
  263. geo = []
  264. for dxf_entity in container:
  265. g = []
  266. # print("Entity", dxf_entity.dxftype())
  267. if dxf_entity.dxftype() == 'POINT':
  268. g = dxfpoint2shapely(dxf_entity,)
  269. elif dxf_entity.dxftype() == 'LINE':
  270. g = dxfline2shapely(dxf_entity,)
  271. elif dxf_entity.dxftype() == 'CIRCLE':
  272. g = dxfcircle2shapely(dxf_entity)
  273. elif dxf_entity.dxftype() == 'ARC':
  274. g = dxfarc2shapely(dxf_entity)
  275. elif dxf_entity.dxftype() == 'ELLIPSE':
  276. g = dxfellipse2shapely(dxf_entity)
  277. elif dxf_entity.dxftype() == 'LWPOLYLINE':
  278. g = dxflwpolyline2shapely(dxf_entity)
  279. elif dxf_entity.dxftype() == 'POLYLINE':
  280. g = dxfpolyline2shapely(dxf_entity)
  281. elif dxf_entity.dxftype() == 'SOLID':
  282. g = dxfsolid2shapely(dxf_entity)
  283. elif dxf_entity.dxftype() == 'TRACE':
  284. g = dxftrace2shapely(dxf_entity)
  285. elif dxf_entity.dxftype() == 'SPLINE':
  286. g = dxfspline2shapely(dxf_entity)
  287. elif dxf_entity.dxftype() == 'INSERT':
  288. g = get_geo_from_insert(dxf_object, dxf_entity)
  289. else:
  290. log.debug(" %s is not supported yet." % dxf_entity.dxftype())
  291. if g is not None:
  292. if type(g) == list:
  293. for subg in g:
  294. geo.append(subg)
  295. else:
  296. geo.append(g)
  297. return geo
  298. def getdxftext(exf_object, object_type, units=None):
  299. pass
  300. # def get_geo_from_block(dxf_object):
  301. # geo_block_transformed = []
  302. #
  303. # msp = dxf_object.modelspace()
  304. # # iterate through all 'INSERT' entities found in modelspace msp
  305. # for insert in msp.query('INSERT'):
  306. # phi = insert.dxf.rotation
  307. # tr = insert.dxf.insert
  308. # sx = insert.dxf.xscale
  309. # sy = insert.dxf.yscale
  310. # r_count = insert.dxf.row_count
  311. # r_spacing = insert.dxf.row_spacing
  312. # c_count = insert.dxf.column_count
  313. # c_spacing = insert.dxf.column_spacing
  314. #
  315. # # print(phi, tr)
  316. #
  317. # # identify the block given the 'INSERT' type entity name
  318. # print(insert.dxf.name)
  319. # block = dxf_object.blocks[insert.dxf.name]
  320. # block_coords = (block.block.dxf.base_point[0], block.block.dxf.base_point[1])
  321. #
  322. # # get a list of geometries found in the block
  323. # # store shapely geometry here
  324. # geo_block = []
  325. #
  326. # for dxf_entity in block:
  327. # g = []
  328. # # print("Entity", dxf_entity.dxftype())
  329. # if dxf_entity.dxftype() == 'POINT':
  330. # g = dxfpoint2shapely(dxf_entity, )
  331. # elif dxf_entity.dxftype() == 'LINE':
  332. # g = dxfline2shapely(dxf_entity, )
  333. # elif dxf_entity.dxftype() == 'CIRCLE':
  334. # g = dxfcircle2shapely(dxf_entity)
  335. # elif dxf_entity.dxftype() == 'ARC':
  336. # g = dxfarc2shapely(dxf_entity)
  337. # elif dxf_entity.dxftype() == 'ELLIPSE':
  338. # g = dxfellipse2shapely(dxf_entity)
  339. # elif dxf_entity.dxftype() == 'LWPOLYLINE':
  340. # g = dxflwpolyline2shapely(dxf_entity)
  341. # elif dxf_entity.dxftype() == 'POLYLINE':
  342. # g = dxfpolyline2shapely(dxf_entity)
  343. # elif dxf_entity.dxftype() == 'SOLID':
  344. # g = dxfsolid2shapely(dxf_entity)
  345. # elif dxf_entity.dxftype() == 'TRACE':
  346. # g = dxftrace2shapely(dxf_entity)
  347. # elif dxf_entity.dxftype() == 'SPLINE':
  348. # g = dxfspline2shapely(dxf_entity)
  349. # elif dxf_entity.dxftype() == 'INSERT':
  350. # log.debug("Not supported yet.")
  351. # else:
  352. # log.debug("Not supported yet.")
  353. #
  354. # if g is not None:
  355. # if type(g) == list:
  356. # for subg in g:
  357. # geo_block.append(subg)
  358. # else:
  359. # geo_block.append(g)
  360. #
  361. # # iterate over the geometries found and apply any transformation found in the 'INSERT' entity attributes
  362. # for geo in geo_block:
  363. # if tr[0] != 0 or tr[1] != 0:
  364. # geo = translate(geo, (tr[0] - block_coords[0]), (tr[1] - block_coords[1]))
  365. #
  366. # # support for array block insertions
  367. # if r_count > 1:
  368. # for r in range(r_count):
  369. # geo_block_transformed.append(translate(geo, (tr[0] + (r * r_spacing) - block_coords[0]), 0))
  370. #
  371. # if c_count > 1:
  372. # for c in range(c_count):
  373. # geo_block_transformed.append(translate(geo, 0, (tr[1] + (c * c_spacing) - block_coords[1])))
  374. #
  375. # if sx != 1 or sy != 1:
  376. # geo = scale(geo, sx, sy)
  377. # if phi != 0:
  378. # geo = rotate(geo, phi, origin=tr)
  379. #
  380. # geo_block_transformed.append(geo)
  381. # return geo_block_transformed