ParseDXF.py 16 KB

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