camlib.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. import cairo
  2. #from string import *
  3. #from math import *
  4. #from random import *
  5. #from struct import *
  6. #import os
  7. #import sys
  8. from numpy import arctan2, Inf, array
  9. from matplotlib.figure import Figure
  10. # See: http://toblerity.org/shapely/manual.html
  11. from shapely.geometry import Polygon, LineString, Point
  12. from shapely.geometry import MultiPoint, MultiPolygon
  13. from shapely.geometry import box as shply_box
  14. from shapely.ops import cascaded_union
  15. class Geometry:
  16. def __init__(self):
  17. # Units (in or mm)
  18. self.units = 'in'
  19. # Final geometry: MultiPolygon
  20. self.solid_geometry = None
  21. def isolation_geometry(self, offset):
  22. '''
  23. Creates contours around geometry at a given
  24. offset distance.
  25. '''
  26. return self.solid_geometry.buffer(offset)
  27. def bounds(self):
  28. '''
  29. Returns coordinates of rectangular bounds
  30. of geometry: (xmin, ymin, xmax, ymax).
  31. '''
  32. if self.solid_geometry == None:
  33. print "Warning: solid_geometry not computed yet."
  34. return (0,0,0,0)
  35. return self.solid_geometry.bounds
  36. def size(self):
  37. '''
  38. Returns (width, height) of rectangular
  39. bounds of geometry.
  40. '''
  41. if self.solid_geometry == None:
  42. print "Warning: solid_geometry not computed yet."
  43. return 0
  44. bounds = self.bounds()
  45. return (bounds[2]-bounds[0], bounds[3]-bounds[1])
  46. def get_empty_area(self, boundary=None):
  47. '''
  48. Returns the complement of self.solid_geometry within
  49. the given boundary polygon. If not specified, it defaults to
  50. the rectangular bounding box of self.solid_geometry.
  51. '''
  52. if boundary == None:
  53. boundary = self.solid_geometry.envelope
  54. return boundary.difference(g.solid_geometry)
  55. def clear_polygon(self, polygon, tooldia, overlap = 0.15):
  56. '''
  57. Creates geometry inside a polygon for a tool to cover
  58. the whole area.
  59. '''
  60. poly_cuts = [polygon.buffer(-tooldia/2.0)]
  61. while(1):
  62. polygon = poly_cuts[-1].buffer(-tooldia*(1-overlap))
  63. if polygon.area > 0:
  64. poly_cuts.append(polygon)
  65. else:
  66. break
  67. return poly_cuts
  68. class Gerber (Geometry):
  69. def __init__(self):
  70. # Initialize parent
  71. Geometry.__init__(self)
  72. # Number format
  73. self.digits = 3
  74. self.fraction = 4
  75. ## Gerber elements ##
  76. # Apertures {'id':{'type':chr,
  77. # ['size':float], ['width':float],
  78. # ['height':float]}, ...}
  79. self.apertures = {}
  80. # Paths [{'linestring':LineString, 'aperture':dict}]
  81. self.paths = []
  82. # Buffered Paths [Polygon]
  83. # Paths transformed into Polygons by
  84. # offsetting the aperture size/2
  85. self.buffered_paths = []
  86. # Polygon regions [{'polygon':Polygon, 'aperture':dict}]
  87. self.regions = []
  88. # Flashes [{'loc':[float,float], 'aperture':dict}]
  89. self.flashes = []
  90. # Geometry from flashes
  91. self.flash_geometry = []
  92. def fix_regions(self):
  93. '''
  94. Overwrites the region polygons with fixed
  95. versions if found to be invalid (according to Shapely).
  96. '''
  97. for region in self.regions:
  98. if region['polygon'].is_valid == False:
  99. #polylist = fix_poly(region['polygon'])
  100. #region['polygon'] = fix_poly3(polylist)
  101. region['polygon'] = region['polygon'].buffer(0)
  102. def buffer_paths(self):
  103. self.buffered_paths = []
  104. for path in self.paths:
  105. width = self.apertures[path["aperture"]]["size"]
  106. self.buffered_paths.append(path["linestring"].buffer(width/2))
  107. def aperture_parse(self, gline):
  108. '''
  109. Parse gerber aperture definition
  110. into dictionary of apertures.
  111. '''
  112. indexstar = gline.find("*")
  113. indexC = gline.find("C,")
  114. if indexC != -1: # Circle, example: %ADD11C,0.1*%
  115. apid = gline[4:indexC]
  116. self.apertures[apid] = {"type":"C",
  117. "size":float(gline[indexC+2:indexstar])}
  118. return apid
  119. indexR = gline.find("R,")
  120. if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
  121. apid = gline[4:indexR]
  122. indexX = gline.find("X")
  123. self.apertures[apid] = {"type":"R",
  124. "width":float(gline[indexR+2:indexX]),
  125. "height":float(gline[indexX+1:indexstar])}
  126. return apid
  127. indexO = gline.find("O,")
  128. if indexO != -1: # Obround
  129. apid = gline[4:indexO]
  130. indexX = gline.find("X")
  131. self.apertures[apid] = {"type":"O",
  132. "width":float(gline[indexO+2:indexX]),
  133. "height":float(gline[indexX+1:indexstar])}
  134. return apid
  135. print "WARNING: Aperture not implemented:", gline
  136. return None
  137. def parse_file(self, filename):
  138. '''
  139. Calls Gerber.parse_lines() with array of lines
  140. read from the given file.
  141. '''
  142. gfile = open(filename, 'r')
  143. gstr = gfile.readlines()
  144. gfile.close()
  145. self.parse_lines(gstr)
  146. def parse_lines(self, glines):
  147. '''
  148. Main Gerber parser.
  149. '''
  150. path = [] # Coordinates of the current path
  151. last_path_aperture = None
  152. current_aperture = None
  153. for gline in glines:
  154. if gline.find("D01*") != -1: # pen down
  155. path.append(coord(gline, self.digits, self.fraction))
  156. last_path_aperture = current_aperture
  157. continue
  158. if gline.find("D02*") != -1: # pen up
  159. if len(path) > 1:
  160. # Path completed, create shapely LineString
  161. self.paths.append({"linestring":LineString(path),
  162. "aperture":last_path_aperture})
  163. path = [coord(gline, self.digits, self.fraction)]
  164. continue
  165. indexD3 = gline.find("D03*")
  166. if indexD3 > 0: # Flash
  167. self.flashes.append({"loc":coord(gline, self.digits, self.fraction),
  168. "aperture":current_aperture})
  169. continue
  170. if indexD3 == 0: # Flash?
  171. print "WARNING: Uninplemented flash style:", gline
  172. continue
  173. if gline.find("G37*") != -1: # end region
  174. # Only one path defines region?
  175. self.regions.append({"polygon":Polygon(path),
  176. "aperture":last_path_aperture})
  177. path = []
  178. continue
  179. if gline.find("%ADD") != -1: # aperture definition
  180. self.aperture_parse(gline) # adds element to apertures
  181. continue
  182. indexstar = gline.find("*")
  183. if gline.find("D") == 0: # Aperture change
  184. current_aperture = gline[1:indexstar]
  185. continue
  186. if gline.find("G54D") == 0: # Aperture change (deprecated)
  187. current_aperture = gline[4:indexstar]
  188. continue
  189. if gline.find("%FS") != -1: # Format statement
  190. indexX = gline.find("X")
  191. self.digits = int(gline[indexX + 1])
  192. self.fraction = int(gline[indexX + 2])
  193. continue
  194. print "WARNING: Line ignored:", gline
  195. if len(path) > 1:
  196. # EOF, create shapely LineString if something in path
  197. self.paths.append({"linestring":LineString(path),
  198. "aperture":last_path_aperture})
  199. def do_flashes(self):
  200. self.flash_geometry = []
  201. for flash in self.flashes:
  202. aperture = self.apertures[flash['aperture']]
  203. if aperture['type'] == 'C': # Circles
  204. circle = Point(flash['loc']).buffer(aperture['size']/2)
  205. self.flash_geometry.append(circle)
  206. continue
  207. if aperture['type'] == 'R': # Rectangles
  208. loc = flash['loc']
  209. width = aperture['width']
  210. height = aperture['height']
  211. minx = loc[0] - width/2
  212. maxx = loc[0] + width/2
  213. miny = loc[1] - height/2
  214. maxy = loc[1] + height/2
  215. rectangle = shply_box(minx, miny, maxx, maxy)
  216. self.flash_geometry.append(rectangle)
  217. continue
  218. #TODO: Add support for type='O'
  219. print "WARNING: Aperture type %s not implemented"%(aperture['type'])
  220. def create_geometry(self):
  221. if len(self.buffered_paths) == 0:
  222. self.buffer_paths()
  223. self.fix_regions()
  224. self.do_flashes()
  225. self.solid_geometry = cascaded_union(
  226. self.buffered_paths +
  227. [poly['polygon'] for poly in self.regions] +
  228. self.flash_geometry)
  229. class CNCjob:
  230. def __init__(self, units="in", kind="generic", z_move = 0.1,
  231. feedrate = 3.0, z_cut = -0.002):
  232. # Options
  233. self.kind = kind
  234. self.units = units
  235. self.z_cut = z_cut
  236. self.z_move = z_move
  237. self.feedrate = feedrate
  238. # Constants
  239. self.unitcode = {"in": "G20", "mm": "G21"}
  240. self.pausecode = "G04 P1"
  241. self.feedminutecode = "G94"
  242. self.absolutecode = "G90"
  243. # Output G-Code
  244. self.gcode = ""
  245. # Bounds of geometry given to CNCjob.generate_from_geometry()
  246. self.input_geometry_bounds = None
  247. # Tool diameter given to CNCjob.generate_from_geometry()
  248. self.tooldia = 0
  249. # Output generated by CNCjob.create_gcode_geometry()
  250. self.G_geometry = None
  251. def generate_from_excellon(self, exobj):
  252. '''
  253. Generates G-code for drilling from excellon text.
  254. self.gcode becomes a list, each element is a
  255. different job for each tool in the excellon code.
  256. '''
  257. self.kind = "drill"
  258. self.gcode = []
  259. t = "G00 X%.4fY%.4f\n"
  260. down = "G01 Z%.4f\n"%self.z_cut
  261. up = "G01 Z%.4f\n"%self.z_move
  262. for tool in exobj.tools:
  263. points = []
  264. gcode = ""
  265. for drill in exobj.drill:
  266. if drill['tool'] == tool:
  267. points.append(drill['point'])
  268. gcode = self.unitcode[self.units] + "\n"
  269. gcode += self.absolutecode + "\n"
  270. gcode += self.feedminutecode + "\n"
  271. gcode += "F%.2f\n"%self.feedrate
  272. gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height
  273. gcode += "M03\n" # Spindle start
  274. gcode += self.pausecode + "\n"
  275. for point in points:
  276. gcode += t%point
  277. gcode += down + up
  278. gcode += t%(0,0)
  279. gcode += "M05\n" # Spindle stop
  280. self.gcode.append(gcode)
  281. def generate_from_geometry(self, geometry, append=True, tooldia=None):
  282. '''
  283. Generates G-Code for geometry (Shapely collection).
  284. '''
  285. if tooldia == None:
  286. tooldia = self.tooldia
  287. else:
  288. self.tooldia = tooldia
  289. self.input_geometry_bounds = geometry.bounds
  290. if append == False:
  291. self.gcode = ""
  292. t = "G0%d X%.4fY%.4f\n"
  293. self.gcode = self.unitcode[self.units] + "\n"
  294. self.gcode += self.absolutecode + "\n"
  295. self.gcode += self.feedminutecode + "\n"
  296. self.gcode += "F%.2f\n"%self.feedrate
  297. self.gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height
  298. self.gcode += "M03\n" # Spindle start
  299. self.gcode += self.pausecode + "\n"
  300. for geo in geometry:
  301. if type(geo) == Polygon:
  302. path = list(geo.exterior.coords) # Polygon exterior
  303. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  304. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  305. for pt in path[1:]:
  306. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  307. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  308. for ints in geo.interiors: # Polygon interiors
  309. path = list(ints.coords)
  310. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  311. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  312. for pt in path[1:]:
  313. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  314. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  315. continue
  316. if type(geo) == LineString or type(geo) == LineRing:
  317. path = list(geo.coords)
  318. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  319. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  320. for pt in path[1:]:
  321. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  322. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  323. continue
  324. if type(geo) == Point:
  325. path = list(geo.coords)
  326. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  327. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  328. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  329. continue
  330. print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
  331. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  332. self.gcode += "G00 X0Y0\n"
  333. self.gcode += "M05\n" # Spindle stop
  334. def create_gcode_geometry(self):
  335. '''
  336. G-Code parser (from self.gcode). Generates dictionary with
  337. single-segment LineString's and "kind" indicating cut or travel,
  338. fast or feedrate speed.
  339. '''
  340. geometry = []
  341. # TODO: ???? bring this into the class??
  342. gobjs = gparse1b(self.gcode)
  343. # Last known instruction
  344. current = {'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'G': 0}
  345. # Process every instruction
  346. for gobj in gobjs:
  347. if 'Z' in gobj:
  348. if ('X' in gobj or 'Y' in gobj) and gobj['Z'] != current['Z']:
  349. print "WARNING: Non-orthogonal motion: From", current
  350. print " To:", gobj
  351. current['Z'] = gobj['Z']
  352. if 'G' in gobj:
  353. current['G'] = gobj['G']
  354. if 'X' in gobj or 'Y' in gobj:
  355. x = 0
  356. y = 0
  357. kind = ["C","F"] # T=travel, C=cut, F=fast, S=slow
  358. if 'X' in gobj:
  359. x = gobj['X']
  360. else:
  361. x = current['X']
  362. if 'Y' in gobj:
  363. y = gobj['Y']
  364. else:
  365. y = current['Y']
  366. if current['Z'] > 0:
  367. kind[0] = 'T'
  368. if current['G'] == 1:
  369. kind[1] = 'S'
  370. geometry.append({'geom':LineString([(current['X'],current['Y']),
  371. (x,y)]), 'kind':kind})
  372. # Update current instruction
  373. for code in gobj:
  374. current[code] = gobj[code]
  375. self.G_geometry = geometry
  376. return geometry
  377. def plot(self, tooldia=None, dpi=75, margin=0.1,
  378. color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
  379. alpha={"T":0.3, "C":1.0}):
  380. '''
  381. Creates a Matplotlib figure with a plot of the
  382. G-code job.
  383. '''
  384. if tooldia == None:
  385. tooldia = self.tooldia
  386. fig = Figure(dpi=dpi)
  387. ax = fig.add_subplot(111)
  388. ax.set_aspect(1)
  389. xmin, ymin, xmax, ymax = self.input_geometry_bounds
  390. ax.set_xlim(xmin-margin, xmax+margin)
  391. ax.set_ylim(ymin-margin, ymax+margin)
  392. if tooldia == 0:
  393. for geo in self.G_geometry:
  394. linespec = '--'
  395. linecolor = color[geo['kind'][0]][1]
  396. if geo['kind'][0] == 'C':
  397. linespec = 'k-'
  398. x, y = geo['geom'].coords.xy
  399. ax.plot(x, y, linespec, color=linecolor)
  400. else:
  401. for geo in self.G_geometry:
  402. poly = geo['geom'].buffer(tooldia/2.0)
  403. patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
  404. edgecolor=color[geo['kind'][0]][1],
  405. alpha=alpha[geo['kind'][0]], zorder=2)
  406. ax.add_patch(patch)
  407. return fig
  408. class Excellon(Geometry):
  409. def __init__(self):
  410. Geometry.__init__(self)
  411. self.tools = {}
  412. self.drills = []
  413. def parse_file(self, filename):
  414. efile = open(filename, 'r')
  415. estr = efile.readlines()
  416. efile.close()
  417. self.parse_lines(estr)
  418. def parse_lines(self, elines):
  419. '''
  420. Main Excellon parser.
  421. '''
  422. current_tool = ""
  423. for eline in elines:
  424. ## Tool definitions ##
  425. # TODO: Verify all this
  426. indexT = eline.find("T")
  427. indexC = eline.find("C")
  428. indexF = eline.find("F")
  429. # Type 1
  430. if indexT != -1 and indexC > indexT and indexF > indexF:
  431. tool = eline[1:indexC]
  432. spec = eline[indexC+1:indexF]
  433. self.tools[tool] = spec
  434. continue
  435. # Type 2
  436. # TODO: Is this inches?
  437. #indexsp = eline.find(" ")
  438. #indexin = eline.find("in")
  439. #if indexT != -1 and indexsp > indexT and indexin > indexsp:
  440. # tool = eline[1:indexsp]
  441. # spec = eline[indexsp+1:indexin]
  442. # self.tools[tool] = spec
  443. # continue
  444. # Type 3
  445. if indexT != -1 and indexC > indexT:
  446. tool = eline[1:indexC]
  447. spec = eline[indexC+1:-1]
  448. self.tools[tool] = spec
  449. continue
  450. ## Tool change
  451. if indexT == 0:
  452. current_tool = eline[1:-1]
  453. continue
  454. ## Drill
  455. indexX = eline.find("X")
  456. indexY = eline.find("Y")
  457. if indexX != -1 and indexY != -1:
  458. x = float(int(eline[indexX+1:indexY])/10000.0)
  459. y = float(int(eline[indexY+1:-1])/10000.0)
  460. self.drills.append({'point':Point((x,y)), 'tool':current_tool})
  461. continue
  462. print "WARNING: Line ignored:", eline
  463. def create_geometry(self):
  464. self.solid_geometry = []
  465. sizes = {}
  466. for tool in self.tools:
  467. sizes[tool] = float(self.tools[tool])
  468. for drill in self.drills:
  469. poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0)
  470. self.solid_geometry.append(poly)
  471. self.solid_geometry = cascaded_union(self.solid_geometry)
  472. class motion:
  473. '''
  474. Represents a machine motion, which can be cutting or just travelling.
  475. '''
  476. def __init__(self, start, end, depth, typ='line', offset=None, center=None,
  477. radius=None, tooldia=0.5):
  478. self.typ = typ
  479. self.start = start
  480. self.end = end
  481. self.depth = depth
  482. self.center = center
  483. self.radius = radius
  484. self.tooldia = tooldia
  485. self.offset = offset # (I, J)
  486. def gparse1(filename):
  487. '''
  488. Parses G-code file into list of dictionaries like
  489. Examples: {'G': 1.0, 'X': 0.085, 'Y': -0.125},
  490. {'G': 3.0, 'I': -0.01, 'J': 0.0, 'X': 0.0821, 'Y': -0.1179}
  491. '''
  492. f = open(filename)
  493. gcmds = []
  494. for line in f:
  495. line = line.strip()
  496. # Remove comments
  497. # NOTE: Limited to 1 bracket pair
  498. op = line.find("(")
  499. cl = line.find(")")
  500. if op > -1 and cl > op:
  501. #comment = line[op+1:cl]
  502. line = line[:op] + line[(cl+1):]
  503. # Parse GCode
  504. # 0 4 12
  505. # G01 X-0.007 Y-0.057
  506. # --> codes_idx = [0, 4, 12]
  507. codes = "NMGXYZIJFP"
  508. codes_idx = []
  509. i = 0
  510. for ch in line:
  511. if ch in codes:
  512. codes_idx.append(i)
  513. i += 1
  514. n_codes = len(codes_idx)
  515. if n_codes == 0:
  516. continue
  517. # Separate codes in line
  518. parts = []
  519. for p in range(n_codes-1):
  520. parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
  521. parts.append( line[codes_idx[-1]:].strip() )
  522. # Separate codes from values
  523. cmds = {}
  524. for part in parts:
  525. cmds[part[0]] = float(part[1:])
  526. gcmds.append(cmds)
  527. f.close()
  528. return gcmds
  529. def gparse1b(gtext):
  530. gcmds = []
  531. lines = gtext.split("\n")
  532. for line in lines:
  533. line = line.strip()
  534. # Remove comments
  535. # NOTE: Limited to 1 bracket pair
  536. op = line.find("(")
  537. cl = line.find(")")
  538. if op > -1 and cl > op:
  539. #comment = line[op+1:cl]
  540. line = line[:op] + line[(cl+1):]
  541. # Parse GCode
  542. # 0 4 12
  543. # G01 X-0.007 Y-0.057
  544. # --> codes_idx = [0, 4, 12]
  545. codes = "NMGXYZIJFP"
  546. codes_idx = []
  547. i = 0
  548. for ch in line:
  549. if ch in codes:
  550. codes_idx.append(i)
  551. i += 1
  552. n_codes = len(codes_idx)
  553. if n_codes == 0:
  554. continue
  555. # Separate codes in line
  556. parts = []
  557. for p in range(n_codes-1):
  558. parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
  559. parts.append( line[codes_idx[-1]:].strip() )
  560. # Separate codes from values
  561. cmds = {}
  562. for part in parts:
  563. cmds[part[0]] = float(part[1:])
  564. gcmds.append(cmds)
  565. return gcmds
  566. def gparse2(gcmds):
  567. x = []
  568. y = []
  569. z = []
  570. xypoints = []
  571. motions = []
  572. current_g = None
  573. for cmds in gcmds:
  574. # Destination point
  575. x_ = None
  576. y_ = None
  577. z_ = None
  578. if 'X' in cmds:
  579. x_ = cmds['X']
  580. x.append(x_)
  581. if 'Y' in cmds:
  582. y_ = cmds['Y']
  583. y.append(y_)
  584. if 'Z' in cmds:
  585. z_ = cmds['Z']
  586. z.append(z_)
  587. # Ingnore anything but XY movements from here on
  588. if x_ is None and y_ is None:
  589. #print "-> no x,y"
  590. continue
  591. if x_ is None:
  592. x_ = xypoints[-1][0]
  593. if y_ is None:
  594. y_ = xypoints[-1][1]
  595. if z_ is None:
  596. z_ = z[-1]
  597. mot = None
  598. if 'G' in cmds:
  599. current_g = cmds['G']
  600. if current_g == 0: # Fast linear
  601. if len(xypoints) > 0:
  602. #print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
  603. mot = motion(xypoints[-1], (x_, y_), z_)
  604. if current_g == 1: # Feed-rate linear
  605. if len(xypoints) > 0:
  606. #print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
  607. mot = motion(xypoints[-1], (x_, y_), z_)
  608. if current_g == 2: # Clockwise arc
  609. if len(xypoints) > 0:
  610. if 'I' in cmds and 'J' in cmds:
  611. mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
  612. cmds['J']), typ='arccw')
  613. if current_g == 3: # Counter-clockwise arc
  614. if len(xypoints) > 0:
  615. if 'I' in cmds and 'J' in cmds:
  616. mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
  617. cmds['J']), typ='arcacw')
  618. if mot is not None:
  619. motions.append(mot)
  620. xypoints.append((x_, y_))
  621. x = array(x)
  622. y = array(y)
  623. z = array(z)
  624. xmin = min(x)
  625. xmax = max(x)
  626. ymin = min(y)
  627. ymax = max(y)
  628. print "x:", min(x), max(x)
  629. print "y:", min(y), max(y)
  630. print "z:", min(z), max(z)
  631. print xypoints[-1]
  632. return xmin, xmax, ymin, ymax, motions
  633. ############### cam.py ####################
  634. def coord(gstr,digits,fraction):
  635. '''
  636. Parse Gerber coordinates
  637. '''
  638. global gerbx, gerby
  639. xindex = gstr.find("X")
  640. yindex = gstr.find("Y")
  641. index = gstr.find("D")
  642. if (xindex == -1):
  643. x = gerbx
  644. y = int(gstr[(yindex+1):index])*(10**(-fraction))
  645. elif (yindex == -1):
  646. y = gerby
  647. x = int(gstr[(xindex+1):index])*(10**(-fraction))
  648. else:
  649. x = int(gstr[(xindex+1):yindex])*(10**(-fraction))
  650. y = int(gstr[(yindex+1):index])*(10**(-fraction))
  651. gerbx = x
  652. gerby = y
  653. return [x,y]
  654. ################ end of cam.py #############