camlib.py 82 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358
  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. class Gerber (Geometry):
  47. def __init__(self):
  48. # Initialize parent
  49. Geometry.__init__(self)
  50. # Number format
  51. self.digits = 3
  52. self.fraction = 4
  53. ## Gerber elements ##
  54. # Apertures {'id':{'type':chr,
  55. # ['size':float], ['width':float],
  56. # ['height':float]}, ...}
  57. self.apertures = {}
  58. # Paths [{'linestring':LineString, 'aperture':dict}]
  59. self.paths = []
  60. # Buffered Paths [Polygon]
  61. # Paths transformed into Polygons by
  62. # offsetting the aperture size/2
  63. self.buffered_paths = []
  64. # Polygon regions [{'polygon':Polygon, 'aperture':dict}]
  65. self.regions = []
  66. # Flashes [{'loc':[float,float], 'aperture':dict}]
  67. self.flashes = []
  68. def fix_regions(self):
  69. '''
  70. Overwrites the region polygons with fixed
  71. versions if found to be invalid (according to Shapely).
  72. '''
  73. for region in self.regions:
  74. if region['polygon'].is_valid == False:
  75. #polylist = fix_poly(region['polygon'])
  76. #region['polygon'] = fix_poly3(polylist)
  77. region['polygon'] = region['polygon'].buffer(0)
  78. def buffer_paths(self):
  79. self.buffered_paths = []
  80. for path in self.paths:
  81. width = self.apertures[path["aperture"]]["size"]
  82. self.buffered_paths.append(path["linestring"].buffer(width/2))
  83. def aperture_parse(self, gline):
  84. '''
  85. Parse gerber aperture definition
  86. into dictionary of apertures.
  87. '''
  88. indexstar = gline.find("*")
  89. indexC = gline.find("C,")
  90. if indexC != -1: # Circle, example: %ADD11C,0.1*%
  91. apid = gline[4:indexC]
  92. self.apertures[apid] = {"type":"C",
  93. "size":float(gline[indexC+2:indexstar])}
  94. return apid
  95. indexR = gline.find("R,")
  96. if indexR != -1: # Rectangle, example: %ADD15R,0.05X0.12*%
  97. apid = gline[4:indexR]
  98. indexX = gline.find("X")
  99. self.apertures[apid] = {"type":"R",
  100. "width":float(gline[indexR+2:indexX]),
  101. "height":float(gline[indexX+1:indexstar])}
  102. return apid
  103. indexO = gline.find("O,")
  104. if indexO != -1: # Obround
  105. apid = gline[4:indexO]
  106. indexX = gline.find("X")
  107. self.apertures[apid] = {"type":"O",
  108. "width":float(gline[indexO+2:indexX]),
  109. "height":float(gline[indexX+1:indexstar])}
  110. return apid
  111. print "WARNING: Aperture not implemented:", gline
  112. return None
  113. def parse_file(self, filename):
  114. gfile = open(filename, 'r')
  115. gstr = gfile.readlines()
  116. gfile.close()
  117. self.parse_lines(gstr)
  118. def parse_lines(self, glines):
  119. '''
  120. Main Gerber parser.
  121. '''
  122. path = [] # Coordinates of the current path
  123. last_path_aperture = None
  124. current_aperture = None
  125. for gline in glines:
  126. if gline.find("D01*") != -1: # pen down
  127. path.append(coord(gline, self.digits, self.fraction))
  128. last_path_aperture = current_aperture
  129. continue
  130. if gline.find("D02*") != -1: # pen up
  131. if len(path) > 1:
  132. # Path completed, create shapely LineString
  133. self.paths.append({"linestring":LineString(path),
  134. "aperture":last_path_aperture})
  135. path = [coord(gline, self.digits, self.fraction)]
  136. continue
  137. indexD3 = gline.find("D03*")
  138. if indexD3 > 0: # Flash
  139. self.flashes.append({"loc":coord(gline, self.digits, self.fraction),
  140. "aperture":current_aperture})
  141. continue
  142. if indexD3 == 0: # Flash?
  143. print "WARNING: Uninplemented flash style:", gline
  144. continue
  145. if gline.find("G37*") != -1: # end region
  146. # Only one path defines region?
  147. self.regions.append({"polygon":Polygon(path),
  148. "aperture":last_path_aperture})
  149. path = []
  150. continue
  151. if gline.find("%ADD") != -1: # aperture definition
  152. self.aperture_parse(gline) # adds element to apertures
  153. continue
  154. indexstar = gline.find("*")
  155. if gline.find("D") == 0: # Aperture change
  156. current_aperture = gline[1:indexstar]
  157. continue
  158. if gline.find("G54D") == 0: # Aperture change (deprecated)
  159. current_aperture = gline[4:indexstar]
  160. continue
  161. if gline.find("%FS") != -1: # Format statement
  162. indexX = gline.find("X")
  163. self.digits = int(gline[indexX + 1])
  164. self.fraction = int(gline[indexX + 2])
  165. continue
  166. print "WARNING: Line ignored:", gline
  167. if len(path) > 1:
  168. # EOF, create shapely LineString if something in path
  169. self.paths.append({"linestring":LineString(path),
  170. "aperture":last_path_aperture})
  171. def create_geometry(self):
  172. if len(self.buffered_paths) == 0:
  173. self.buffer_paths()
  174. self.fix_regions()
  175. flash_polys = []
  176. for flash in self.flashes:
  177. aperture = self.apertures[flash['aperture']]
  178. if aperture['type'] == 'C': # Circles
  179. circle = Point(flash['loc']).buffer(aperture['size']/2)
  180. flash_polys.append(circle)
  181. continue
  182. if aperture['type'] == 'R': # Rectangles
  183. loc = flash['loc']
  184. width = aperture['width']
  185. height = aperture['height']
  186. minx = loc[0] - width/2
  187. maxx = loc[0] + width/2
  188. miny = loc[1] - height/2
  189. maxy = loc[1] + height/2
  190. rectangle = shply_box(minx, miny, maxx, maxy)
  191. flash_polys.append(rectangle)
  192. continue
  193. print "WARNING: Aperture type %s not implemented"%(aperture['type'])
  194. #TODO: Add support for type='O'
  195. self.solid_geometry = cascaded_union(
  196. self.buffered_paths +
  197. [poly['polygon'] for poly in self.regions] +
  198. flash_polys)
  199. class CNCjob():
  200. def __init__(self, units="in", kind="generic", z_move = 0.1,
  201. feedrate = 3.0, z_cut = -0.002):
  202. # Options
  203. self.kind = kind
  204. self.units = units
  205. self.z_cut = z_cut
  206. self.z_move = z_move
  207. self.feedrate = feedrate
  208. # Constants
  209. self.unitcode = {"in": "G20", "mm": "G21"}
  210. self.pausecode = "G04 P1"
  211. self.feedminutecode = "G94"
  212. self.absolutecode = "G90"
  213. # Output G-Code
  214. self.gcode = ""
  215. # Bounds of geometry given to CNCjob.generate_from_geometry()
  216. self.input_geometry_bounds = None
  217. # Tool diameter given to CNCjob.generate_from_geometry()
  218. self.tooldia = 0
  219. # Output generated by CNCjob.create_gcode_geometry()
  220. self.G_geometry = None
  221. def generate_from_geometry(self, geometry, append=True, tooldia=None):
  222. '''
  223. Generates G-Code for geometry (Shapely collection).
  224. '''
  225. if tooldia == None:
  226. tooldia = self.tooldia
  227. else:
  228. self.tooldia = tooldia
  229. self.input_geometry_bounds = geometry.bounds
  230. if append == False:
  231. self.gcode = ""
  232. t = "G0%d X%.4fY%.4f\n"
  233. self.gcode = self.unitcode[self.units] + "\n"
  234. self.gcode += self.absolutecode + "\n"
  235. self.gcode += self.feedminutecode + "\n"
  236. self.gcode += "F%.2f\n"%self.feedrate
  237. self.gcode += "G00 Z%.4f\n"%self.z_move # Move to travel height
  238. self.gcode += "M03\n" # Spindle start
  239. self.gcode += self.pausecode + "\n"
  240. for geo in geometry:
  241. if type(geo) == Polygon:
  242. path = list(geo.exterior.coords) # Polygon exterior
  243. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  244. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  245. for pt in path[1:]:
  246. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  247. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  248. for ints in geo.interiors: # Polygon interiors
  249. path = list(ints.coords)
  250. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  251. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  252. for pt in path[1:]:
  253. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  254. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  255. continue
  256. if type(geo) == LineString or type(geo) == LineRing:
  257. path = list(geo.coords)
  258. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  259. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  260. for pt in path[1:]:
  261. self.gcode += t%(1, pt[0], pt[1]) # Linear motion to point
  262. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  263. continue
  264. if type(geo) == Point:
  265. path = list(geo.coords)
  266. self.gcode += t%(0, path[0][0], path[0][1]) # Move to first point
  267. self.gcode += "G01 Z%.4f\n"%self.z_cut # Start cutting
  268. self.gcode += "G00 Z%.4f\n"%self.z_move # Stop cutting
  269. continue
  270. print "WARNING: G-code generation not implemented for %s"%(str(type(geo)))
  271. self.gcode += "M05\n" # Spindle stop
  272. def create_gcode_geometry(self):
  273. '''
  274. G-Code parser. Generates dictionary with single-segment LineString's
  275. and "kind" indicating cut or travel, fast or feedrate speed.
  276. '''
  277. geometry = []
  278. gobjs = gparse1b(self.gcode)
  279. # Last known instruction
  280. current = {'X': 0.0, 'Y': 0.0, 'Z': 0.0, 'G': 0}
  281. # Process every instruction
  282. for gobj in gobjs:
  283. if 'Z' in gobj:
  284. if ('X' in gobj or 'Y' in gobj) and gobj['Z'] != current['Z']:
  285. print "WARNING: Non-orthogonal motion: From", current
  286. print " To:", gobj
  287. current['Z'] = gobj['Z']
  288. if 'G' in gobj:
  289. current['G'] = gobj['G']
  290. if 'X' in gobj or 'Y' in gobj:
  291. x = 0
  292. y = 0
  293. kind = ["C","F"] # T=travel, C=cut, F=fast, S=slow
  294. if 'X' in gobj:
  295. x = gobj['X']
  296. else:
  297. x = current['X']
  298. if 'Y' in gobj:
  299. y = gobj['Y']
  300. else:
  301. y = current['Y']
  302. if current['Z'] > 0:
  303. kind[0] = 'T'
  304. if current['G'] == 1:
  305. kind[1] = 'S'
  306. geometry.append({'geom':LineString([(current['X'],current['Y']),
  307. (x,y)]), 'kind':kind})
  308. # Update current instruction
  309. for code in gobj:
  310. current[code] = gobj[code]
  311. self.G_geometry = geometry
  312. return geometry
  313. def plot(self, tooldia=None, dpi=75, margin=0.1,
  314. color={"T":["#F0E24D", "#B5AB3A"], "C":["#5E6CFF", "#4650BD"]},
  315. alpha={"T":0.3, "C":1.0}):
  316. '''
  317. Creates a Matplotlib figure with a plot of the
  318. G-code job.
  319. '''
  320. if tooldia == None:
  321. tooldia = self.tooldia
  322. fig = Figure(dpi=dpi)
  323. ax = fig.add_subplot(111)
  324. ax.set_aspect(1)
  325. xmin, ymin, xmax, ymax = self.input_geometry_bounds
  326. ax.set_xlim(xmin-margin, xmax+margin)
  327. ax.set_ylim(ymin-margin, ymax+margin)
  328. if tooldia == 0:
  329. for geo in self.G_geometry:
  330. linespec = '--'
  331. linecolor = color[geo['kind'][0]][1]
  332. if geo['kind'][0] == 'C':
  333. linespec = 'k-'
  334. x, y = geo['geom'].coords.xy
  335. ax.plot(x, y, linespec, color=linecolor)
  336. else:
  337. for geo in self.G_geometry:
  338. poly = geo['geom'].buffer(tooldia/2.0)
  339. patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0],
  340. edgecolor=color[geo['kind'][0]][1],
  341. alpha=alpha[geo['kind'][0]], zorder=2)
  342. ax.add_patch(patch)
  343. return fig
  344. def fix_poly(poly):
  345. '''
  346. Fixes polygons with internal cutouts by identifying
  347. loops and touching segments. Loops are extracted
  348. as individual polygons. If a smaller loop is still
  349. not a valid Polygon, fix_poly2() adds vertices such
  350. that fix_poly() can continue to extract smaller loops.
  351. '''
  352. if poly.is_valid: # Nothing to do
  353. return [poly]
  354. coords = poly.exterior.coords[:]
  355. n_points = len(coords)
  356. i = 0
  357. result = []
  358. while i<n_points:
  359. if coords[i] in coords[:i]: # closed a loop
  360. j = coords[:i].index(coords[i]) # index of repeated point
  361. if i-j>1: # points do not repeat in 1 step
  362. sub_poly = Polygon(coords[j:i+1])
  363. if sub_poly.is_valid:
  364. result.append(sub_poly)
  365. elif sub_poly.area > 0:
  366. sub_poly = fix_poly2(sub_poly)
  367. result += fix_poly(sub_poly) # try again
  368. # Preserve the repeated point such as not to break the
  369. # remaining geometry
  370. remaining = coords[:j+1]+coords[i+1:n_points+1]
  371. rem_poly = Polygon(remaining)
  372. if len(remaining)>2 and rem_poly.area > 0:
  373. result += fix_poly(rem_poly)
  374. break
  375. i += 1
  376. return result
  377. def fix_poly2(poly):
  378. coords = poly.exterior.coords[:]
  379. n_points = len(coords)
  380. ls = None
  381. i = 1
  382. while i<n_points:
  383. ls = LineString(coords[i-1:i+1])
  384. other_points = coords[:i-1]+coords[i+1:] # i=3 ... [0:2] + [4:]
  385. if ls.intersects(MultiPoint(other_points)):
  386. # Add a copy of that point to the segment
  387. isect = ls.intersection(MultiPoint(other_points))
  388. if type(isect) == Point:
  389. if isect.coords[0] != coords[i-1] and isect.coords[0] != coords[i]:
  390. coords = coords[:i] + [isect.coords[0]] + coords[i:]
  391. if type(isect) == MultiPoint:
  392. for p in isect:
  393. if p.coords[0] != coords[i-1] and p.coords[0] != coords[i]:
  394. coords = coords[:i] + [p.coords[0]] + coords[i:]
  395. return Polygon(coords)
  396. return Polygon(coords)
  397. def fix_poly3(polylist):
  398. mp = MultiPolygon(polylist)
  399. interior = None
  400. exterior = None
  401. for i in range(len(polylist)):
  402. if polylist[i].contains(mp):
  403. exterior = polylist[i]
  404. interior = polylist[:i]+polylist[i+1:]
  405. return Polygon(exterior.exterior.coords[:],
  406. [p.exterior.coords[:] for p in interior])
  407. class motion:
  408. '''
  409. Represents a machine motion, which can be cutting or just travelling.
  410. '''
  411. def __init__(self, start, end, depth, typ='line', offset=None, center=None,
  412. radius=None, tooldia=0.5):
  413. self.typ = typ
  414. self.start = start
  415. self.end = end
  416. self.depth = depth
  417. self.center = center
  418. self.radius = radius
  419. self.tooldia = tooldia
  420. self.offset = offset # (I, J)
  421. def gparse1(filename):
  422. '''
  423. Parses G-code file into list of dictionaries like
  424. Examples: {'G': 1.0, 'X': 0.085, 'Y': -0.125},
  425. {'G': 3.0, 'I': -0.01, 'J': 0.0, 'X': 0.0821, 'Y': -0.1179}
  426. '''
  427. f = open(filename)
  428. gcmds = []
  429. for line in f:
  430. line = line.strip()
  431. # Remove comments
  432. # NOTE: Limited to 1 bracket pair
  433. op = line.find("(")
  434. cl = line.find(")")
  435. if op > -1 and cl > op:
  436. #comment = line[op+1:cl]
  437. line = line[:op] + line[(cl+1):]
  438. # Parse GCode
  439. # 0 4 12
  440. # G01 X-0.007 Y-0.057
  441. # --> codes_idx = [0, 4, 12]
  442. codes = "NMGXYZIJFP"
  443. codes_idx = []
  444. i = 0
  445. for ch in line:
  446. if ch in codes:
  447. codes_idx.append(i)
  448. i += 1
  449. n_codes = len(codes_idx)
  450. if n_codes == 0:
  451. continue
  452. # Separate codes in line
  453. parts = []
  454. for p in range(n_codes-1):
  455. parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
  456. parts.append( line[codes_idx[-1]:].strip() )
  457. # Separate codes from values
  458. cmds = {}
  459. for part in parts:
  460. cmds[part[0]] = float(part[1:])
  461. gcmds.append(cmds)
  462. f.close()
  463. return gcmds
  464. def gparse1b(gtext):
  465. gcmds = []
  466. lines = gtext.split("\n")
  467. for line in lines:
  468. line = line.strip()
  469. # Remove comments
  470. # NOTE: Limited to 1 bracket pair
  471. op = line.find("(")
  472. cl = line.find(")")
  473. if op > -1 and cl > op:
  474. #comment = line[op+1:cl]
  475. line = line[:op] + line[(cl+1):]
  476. # Parse GCode
  477. # 0 4 12
  478. # G01 X-0.007 Y-0.057
  479. # --> codes_idx = [0, 4, 12]
  480. codes = "NMGXYZIJFP"
  481. codes_idx = []
  482. i = 0
  483. for ch in line:
  484. if ch in codes:
  485. codes_idx.append(i)
  486. i += 1
  487. n_codes = len(codes_idx)
  488. if n_codes == 0:
  489. continue
  490. # Separate codes in line
  491. parts = []
  492. for p in range(n_codes-1):
  493. parts.append( line[ codes_idx[p]:codes_idx[p+1] ].strip() )
  494. parts.append( line[codes_idx[-1]:].strip() )
  495. # Separate codes from values
  496. cmds = {}
  497. for part in parts:
  498. cmds[part[0]] = float(part[1:])
  499. gcmds.append(cmds)
  500. return gcmds
  501. def gparse2(gcmds):
  502. x = []
  503. y = []
  504. z = []
  505. xypoints = []
  506. motions = []
  507. current_g = None
  508. for cmds in gcmds:
  509. # Destination point
  510. x_ = None
  511. y_ = None
  512. z_ = None
  513. if 'X' in cmds:
  514. x_ = cmds['X']
  515. x.append(x_)
  516. if 'Y' in cmds:
  517. y_ = cmds['Y']
  518. y.append(y_)
  519. if 'Z' in cmds:
  520. z_ = cmds['Z']
  521. z.append(z_)
  522. # Ingnore anything but XY movements from here on
  523. if x_ is None and y_ is None:
  524. #print "-> no x,y"
  525. continue
  526. if x_ is None:
  527. x_ = xypoints[-1][0]
  528. if y_ is None:
  529. y_ = xypoints[-1][1]
  530. if z_ is None:
  531. z_ = z[-1]
  532. mot = None
  533. if 'G' in cmds:
  534. current_g = cmds['G']
  535. if current_g == 0: # Fast linear
  536. if len(xypoints) > 0:
  537. #print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
  538. mot = motion(xypoints[-1], (x_, y_), z_)
  539. if current_g == 1: # Feed-rate linear
  540. if len(xypoints) > 0:
  541. #print "motion(", xypoints[-1], ", (", x_, ",", y_, "),", z_, ")"
  542. mot = motion(xypoints[-1], (x_, y_), z_)
  543. if current_g == 2: # Clockwise arc
  544. if len(xypoints) > 0:
  545. if 'I' in cmds and 'J' in cmds:
  546. mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
  547. cmds['J']), typ='arccw')
  548. if current_g == 3: # Counter-clockwise arc
  549. if len(xypoints) > 0:
  550. if 'I' in cmds and 'J' in cmds:
  551. mot = motion(xypoints[-1], (x_, y_), z_, offset=(cmds['I'],
  552. cmds['J']), typ='arcacw')
  553. if mot is not None:
  554. motions.append(mot)
  555. xypoints.append((x_, y_))
  556. x = array(x)
  557. y = array(y)
  558. z = array(z)
  559. xmin = min(x)
  560. xmax = max(x)
  561. ymin = min(y)
  562. ymax = max(y)
  563. print "x:", min(x), max(x)
  564. print "y:", min(y), max(y)
  565. print "z:", min(z), max(z)
  566. print xypoints[-1]
  567. return xmin, xmax, ymin, ymax, motions
  568. class canvas:
  569. def __init__(self):
  570. self.surface = None
  571. self.context = None
  572. self.origin = [0, 0] # Pixel coordinate
  573. self.resolution = 200.0 # Pixels per unit.
  574. self.pixel_height = 0
  575. self.pixel_width = 0
  576. def create_surface(self, width, height):
  577. self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
  578. def crosshair(self, x, y, s):
  579. cr = self.context
  580. cr.set_line_width (1.0/self.resolution)
  581. cr.set_line_cap(cairo.LINE_CAP_ROUND)
  582. cr.set_source_rgba (1, 0, 0, 1)
  583. cr.move_to(x-s, y-s)
  584. cr.line_to(x+s, y+s)
  585. cr.stroke()
  586. cr.move_to(x-s, y+s)
  587. cr.line_to(x+s, y-s)
  588. cr.stroke()
  589. def draw_motions(self, motions, linewidth, margin=10):
  590. # Has many things in common with draw boundaries, merge.
  591. # Analyze motions
  592. X = 0
  593. Y = 1
  594. xmin = Inf
  595. xmax = -Inf
  596. ymin = Inf
  597. ymax = -Inf
  598. for mot in motions:
  599. if mot.start[X] < xmin:
  600. xmin = mot.start[X]
  601. if mot.end[X] < xmin:
  602. xmin = mot.end[X]
  603. if mot.start[X] > xmax:
  604. xmax = mot.end[X]
  605. if mot.end[X] > xmax:
  606. xmax = mot.end[X]
  607. if mot.start[Y] < ymin:
  608. ymin = mot.start[Y]
  609. if mot.end[Y] < ymin:
  610. ymin = mot.end[Y]
  611. if mot.start[Y] > ymax:
  612. ymax = mot.end[Y]
  613. if mot.end[Y] > ymax:
  614. ymax = mot.end[Y]
  615. width = xmax - xmin
  616. height = ymax - ymin
  617. print "x in", xmin, xmax
  618. print "y in", ymin, ymax
  619. print "width", width
  620. print "heigh", height
  621. # Create surface if it doesn't exist
  622. if self.surface == None:
  623. self.pixel_width = int(width*self.resolution + 2*margin)
  624. self.pixel_height = int(height*self.resolution + 2*margin)
  625. self.create_surface(self.pixel_width, self.pixel_height)
  626. self.origin = [int(-xmin*self.resolution + margin),
  627. int(-ymin*self.resolution + margin)]
  628. print "Created surface: %d x %d"%(self.pixel_width, self.pixel_height)
  629. print "Origin: %d, %d"%(self.origin[X], self.origin[Y])
  630. # Context
  631. # Flip and shift
  632. self.context = cairo.Context(self.surface)
  633. cr = self.context
  634. cr.set_source_rgb(0.9, 0.9, 0.9)
  635. cr.rectangle(0,0, self.pixel_width, self.pixel_height)
  636. cr.fill()
  637. cr.scale(self.resolution, -self.resolution)
  638. cr.translate(self.origin[X]/self.resolution,
  639. (-self.pixel_height+self.origin[Y])/self.resolution)
  640. # Draw
  641. cr.move_to(0,0)
  642. cr.set_line_width (linewidth)
  643. cr.set_line_cap(cairo.LINE_CAP_ROUND)
  644. n = len(motions)
  645. for i in range(0, n):
  646. #if motions[i].depth<0 and i>0 and motions[i-1].depth>0:
  647. if motions[i].depth <= 0:
  648. # change to cutting
  649. #print "x",
  650. # Draw previous travel
  651. cr.set_source_rgba (0.3, 0.2, 0.1, 1.0) # Solid color
  652. #cr.stroke()
  653. #if motions[i].depth >0 and i>0 and motions[i-1].depth<0:
  654. if motions[i].depth > 0:
  655. # change to cutting
  656. #print "-",
  657. # Draw previous cut
  658. cr.set_source_rgba (0.3, 0.2, 0.5, 0.2)
  659. #cr.stroke()
  660. if motions[i].typ == 'line':
  661. cr.move_to(motions[i].start[0], motions[i].start[1])
  662. cr.line_to(motions[i].end[0], motions[i].end[1])
  663. cr.stroke()
  664. #print 'cr.line_to(%f, %f)'%(motions[i].end[0], motions[i].end[0]),
  665. if motions[i].typ == 'arcacw':
  666. c = (motions[i].offset[0]+motions[i].start[0],
  667. motions[i].offset[1]+motions[i].start[1])
  668. r = sqrt(motions[i].offset[0]**2 + motions[i].offset[1]**2)
  669. ts = arctan2(-motions[i].offset[1], -motions[i].offset[0])
  670. te = arctan2(-c[1]+motions[i].end[1], -c[0]+motions[i].end[0])
  671. if te <= ts:
  672. te += 2*pi
  673. cr.arc(c[0], c[1], r, ts, te)
  674. cr.stroke()
  675. if motions[i].typ == 'arccw':
  676. c = (motions[i].offset[0]+motions[i].start[0],
  677. motions[i].offset[1]+motions[i].start[1])
  678. r = sqrt(motions[i].offset[0]**2 + motions[i].offset[1]**2)
  679. ts = arctan2(-motions[i].offset[1], -motions[i].offset[0])
  680. te = arctan2(-c[1]+motions[i].end[1], -c[0]+motions[i].end[0])
  681. if te <= ts:
  682. te += 2*pi
  683. cr.arc(c[0], c[1], r, te, ts)
  684. cr.stroke()
  685. def draw_boundaries(self, boundaries, linewidth, margin=10):
  686. '''
  687. margin Margin in pixels.
  688. '''
  689. # Analyze boundaries
  690. X = 0
  691. Y = 1
  692. #Z = 2
  693. xmin = Inf
  694. xmax = -Inf
  695. ymin = Inf
  696. ymax = -Inf
  697. for seg in boundaries[0]:
  698. for vertex in seg:
  699. try:
  700. if vertex[X] < xmin:
  701. xmin = vertex[X]
  702. if vertex[X] > xmax:
  703. xmax = vertex[X]
  704. if vertex[Y] < ymin:
  705. ymin = vertex[Y]
  706. if vertex[Y] > ymax:
  707. ymax = vertex[Y]
  708. except:
  709. print "Woops! vertex = [", [x for x in vertex], "]"
  710. width = xmax - xmin
  711. height = ymax - ymin
  712. print "x in", xmin, xmax
  713. print "y in", ymin, ymax
  714. print "width", width
  715. print "heigh", height
  716. # Create surface if it doesn't exist
  717. if self.surface == None:
  718. self.pixel_width = int(width*self.resolution + 2*margin)
  719. self.pixel_height = int(height*self.resolution + 2*margin)
  720. self.create_surface(self.pixel_width, self.pixel_height)
  721. self.origin = [int(-xmin*self.resolution + margin),
  722. int(-ymin*self.resolution + margin)]
  723. print "Created surface: %d x %d"%(self.pixel_width, self.pixel_height)
  724. print "Origin: %d, %d"%(self.origin[X], self.origin[Y])
  725. # Context
  726. # Flip and shift
  727. self.context = cairo.Context(self.surface)
  728. cr = self.context
  729. cr.set_source_rgb(0.9, 0.9, 0.9)
  730. cr.rectangle(0,0, self.pixel_width, self.pixel_height)
  731. cr.fill()
  732. cr.scale(self.resolution, -self.resolution)
  733. cr.translate(self.origin[X]/self.resolution,
  734. (-self.pixel_height+self.origin[Y])/self.resolution)
  735. # Draw
  736. cr.set_line_width (linewidth)
  737. cr.set_line_cap(cairo.LINE_CAP_ROUND)
  738. cr.set_source_rgba (0.3, 0.2, 0.5, 1)
  739. for seg in boundaries[0]:
  740. #print "segment"
  741. cr.move_to(seg[0][X],seg[0][Y])
  742. for i in range(1,len(seg)):
  743. #print seg[i][X],seg[i][Y]
  744. cr.line_to(seg[i][X], seg[i][Y])
  745. cr.stroke()
  746. def plotby(b, res, linewidth, ims=None):
  747. '''
  748. Creates a Cairo image object for the "boundarys" object
  749. generated by read_gerber().
  750. '''
  751. X = 0
  752. Y = 1
  753. xmin = Inf
  754. xmax = -Inf
  755. ymin = Inf
  756. ymax = -Inf
  757. for seg in b[0]:
  758. for vertex in seg:
  759. try:
  760. if vertex[X] < xmin:
  761. xmin = vertex[X]
  762. if vertex[X] > xmax:
  763. xmax = vertex[X]
  764. if vertex[Y] < ymin:
  765. ymin = vertex[Y]
  766. if vertex[Y] > ymax:
  767. ymax = vertex[Y]
  768. except:
  769. print "Woops! vertex = [", [x for x in vertex], "]"
  770. width = xmax - xmin
  771. height = ymax - ymin
  772. print "x in", xmin, xmax
  773. print "y in", ymin, ymax
  774. print "width", width
  775. print "heigh", height
  776. WIDTH = int((xmax-xmin)*res)
  777. HEIGHT = int((ymax-ymin)*res)
  778. # Create a new image if none given
  779. if ims == None:
  780. ims = cairo.ImageSurface(cairo.FORMAT_ARGB32, WIDTH, HEIGHT)
  781. cr = cairo.Context(ims)
  782. cr.scale(res, -res)
  783. #cr.scale(res, res)
  784. #cr.translate(0, -(ymax-ymin))
  785. #cr.translate(-xmin, -ymin)
  786. cr.translate(-xmin, -(ymax))
  787. cr.set_line_width (linewidth)
  788. cr.set_line_cap(cairo.LINE_CAP_ROUND)
  789. cr.set_source_rgba (0.3, 0.2, 0.5, 1)
  790. for seg in b[0]:
  791. #print "segment"
  792. cr.move_to(seg[0][X],seg[0][Y])
  793. for i in range(1,len(seg)):
  794. #print seg[i][X],seg[i][Y]
  795. cr.line_to(seg[i][X], seg[i][Y])
  796. cr.stroke()
  797. cr.scale(1,-1)
  798. cr.translate(-xmin, -(ymax))
  799. cr.set_source_rgba (1, 0, 0, 1)
  800. cr.select_font_face("Arial", cairo.FONT_SLANT_NORMAL,
  801. cairo.FONT_WEIGHT_NORMAL)
  802. cr.set_font_size(0.1)
  803. cr.move_to(0, 0)
  804. cr.show_text("(0,0)")
  805. cr.move_to(1, 1)
  806. cr.show_text("(1,1)")
  807. return ims
  808. ############### cam.py ####################
  809. def coord(gstr,digits,fraction):
  810. '''
  811. Parse Gerber coordinates
  812. '''
  813. global gerbx, gerby
  814. xindex = gstr.find("X")
  815. yindex = gstr.find("Y")
  816. index = gstr.find("D")
  817. if (xindex == -1):
  818. x = gerbx
  819. y = int(gstr[(yindex+1):index])*(10**(-fraction))
  820. elif (yindex == -1):
  821. y = gerby
  822. x = int(gstr[(xindex+1):index])*(10**(-fraction))
  823. else:
  824. x = int(gstr[(xindex+1):yindex])*(10**(-fraction))
  825. y = int(gstr[(yindex+1):index])*(10**(-fraction))
  826. gerbx = x
  827. gerby = y
  828. return [x,y]
  829. def read_Gerber_Shapely(filename, nverts=10):
  830. '''
  831. Gerber parser.
  832. '''
  833. EPS = 1e-20
  834. TYPE = 0
  835. SIZE = 1
  836. WIDTH = 1
  837. HEIGHT = 2
  838. gfile = open(filename, 'r')
  839. gstr = gfile.readlines()
  840. gfile.close()
  841. segment = -1
  842. xold = []
  843. yold = []
  844. boundary = []
  845. macros = []
  846. N_macros = 0
  847. apertures = [[] for i in range(1000)]
  848. for gline in gstr:
  849. if (find(gline, "%FS") != -1):
  850. ### format statement ###
  851. index = find(gline, "X")
  852. digits = int(gline[index + 1])
  853. fraction = int(gline[index + 2])
  854. continue
  855. elif (find(gline, "%AM") != -1):
  856. ### aperture macro ###
  857. index = find(gline, "%AM")
  858. index1 = find(gline, "*")
  859. macros.append([])
  860. macros[-1] = gline[index + 3:index1]
  861. N_macros += 1
  862. continue
  863. elif (find(gline, "%MOIN*%") != -1):
  864. # inches
  865. continue
  866. elif (find(gline, "G01*") != -1):
  867. ### linear interpolation ###
  868. continue
  869. elif (find(gline, "G70*") != -1):
  870. ### inches ###
  871. continue
  872. elif (find(gline, "G75*") != -1):
  873. ### circular interpolation ###
  874. continue
  875. elif (find(gline, "%ADD") != -1):
  876. ### aperture definition ###
  877. index = find(gline, "%ADD")
  878. parse = 0
  879. if (find(gline, "C,") != -1):
  880. ## circle ##
  881. index = find(gline, "C,")
  882. index1 = find(gline, "*")
  883. aperture = int(gline[4:index])
  884. size = float(gline[index + 2:index1])
  885. apertures[aperture] = ["C", size]
  886. print " read aperture", aperture, ": circle diameter", size
  887. continue
  888. elif (find(gline, "O,") != -1):
  889. ## obround ##
  890. index = find(gline, "O,")
  891. aperture = int(gline[4:index])
  892. index1 = find(gline, ",", index)
  893. index2 = find(gline, "X", index)
  894. index3 = find(gline, "*", index)
  895. width = float(gline[index1 + 1:index2])
  896. height = float(gline[index2 + 1:index3])
  897. apertures[aperture] = ["O", width, height]
  898. print " read aperture", aperture, ": obround", width, "x", height
  899. continue
  900. elif (find(gline, "R,") != -1):
  901. ## rectangle ##
  902. index = find(gline, "R,")
  903. aperture = int(gline[4:index])
  904. index1 = find(gline, ",", index)
  905. index2 = find(gline, "X", index)
  906. index3 = find(gline, "*", index)
  907. width = float(gline[index1 + 1:index2])
  908. height = float(gline[index2 + 1:index3])
  909. apertures[aperture] = ["R", width, height]
  910. print " read aperture", aperture, ": rectangle", width, "x", height
  911. continue
  912. for macro in range(N_macros):
  913. ## macros ##
  914. index = find(gline, macros[macro] + ',')
  915. if (index != -1):
  916. # hack: assume macros can be approximated by
  917. # a circle, and has a size parameter
  918. aperture = int(gline[4:index])
  919. index1 = find(gline, ",", index)
  920. index2 = find(gline, "*", index)
  921. size = float(gline[index1 + 1:index2])
  922. apertures[aperture] = ["C", size]
  923. print " read aperture", aperture, ": macro (assuming circle) diameter", size
  924. parse = 1
  925. continue
  926. if (parse == 0):
  927. print " aperture not implemented:", gline
  928. return
  929. # End of if aperture definition
  930. elif (find(gline, "D01*") != -1):
  931. ### pen down ###
  932. [xnew, ynew] = coord(gline, digits, fraction)
  933. if (size > EPS):
  934. if ((abs(xnew - xold) > EPS) | (abs(ynew - yold) > EPS)):
  935. newpath = stroke(xold, yold, xnew, ynew, size, nverts=nverts)
  936. boundary.append(newpath)
  937. segment += 1
  938. else:
  939. boundary[segment].append([xnew, ynew, []])
  940. xold = xnew
  941. yold = ynew
  942. continue
  943. elif (find(gline, "D02*") != -1):
  944. ### pen up ###
  945. [xold, yold] = coord(gline, digits, fraction)
  946. if (size < EPS):
  947. boundary.append([])
  948. segment += 1
  949. boundary[segment].append([xold, yold, []])
  950. newpath = []
  951. continue
  952. elif (find(gline, "D03*") != -1):
  953. ### flash ###
  954. if (find(gline, "D03*") == 0):
  955. # coordinates on preceeding line
  956. [xnew, ynew] = [xold, yold]
  957. else:
  958. # coordinates on this line
  959. [xnew, ynew] = coord(gline, digits, fraction)
  960. if (apertures[aperture][TYPE] == "C"):
  961. # circle
  962. boundary.append([])
  963. segment += 1
  964. size = apertures[aperture][SIZE]
  965. for i in range(nverts):
  966. angle = i * 2.0 * pi / (nverts - 1.0)
  967. x = xnew + (size / 2.0) * cos(angle)
  968. y = ynew + (size / 2.0) * sin(angle)
  969. boundary[segment].append([x, y, []])
  970. elif (apertures[aperture][TYPE] == "R"):
  971. # rectangle
  972. boundary.append([])
  973. segment += 1
  974. width = apertures[aperture][WIDTH] / 2.0
  975. height = apertures[aperture][HEIGHT] / 2.0
  976. boundary[segment].append([xnew - width, ynew - height, []])
  977. boundary[segment].append([xnew + width, ynew - height, []])
  978. boundary[segment].append([xnew + width, ynew + height, []])
  979. boundary[segment].append([xnew - width, ynew + height, []])
  980. boundary[segment].append([xnew - width, ynew - height, []])
  981. elif (apertures[aperture][TYPE] == "O"):
  982. # obround
  983. boundary.append([])
  984. segment += 1
  985. width = apertures[aperture][WIDTH]
  986. height = apertures[aperture][HEIGHT]
  987. if (width > height):
  988. for i in range(nverts / 2):
  989. angle = i * pi / (nverts / 2 - 1.0) + pi / 2.0
  990. x = xnew - (width - height) / 2.0 + (height / 2.0) * cos(angle)
  991. y = ynew + (height / 2.0) * sin(angle)
  992. boundary[segment].append([x, y, []])
  993. for i in range(nverts / 2):
  994. angle = i * pi / (nverts / 2 - 1.0) - pi / 2.0
  995. x = xnew + (width - height) / 2.0 + (height / 2.0) * cos(angle)
  996. y = ynew + (height / 2.0) * sin(angle)
  997. boundary[segment].append([x, y, []])
  998. else:
  999. for i in range(nverts / 2):
  1000. angle = i * pi / (nverts / 2 - 1.0) + pi
  1001. x = xnew + (width / 2.0) * cos(angle)
  1002. y = ynew - (height - width) / 2.0 + (width / 2.0) * sin(angle)
  1003. boundary[segment].append([x, y, []])
  1004. for i in range(nverts / 2):
  1005. angle = i * pi / (nverts / 2 - 1.0)
  1006. x = xnew + (width / 2.0) * cos(angle)
  1007. y = ynew + (height - width) / 2.0 + (width / 2.0) * sin(angle)
  1008. boundary[segment].append([x, y, []])
  1009. boundary[segment].append(boundary[segment][0])
  1010. else:
  1011. print " aperture", apertures[aperture][TYPE], "is not implemented"
  1012. return
  1013. xold = xnew
  1014. yold = ynew
  1015. continue # End of flash
  1016. elif (find(gline, "D") == 0):
  1017. ### change aperture ###
  1018. index = find(gline, '*')
  1019. aperture = int(gline[1:index])
  1020. size = apertures[aperture][SIZE]
  1021. continue
  1022. elif (find(gline, "G54D") == 0):
  1023. ### change aperture ###
  1024. index = find(gline, '*')
  1025. aperture = int(gline[4:index])
  1026. size = apertures[aperture][SIZE]
  1027. continue
  1028. else:
  1029. print " not parsed:", gline
  1030. boundarys[0] = boundary
  1031. def read_Gerber(filename, nverts=10):
  1032. '''
  1033. Gerber parser.
  1034. '''
  1035. global boundarys
  1036. EPS = 1e-20
  1037. TYPE = 0
  1038. SIZE = 1
  1039. WIDTH = 1
  1040. HEIGHT = 2
  1041. gfile = open(filename, 'r')
  1042. gstr = gfile.readlines()
  1043. gfile.close()
  1044. segment = -1
  1045. xold = []
  1046. yold = []
  1047. boundary = []
  1048. macros = []
  1049. N_macros = 0
  1050. apertures = [[] for i in range(1000)]
  1051. for gline in gstr:
  1052. if (find(gline, "%FS") != -1):
  1053. ### format statement ###
  1054. index = find(gline, "X")
  1055. digits = int(gline[index + 1])
  1056. fraction = int(gline[index + 2])
  1057. continue
  1058. elif (find(gline, "%AM") != -1):
  1059. ### aperture macro ###
  1060. index = find(gline, "%AM")
  1061. index1 = find(gline, "*")
  1062. macros.append([])
  1063. macros[-1] = gline[index + 3:index1]
  1064. N_macros += 1
  1065. continue
  1066. elif (find(gline, "%MOIN*%") != -1):
  1067. # inches
  1068. continue
  1069. elif (find(gline, "G01*") != -1):
  1070. ### linear interpolation ###
  1071. continue
  1072. elif (find(gline, "G70*") != -1):
  1073. ### inches ###
  1074. continue
  1075. elif (find(gline, "G75*") != -1):
  1076. ### circular interpolation ###
  1077. continue
  1078. elif (find(gline, "%ADD") != -1):
  1079. ### aperture definition ###
  1080. index = find(gline, "%ADD")
  1081. parse = 0
  1082. if (find(gline, "C,") != -1):
  1083. ## circle ##
  1084. index = find(gline, "C,")
  1085. index1 = find(gline, "*")
  1086. aperture = int(gline[4:index])
  1087. size = float(gline[index + 2:index1])
  1088. apertures[aperture] = ["C", size]
  1089. print " read aperture", aperture, ": circle diameter", size
  1090. continue
  1091. elif (find(gline, "O,") != -1):
  1092. ## obround ##
  1093. index = find(gline, "O,")
  1094. aperture = int(gline[4:index])
  1095. index1 = find(gline, ",", index)
  1096. index2 = find(gline, "X", index)
  1097. index3 = find(gline, "*", index)
  1098. width = float(gline[index1 + 1:index2])
  1099. height = float(gline[index2 + 1:index3])
  1100. apertures[aperture] = ["O", width, height]
  1101. print " read aperture", aperture, ": obround", width, "x", height
  1102. continue
  1103. elif (find(gline, "R,") != -1):
  1104. ## rectangle ##
  1105. index = find(gline, "R,")
  1106. aperture = int(gline[4:index])
  1107. index1 = find(gline, ",", index)
  1108. index2 = find(gline, "X", index)
  1109. index3 = find(gline, "*", index)
  1110. width = float(gline[index1 + 1:index2])
  1111. height = float(gline[index2 + 1:index3])
  1112. apertures[aperture] = ["R", width, height]
  1113. print " read aperture", aperture, ": rectangle", width, "x", height
  1114. continue
  1115. for macro in range(N_macros):
  1116. ## macros ##
  1117. index = find(gline, macros[macro] + ',')
  1118. if (index != -1):
  1119. # hack: assume macros can be approximated by
  1120. # a circle, and has a size parameter
  1121. aperture = int(gline[4:index])
  1122. index1 = find(gline, ",", index)
  1123. index2 = find(gline, "*", index)
  1124. size = float(gline[index1 + 1:index2])
  1125. apertures[aperture] = ["C", size]
  1126. print " read aperture", aperture, ": macro (assuming circle) diameter", size
  1127. parse = 1
  1128. continue
  1129. if (parse == 0):
  1130. print " aperture not implemented:", gline
  1131. return
  1132. # End of if aperture definition
  1133. elif (find(gline, "D01*") != -1):
  1134. ### pen down ###
  1135. [xnew, ynew] = coord(gline, digits, fraction)
  1136. if (size > EPS):
  1137. if ((abs(xnew - xold) > EPS) | (abs(ynew - yold) > EPS)):
  1138. newpath = stroke(xold, yold, xnew, ynew, size, nverts=nverts)
  1139. boundary.append(newpath)
  1140. segment += 1
  1141. else:
  1142. boundary[segment].append([xnew, ynew, []])
  1143. xold = xnew
  1144. yold = ynew
  1145. continue
  1146. elif (find(gline, "D02*") != -1):
  1147. ### pen up ###
  1148. [xold, yold] = coord(gline, digits, fraction)
  1149. if (size < EPS):
  1150. boundary.append([])
  1151. segment += 1
  1152. boundary[segment].append([xold, yold, []])
  1153. newpath = []
  1154. continue
  1155. elif (find(gline, "D03*") != -1):
  1156. ### flash ###
  1157. if (find(gline, "D03*") == 0):
  1158. # coordinates on preceeding line
  1159. [xnew, ynew] = [xold, yold]
  1160. else:
  1161. # coordinates on this line
  1162. [xnew, ynew] = coord(gline, digits, fraction)
  1163. if (apertures[aperture][TYPE] == "C"):
  1164. # circle
  1165. boundary.append([])
  1166. segment += 1
  1167. size = apertures[aperture][SIZE]
  1168. for i in range(nverts):
  1169. angle = i * 2.0 * pi / (nverts - 1.0)
  1170. x = xnew + (size / 2.0) * cos(angle)
  1171. y = ynew + (size / 2.0) * sin(angle)
  1172. boundary[segment].append([x, y, []])
  1173. elif (apertures[aperture][TYPE] == "R"):
  1174. # rectangle
  1175. boundary.append([])
  1176. segment += 1
  1177. width = apertures[aperture][WIDTH] / 2.0
  1178. height = apertures[aperture][HEIGHT] / 2.0
  1179. boundary[segment].append([xnew - width, ynew - height, []])
  1180. boundary[segment].append([xnew + width, ynew - height, []])
  1181. boundary[segment].append([xnew + width, ynew + height, []])
  1182. boundary[segment].append([xnew - width, ynew + height, []])
  1183. boundary[segment].append([xnew - width, ynew - height, []])
  1184. elif (apertures[aperture][TYPE] == "O"):
  1185. # obround
  1186. boundary.append([])
  1187. segment += 1
  1188. width = apertures[aperture][WIDTH]
  1189. height = apertures[aperture][HEIGHT]
  1190. if (width > height):
  1191. for i in range(nverts / 2):
  1192. angle = i * pi / (nverts / 2 - 1.0) + pi / 2.0
  1193. x = xnew - (width - height) / 2.0 + (height / 2.0) * cos(angle)
  1194. y = ynew + (height / 2.0) * sin(angle)
  1195. boundary[segment].append([x, y, []])
  1196. for i in range(nverts / 2):
  1197. angle = i * pi / (nverts / 2 - 1.0) - pi / 2.0
  1198. x = xnew + (width - height) / 2.0 + (height / 2.0) * cos(angle)
  1199. y = ynew + (height / 2.0) * sin(angle)
  1200. boundary[segment].append([x, y, []])
  1201. else:
  1202. for i in range(nverts / 2):
  1203. angle = i * pi / (nverts / 2 - 1.0) + pi
  1204. x = xnew + (width / 2.0) * cos(angle)
  1205. y = ynew - (height - width) / 2.0 + (width / 2.0) * sin(angle)
  1206. boundary[segment].append([x, y, []])
  1207. for i in range(nverts / 2):
  1208. angle = i * pi / (nverts / 2 - 1.0)
  1209. x = xnew + (width / 2.0) * cos(angle)
  1210. y = ynew + (height - width) / 2.0 + (width / 2.0) * sin(angle)
  1211. boundary[segment].append([x, y, []])
  1212. boundary[segment].append(boundary[segment][0])
  1213. else:
  1214. print " aperture", apertures[aperture][TYPE], "is not implemented"
  1215. return
  1216. xold = xnew
  1217. yold = ynew
  1218. continue # End of flash
  1219. elif (find(gline, "D") == 0):
  1220. ### change aperture ###
  1221. index = find(gline, '*')
  1222. aperture = int(gline[1:index])
  1223. size = apertures[aperture][SIZE]
  1224. continue
  1225. elif (find(gline, "G54D") == 0):
  1226. ### change aperture ###
  1227. index = find(gline, '*')
  1228. aperture = int(gline[4:index])
  1229. size = apertures[aperture][SIZE]
  1230. continue
  1231. else:
  1232. print " not parsed:", gline
  1233. boundarys[0] = boundary
  1234. def read_Excellon(filename):
  1235. global boundarys
  1236. #
  1237. # Excellon parser
  1238. #
  1239. file = open(filename,'r')
  1240. str = file.readlines()
  1241. file.close()
  1242. segment = -1
  1243. line = 0
  1244. nlines = len(str)
  1245. boundary = []
  1246. #header = TRUE
  1247. drills = [[] for i in range(1000)]
  1248. while line < nlines:
  1249. if ((find(str[line],"T") != -1) & (find(str[line],"C") != -1) \
  1250. & (find(str[line],"F") != -1)):
  1251. #
  1252. # alternate drill definition style
  1253. #
  1254. index = find(str[line],"T")
  1255. index1 = find(str[line],"C")
  1256. index2 = find(str[line],"F")
  1257. drill = int(str[line][1:index1])
  1258. print str[line][index1+1:index2]
  1259. size = float(str[line][index1+1:index2])
  1260. drills[drill] = ["C",size]
  1261. print " read drill",drill,"size:",size
  1262. line += 1
  1263. continue
  1264. if ((find(str[line],"T") != -1) & (find(str[line]," ") != -1) \
  1265. & (find(str[line],"in") != -1)):
  1266. #
  1267. # alternate drill definition style
  1268. #
  1269. index = find(str[line],"T")
  1270. index1 = find(str[line]," ")
  1271. index2 = find(str[line],"in")
  1272. drill = int(str[line][1:index1])
  1273. print str[line][index1+1:index2]
  1274. size = float(str[line][index1+1:index2])
  1275. drills[drill] = ["C",size]
  1276. print " read drill",drill,"size:",size
  1277. line += 1
  1278. continue
  1279. elif ((find(str[line],"T") != -1) & (find(str[line],"C") != -1)):
  1280. #
  1281. # alternate drill definition style
  1282. #
  1283. index = find(str[line],"T")
  1284. index1 = find(str[line],"C")
  1285. drill = int(str[line][1:index1])
  1286. size = float(str[line][index1+1:-1])
  1287. drills[drill] = ["C",size]
  1288. print " read drill",drill,"size:",size
  1289. line += 1
  1290. continue
  1291. elif (find(str[line],"T") == 0):
  1292. #
  1293. # change drill
  1294. #
  1295. index = find(str[line],'T')
  1296. drill = int(str[line][index+1:-1])
  1297. size = drills[drill][SIZE]
  1298. line += 1
  1299. continue
  1300. elif (find(str[line],"X") != -1):
  1301. #
  1302. # drill location
  1303. #
  1304. index = find(str[line],"X")
  1305. index1 = find(str[line],"Y")
  1306. x0 = float(int(str[line][index+1:index1])/10000.0)
  1307. y0 = float(int(str[line][index1+1:-1])/10000.0)
  1308. line += 1
  1309. boundary.append([])
  1310. segment += 1
  1311. size = drills[drill][SIZE]
  1312. for i in range(nverts):
  1313. angle = -i*2.0*pi/(nverts-1.0)
  1314. x = x0 + (size/2.0)*cos(angle)
  1315. y = y0 + (size/2.0)*sin(angle)
  1316. boundary[segment].append([x,y,[]])
  1317. continue
  1318. else:
  1319. print " not parsed:",str[line]
  1320. line += 1
  1321. boundarys[0] = boundary
  1322. def stroke(x0,y0,x1,y1,width, nverts=10):
  1323. #
  1324. # stroke segment with width
  1325. #
  1326. #print "stroke:",x0,y0,x1,y1,width
  1327. X = 0
  1328. Y = 1
  1329. dx = x1 - x0
  1330. dy = y1 - y0
  1331. d = sqrt(dx*dx + dy*dy)
  1332. dxpar = dx / d
  1333. dypar = dy / d
  1334. dxperp = dypar
  1335. dyperp = -dxpar
  1336. dx = -dxperp * width/2.0
  1337. dy = -dyperp * width/2.0
  1338. angle = pi/(nverts/2-1.0)
  1339. c = cos(angle)
  1340. s = sin(angle)
  1341. newpath = []
  1342. for i in range(nverts/2):
  1343. newpath.append([x0+dx,y0+dy,0])
  1344. [dx,dy] = [c*dx-s*dy, s*dx+c*dy]
  1345. dx = dxperp * width/2.0
  1346. dy = dyperp * width/2.0
  1347. for i in range(nverts/2):
  1348. newpath.append([x1+dx,y1+dy,0])
  1349. [dx,dy] = [c*dx-s*dy, s*dx+c*dy]
  1350. x0 = newpath[0][X]
  1351. y0 = newpath[0][Y]
  1352. newpath.append([x0,y0,0])
  1353. return newpath
  1354. def contour(event):
  1355. '''
  1356. Uses displace() and adjust_contours()
  1357. '''
  1358. global boundarys, toolpaths, contours
  1359. #
  1360. # contour boundary to find toolpath
  1361. #
  1362. print "contouring boundary ..."
  1363. xyscale = float(sxyscale.get())
  1364. undercut = float(sundercut.get())
  1365. if (undercut != 0.0):
  1366. print " undercutting contour by",undercut
  1367. N_contour = 1
  1368. if (len(boundarys) == 1):
  1369. #
  1370. # 2D contour
  1371. #
  1372. toolpaths[0] = []
  1373. for n in range(N_contour):
  1374. toolrad = (n+1)*(float(sdia.get())/2.0-undercut)/xyscale
  1375. contours[0] = displace(boundarys[0],toolrad)
  1376. altern = ialtern.get();
  1377. if (altern == TRUE):
  1378. contours[0] = adjust_contour(contours[0],boundarys[0],toolrad)
  1379. else:
  1380. contours[0] = prune(contours[0],-1,event)
  1381. toolpaths[0].extend(contours[0])
  1382. plot(event)
  1383. else:
  1384. #
  1385. # 3D contour
  1386. #
  1387. for layer in range(len(boundarys)):
  1388. toolpaths[layer] = []
  1389. contours[layer] = []
  1390. if (boundarys[layer] != []):
  1391. [xindex,yindex,zindex,z] = orient(boundarys[layer])
  1392. for n in range(N_contour):
  1393. toolrad = (n+1)*(float(sdia.get())/2.0-undercut)/xyscale
  1394. path = project(boundarys[layer],xindex,yindex)
  1395. contour = displace(path,toolrad)
  1396. contour = prune(contour,-1,event)
  1397. contours[layer] = lift(contour,xindex,yindex,zindex,z)
  1398. toolpaths[layer].extend(contours[layer])
  1399. plot(event)
  1400. print " done"
  1401. def adjust_contour(path, boundary, toolrad):
  1402. print " adjust_contour ..."
  1403. newpath = []
  1404. for seg in range(len(path)):
  1405. newpath.append([])
  1406. # print "points"
  1407. # for vert in range(len(path[seg])):
  1408. # Px = boundary[seg][vert][X]
  1409. # Py = boundary[seg][vert][Y]
  1410. # print "%2i : %5.2f,%5.2f" % (vert, Px, Py)
  1411. # print "len(path[seg]): ", len(path[seg])
  1412. # print "len(boundary[seg]: ", len(boundary[seg])
  1413. for vert in range(len(path[seg])):
  1414. Px = path[seg][vert][X]
  1415. Py = path[seg][vert][Y]
  1416. avgvalue = []
  1417. avgvalue.append(0.0)
  1418. avgvalue.append(0.0)
  1419. changed = 1
  1420. iteration = 0
  1421. avg = []
  1422. while ((iteration < MAXITER) & (changed != 0)):
  1423. changed = 0
  1424. for orgvert in range(len(boundary[seg]) - 1):
  1425. # if (orgvert == 0):
  1426. # x0 = boundary[seg][len(boundary[seg]) - 1][X]
  1427. # y0 = boundary[seg][len(boundary[seg]) - 1][Y]
  1428. # else:
  1429. x0 = boundary[seg][orgvert][X]
  1430. y0 = boundary[seg][orgvert][Y]
  1431. x1 = boundary[seg][orgvert + 1][X]
  1432. y1 = boundary[seg][orgvert + 1][Y]
  1433. #print ' A %5.2f,%5.2f B %5.2f,%5.2f' % (x0, y0, x1, y1)
  1434. dx = x1 - x0
  1435. dy = y1 - y0
  1436. nx = dy;
  1437. ny = -dx;
  1438. d = abs(((nx * Px + ny * Py) - (nx * x0 + ny * y0) ) / \
  1439. sqrt( nx * nx + ny * ny ))
  1440. pre = orgvert - 1
  1441. if (pre < 0):
  1442. pre = len(boundary[seg]) - 2
  1443. post = orgvert + 2
  1444. if (post == len(boundary[seg])):
  1445. post = 1
  1446. #print " distance %5.2f" % d
  1447. #print "toolrad ", toolrad
  1448. if (d - toolrad < - NOISE):
  1449. # if (x0 < 1000000000):
  1450. #print " low distance"
  1451. # check if inside
  1452. pre = orgvert - 1
  1453. if (pre < 0):
  1454. pre = len(boundary[seg]) - 2
  1455. post = orgvert + 2
  1456. if (post == len(boundary[seg])):
  1457. post = 1
  1458. diff_d_pre_x = x1 - boundary[seg][pre][X]
  1459. diff_d_pre_y = y1 - boundary[seg][pre][Y]
  1460. diff_d_post_x = boundary[seg][post][X] - x0
  1461. diff_d_post_y = boundary[seg][post][Y] - y0
  1462. #print "diff_pre %5.2f,%5.2f" % (diff_d_pre_x, diff_d_pre_y)
  1463. #print "diff_post %5.2f,%5.2f" % (diff_d_post_x, diff_d_post_y)
  1464. #n_pre_x = diff_d_pre_y
  1465. #n_pre_y = -diff_d_pre_x
  1466. #n_post_x = diff_d_post_y
  1467. #n_post_y = -diff_d_post_x
  1468. diff_px0 = Px - x0
  1469. diff_py0 = Py - y0
  1470. diff_px1 = Px - x1
  1471. diff_py1 = Py - y1
  1472. #print "diff p0 %5.2f,%5.2f" % (diff_px0, diff_py0)
  1473. #print "diff p1 %5.2f,%5.2f" % (diff_px1, diff_py1)
  1474. pre_x = boundary[seg][pre][X]
  1475. pre_y = boundary[seg][pre][Y]
  1476. post_x = boundary[seg][post][X]
  1477. post_y = boundary[seg][post][Y]
  1478. v0_x = x0 - pre_x
  1479. v0_y = y0 - pre_y
  1480. v1_x = post_x - x0
  1481. v1_y = post_y - y0
  1482. if ((v0_x * nx + v0_y * ny) > -NOISE): #angle > 180
  1483. #print "XXXXXXXXXXXXXXXXXXX pre > 180"
  1484. value0 = diff_d_pre_x * diff_px0 + diff_d_pre_y * diff_py0
  1485. #value0 = diff_px0 * dx + diff_py0 * dy
  1486. else:
  1487. value0 = diff_px0 * dx + diff_py0 * dy
  1488. if (-(v1_x * nx + v1_y * ny) > -NOISE): #angle > 180
  1489. #print "XXXXXXXXXXXXXXXXXXX post > 180"
  1490. value1 = diff_d_post_x * diff_px1 + diff_d_post_y * diff_py1
  1491. #value1 = diff_px1 * dx + diff_py1 * dy
  1492. else:
  1493. value1 = diff_px1 * dx + diff_py1 * dy
  1494. #if ((value0 > -NOISE) & (value1 < NOISE)):
  1495. #print " P %5.2f,%5.2f a %5.2f,%5.2f b %5.2f,%5.2f - inside (%8.5f & %8.5f)" % (Px, Py, x0, y0, x1, y1, value0, value1)
  1496. #else:
  1497. #print " P %5.2f,%5.2f a %5.2f,%5.2f b %5.2f,%5.2f - outside (%8.5f & %8.5f)" % (Px, Py, x0, y0, x1, y1, value0, value1)
  1498. # if (vert == 3) & (orgvert == 2):
  1499. # print "-p1 %5.2f,%5.2f p2 %5.2f,%5.2f P %5.2f,%5.2f " % (x0, y0, x1, y1, Px, Py)
  1500. # print "d %5.2f,%5.2f" % (dx, dy)
  1501. # print "n %5.2f,%5.2f" % (nx, ny)
  1502. # print "di0 %5.2f,%5.2f" % (diff_px0, diff_py0)
  1503. # print "di1 %5.2f,%5.2f" % (diff_px1, diff_py1)
  1504. # print "val %5.2f,%5.2f" % (value0, value1)
  1505. # if ((value0 == 0) | (value1 == 0)):
  1506. # #print " fix me"
  1507. # value = value1
  1508. # else:
  1509. if ((value0 > -NOISE) & (value1 < NOISE)):
  1510. #value = value1 * value0;
  1511. #if (value < 0 ):
  1512. #print 'P %5.2f,%5.2f' % (Px, Py)
  1513. #print ' A %5.2f,%5.2f B %5.2f,%5.2f' % (x0, y0, x1, y1)
  1514. #print " distance %5.2f" % d
  1515. #print " move"
  1516. ln = sqrt((nx * nx) + (ny * ny))
  1517. Px = Px + (nx / ln) * (toolrad - d);
  1518. Py = Py + (ny / ln) * (toolrad - d);
  1519. changed += 1
  1520. iteration += 1
  1521. # print ' new %5.2f,%5.2f' % (Px, Py)
  1522. if (iteration > MAXITER - AVGITER):
  1523. # print "ii %2i %7.4f,%7.4f" % (iteration, Px,Py)
  1524. avgvalue[X] += Px
  1525. avgvalue[Y] += Py
  1526. # if (iteration > 1):
  1527. # print iteration
  1528. if (iteration >= MAXITER):
  1529. # print " diff", (iteration - (MAXITER - AVGITER))
  1530. avgvalue[X] /= float(iteration - (MAXITER - AVGITER))
  1531. avgvalue[Y] /= float(iteration - (MAXITER - AVGITER))
  1532. newpath[seg].append([avgvalue[X],avgvalue[Y],[]])
  1533. # print "NEW : %7.4f,%7.4f" % (avgvalue[X], avgvalue[Y])
  1534. else:
  1535. newpath[seg].append([Px,Py,[]])
  1536. # for vert in range(len(path[seg])):
  1537. # Px = newpath[seg][vert][X]
  1538. # Py = newpath[seg][vert][Y]
  1539. # print "NEW %2i : %5.2f,%5.2f" % (vert, Px, Py)
  1540. return newpath
  1541. def displace(path,toolrad):
  1542. '''
  1543. Uses offset()
  1544. '''
  1545. #
  1546. # displace path inwards by tool radius
  1547. #
  1548. print " displacing ..."
  1549. newpath = []
  1550. for seg in range(len(path)):
  1551. newpath.append([])
  1552. if (len(path[seg]) > 2):
  1553. for vert1 in range(len(path[seg])-1):
  1554. if (vert1 == 0):
  1555. vert0 = len(path[seg]) - 2
  1556. else:
  1557. vert0 = vert1 - 1
  1558. vert2 = vert1 + 1
  1559. x0 = path[seg][vert0][X]
  1560. x1 = path[seg][vert1][X]
  1561. x2 = path[seg][vert2][X]
  1562. y0 = path[seg][vert0][Y]
  1563. y1 = path[seg][vert1][Y]
  1564. y2 = path[seg][vert2][Y]
  1565. [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad)
  1566. if (dx != []):
  1567. newpath[seg].append([(x1+dx),(y1+dy),[]])
  1568. x0 = newpath[seg][0][X]
  1569. y0 = newpath[seg][0][Y]
  1570. newpath[seg].append([x0,y0,[]])
  1571. elif (len(path[seg]) == 2):
  1572. x0 = path[seg][0][X]
  1573. y0 = path[seg][0][Y]
  1574. x1 = path[seg][1][X]
  1575. y1 = path[seg][1][Y]
  1576. x2 = 2*x1 - x0
  1577. y2 = 2*y1 - y0
  1578. [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad)
  1579. if (dx != []):
  1580. newpath[seg].append([x0+dx,y0+dy,[]])
  1581. newpath[seg].append([x1+dx,y1+dy,[]])
  1582. else:
  1583. newpath[seg].append([x0,y0,[]])
  1584. newpath[seg].append([x1,y1,[]])
  1585. else:
  1586. print " displace: shouldn't happen"
  1587. return newpath
  1588. def offset(x0,x1,x2,y0,y1,y2,r):
  1589. #
  1590. # calculate offset by r for vertex 1
  1591. #
  1592. dx0 = x1 - x0
  1593. dx1 = x2 - x1
  1594. dy0 = y1 - y0
  1595. dy1 = y2 - y1
  1596. d0 = sqrt(dx0*dx0 + dy0*dy0)
  1597. d1 = sqrt(dx1*dx1 + dy1*dy1)
  1598. if ((d0 == 0) | (d1 == 0)):
  1599. return [[],[]]
  1600. dx0par = dx0 / d0
  1601. dy0par = dy0 / d0
  1602. dx0perp = dy0 / d0
  1603. dy0perp = -dx0 / d0
  1604. dx1perp = dy1 / d1
  1605. dy1perp = -dx1 / d1
  1606. #print "offset points:",x0,x1,x2,y0,y1,y2
  1607. #print "offset normals:",dx0perp,dx1perp,dy0perp,dy1perp
  1608. if ((abs(dx0perp*dy1perp - dx1perp*dy0perp) < EPS) | \
  1609. (abs(dy0perp*dx1perp - dy1perp*dx0perp) < EPS)):
  1610. dx = r * dx1perp
  1611. dy = r * dy1perp
  1612. #print " offset planar:",dx,dy
  1613. elif ((abs(dx0perp+dx1perp) < EPS) & (abs(dy0perp+dy1perp) < EPS)):
  1614. dx = r * dx1par
  1615. dy = r * dy1par
  1616. #print " offset hairpin:",dx,dy
  1617. else:
  1618. dx = r*(dy1perp - dy0perp) / \
  1619. (dx0perp*dy1perp - dx1perp*dy0perp)
  1620. dy = r*(dx1perp - dx0perp) / \
  1621. (dy0perp*dx1perp - dy1perp*dx0perp)
  1622. #print " offset OK:",dx,dy
  1623. return [dx,dy]
  1624. def prune(path,sign,event):
  1625. '''
  1626. Uses add_intersections() and union()
  1627. '''
  1628. #
  1629. # prune path intersections
  1630. #
  1631. # first find the intersections
  1632. #
  1633. print " intersecting ..."
  1634. [path, intersections, seg_intersections] = add_intersections(path)
  1635. #print 'path:',path
  1636. #print 'intersections:',intersections
  1637. #print 'seg_intersections:',seg_intersections
  1638. #
  1639. # then copy non-intersecting segments to new path
  1640. #
  1641. newpath = []
  1642. for seg in range(len(seg_intersections)):
  1643. #print "non-int"
  1644. if (seg_intersections[seg] == []):
  1645. newpath.append(path[seg])
  1646. #
  1647. # finally follow and remove the intersections
  1648. #
  1649. print " pruning ..."
  1650. i = 0
  1651. newseg = 0
  1652. while (i < len(intersections)):
  1653. if (intersections[i] == []):
  1654. #
  1655. # skip null intersections
  1656. #
  1657. i += 1
  1658. #print "null"
  1659. else:
  1660. istart = i
  1661. intersection = istart
  1662. #
  1663. # skip interior intersections
  1664. #
  1665. oldseg = -1
  1666. interior = TRUE
  1667. while 1:
  1668. #print 'testing intersection',intersection,':',intersections[intersection]
  1669. if (intersections[intersection] == []):
  1670. #seg == oldseg
  1671. seg = oldseg
  1672. else:
  1673. [seg,vert] = union(intersection,path,intersections,sign)
  1674. #print ' seg',seg,'vert',vert,'oldseg',oldseg
  1675. if (seg == oldseg):
  1676. #print " remove interior intersection",istart
  1677. seg0 = intersections[istart][0][SEG]
  1678. vert0 = intersections[istart][0][VERT]
  1679. path[seg0][vert0][INTERSECT] = -1
  1680. seg1 = intersections[istart][1][SEG]
  1681. vert1 = intersections[istart][1][VERT]
  1682. path[seg1][vert1][INTERSECT] = -1
  1683. intersections[istart] = []
  1684. break
  1685. elif (seg == []):
  1686. seg = intersections[intersection][0][SEG]
  1687. vert = intersections[intersection][0][SEG]
  1688. oldseg = []
  1689. else:
  1690. oldseg = seg
  1691. intersection = []
  1692. while (intersection == []):
  1693. if (vert < (len(path[seg])-1)):
  1694. vert += 1
  1695. else:
  1696. vert = 0
  1697. intersection = path[seg][vert][INTERSECT]
  1698. if (intersection == -1):
  1699. intersection = istart
  1700. break
  1701. elif (intersection == istart):
  1702. #print ' back to',istart
  1703. interior = FALSE
  1704. intersection = istart
  1705. break
  1706. #
  1707. # save path if valid boundary intersection
  1708. #
  1709. if (interior == FALSE):
  1710. newseg = len(newpath)
  1711. newpath.append([])
  1712. while 1:
  1713. #print 'keeping intersection',intersection,':',intersections[intersection]
  1714. [seg,vert] = union(intersection,path,intersections,sign)
  1715. if (seg == []):
  1716. seg = intersections[intersection][0][SEG]
  1717. vert = intersections[intersection][0][VERT]
  1718. #print ' seg',seg,'vert',vert
  1719. intersections[intersection] = []
  1720. intersection = []
  1721. while (intersection == []):
  1722. if (vert < (len(path[seg])-1)):
  1723. x = path[seg][vert][X]
  1724. y = path[seg][vert][Y]
  1725. newpath[newseg].append([x,y,[]])
  1726. vert += 1
  1727. else:
  1728. vert = 0
  1729. intersection = path[seg][vert][INTERSECT]
  1730. if (intersection == istart):
  1731. #print ' back to',istart
  1732. x = path[seg][vert][X]
  1733. y = path[seg][vert][Y]
  1734. newpath[newseg].append([x,y,[]])
  1735. break
  1736. i += 1
  1737. return newpath
  1738. def add_intersections(path):
  1739. '''
  1740. Uses intersect() and insert() (FIX THIS, BELONG TO OTHER LIBRARY)
  1741. '''
  1742. #
  1743. # add vertices at path intersections
  1744. #
  1745. events = []
  1746. active = []
  1747. #
  1748. # lexicographic sort segments
  1749. #
  1750. for seg in range(len(path)):
  1751. nverts = len(path[seg])
  1752. for vert in range(nverts-1):
  1753. x0 = path[seg][vert][X]
  1754. y0 = path[seg][vert][Y]
  1755. x1 = path[seg][vert+1][X]
  1756. y1 = path[seg][vert+1][Y]
  1757. if (x1 < x0):
  1758. [x0, x1] = [x1, x0]
  1759. [y0, y1] = [y1, y0]
  1760. if ((x1 == x0) & (y1 < y0)):
  1761. [y0, y1] = [y1, y0]
  1762. events.append([x0,y0,START,seg,vert])
  1763. events.append([x1,y1,END,seg,vert])
  1764. events.sort()
  1765. #
  1766. # find intersections with a sweep line
  1767. #
  1768. intersection = 0
  1769. verts = []
  1770. for event in range(len(events)):
  1771. # status.set(" edge "+str(event)+"/"+str(len(events)-1)+" ")
  1772. # outframe.update()
  1773. #
  1774. # loop over start/end points
  1775. #
  1776. type = events[event][INDEX]
  1777. seg0 = events[event][EVENT_SEG]
  1778. vert0 = events[event][EVENT_VERT]
  1779. n0 = len(path[seg0])
  1780. if (events[event][INDEX] == START):
  1781. #
  1782. # loop over active points
  1783. #
  1784. for point in range(len(active)):
  1785. sega = active[point][SEG]
  1786. verta = active[point][VERT]
  1787. if ((sega == seg0) & \
  1788. ((abs(vert0-verta) == 1) | (abs(vert0-verta) == (n0-2)))):
  1789. #print seg0,vert0,verta,n0
  1790. continue
  1791. [xloc,yloc] = intersect(path,seg0,vert0,sega,verta)
  1792. if (xloc != []):
  1793. #
  1794. # found intersection, save it
  1795. #
  1796. d0 = (path[seg0][vert0][X]-xloc)**2 + (path[seg0][vert0][Y]-yloc)**2
  1797. verts.append([seg0,vert0,d0,xloc,yloc,intersection])
  1798. da = (path[sega][verta][X]-xloc)**2 + (path[sega][verta][Y]-yloc)**2
  1799. verts.append([sega,verta,da,xloc,yloc,intersection])
  1800. intersection += 1
  1801. active.append([seg0,vert0])
  1802. else:
  1803. active.remove([seg0,vert0])
  1804. print " found",intersection,"intersections"
  1805. #
  1806. # add vertices at path intersections
  1807. #
  1808. verts.sort()
  1809. verts.reverse()
  1810. for vertex in range(len(verts)):
  1811. seg = verts[vertex][SEG]
  1812. vert = verts[vertex][VERT]
  1813. intersection = verts[vertex][IINTERSECT]
  1814. x = verts[vertex][XINTERSECT]
  1815. y = verts[vertex][YINTERSECT]
  1816. insert(path,x,y,seg,vert,intersection)
  1817. #
  1818. # make vertex table and segment list of intersections
  1819. #
  1820. # status.set(namedate)
  1821. # outframe.update()
  1822. nintersections = len(verts)/2
  1823. intersections = [[] for i in range(nintersections)]
  1824. for seg in range(len(path)):
  1825. for vert in range(len(path[seg])):
  1826. intersection = path[seg][vert][INTERSECT]
  1827. if (intersection != []):
  1828. intersections[intersection].append([seg,vert])
  1829. seg_intersections = [[] for i in path]
  1830. for i in range(len(intersections)):
  1831. if (len(intersections[i]) != 2):
  1832. print " shouldn't happen: i",i,intersections[i]
  1833. else:
  1834. seg_intersections[intersections[i][0][SEG]].append(i)
  1835. seg_intersections[intersections[i][A][SEG]].append(i)
  1836. return [path, intersections, seg_intersections]
  1837. def intersect(path,seg0,vert0,sega,verta):
  1838. #
  1839. # test and return edge intersection
  1840. #
  1841. if ((seg0 == sega) & (vert0 == 0) & (verta == (len(path[sega])-2))):
  1842. #print " return (0-end)"
  1843. return [[],[]]
  1844. x0 = path[seg0][vert0][X]
  1845. y0 = path[seg0][vert0][Y]
  1846. x1 = path[seg0][vert0+1][X]
  1847. y1 = path[seg0][vert0+1][Y]
  1848. dx01 = x1 - x0
  1849. dy01 = y1 - y0
  1850. d01 = sqrt(dx01*dx01 + dy01*dy01)
  1851. if (d01 == 0):
  1852. #
  1853. # zero-length segment, return no intersection
  1854. #
  1855. #print "zero-length segment"
  1856. return [[],[]]
  1857. dxpar01 = dx01 / d01
  1858. dypar01 = dy01 / d01
  1859. dxperp01 = dypar01
  1860. dyperp01 = -dxpar01
  1861. xa = path[sega][verta][X]
  1862. ya = path[sega][verta][Y]
  1863. xb = path[sega][verta+1][X]
  1864. yb = path[sega][verta+1][Y]
  1865. dx0a = xa - x0
  1866. dy0a = ya - y0
  1867. dpar0a = dx0a*dxpar01 + dy0a*dypar01
  1868. dperp0a = dx0a*dxperp01 + dy0a*dyperp01
  1869. dx0b = xb - x0
  1870. dy0b = yb - y0
  1871. dpar0b = dx0b*dxpar01 + dy0b*dypar01
  1872. dperp0b = dx0b*dxperp01 + dy0b*dyperp01
  1873. #if (dperp0a*dperp0b > EPS):
  1874. if (((dperp0a > EPS) & (dperp0b > EPS)) | \
  1875. ((dperp0a < -EPS) & (dperp0b < -EPS))):
  1876. #
  1877. # vertices on same side, return no intersection
  1878. #
  1879. #print " same side"
  1880. return [[],[]]
  1881. elif ((abs(dperp0a) < EPS) & (abs(dperp0b) < EPS)):
  1882. #
  1883. # edges colinear, return no intersection
  1884. #
  1885. #d0a = (xa-x0)*dxpar01 + (ya-y0)*dypar01
  1886. #d0b = (xb-x0)*dxpar01 + (yb-y0)*dypar01
  1887. #print " colinear"
  1888. return [[],[]]
  1889. #
  1890. # calculation distance to intersection
  1891. #
  1892. d = (dpar0a*abs(dperp0b)+dpar0b*abs(dperp0a))/(abs(dperp0a)+abs(dperp0b))
  1893. if ((d < -EPS) | (d > (d01+EPS))):
  1894. #
  1895. # intersection outside segment, return no intersection
  1896. #
  1897. #print " found intersection outside segment"
  1898. return [[],[]]
  1899. else:
  1900. #
  1901. # intersection in segment, return intersection
  1902. #
  1903. #print " found intersection in segment s0 v0 sa va",seg0,vert0,sega,verta
  1904. xloc = x0 + dxpar01*d
  1905. yloc = y0 + dypar01*d
  1906. return [xloc,yloc]
  1907. def insert(path,x,y,seg,vert,intersection):
  1908. #
  1909. # insert a vertex at x,y in seg,vert, if needed
  1910. #
  1911. d0 = (path[seg][vert][X]-x)**2 + (path[seg][vert][Y]-y)**2
  1912. d1 = (path[seg][vert+1][X]-x)**2 + (path[seg][vert+1][Y]-y)**2
  1913. #print "check insert seg",seg,"vert",vert,"intersection",intersection
  1914. if ((d0 > EPS) & (d1 > EPS)):
  1915. #print " added intersection vertex",vert+1
  1916. path[seg].insert((vert+1),[x,y,intersection])
  1917. return 1
  1918. elif (d0 < EPS):
  1919. if (path[seg][vert][INTERSECT] == []):
  1920. path[seg][vert][INTERSECT] = intersection
  1921. #print " added d0",vert
  1922. return 0
  1923. elif (d1 < EPS):
  1924. if (path[seg][vert+1][INTERSECT] == []):
  1925. path[seg][vert+1][INTERSECT] = intersection
  1926. #print " added d1",vert+1
  1927. return 0
  1928. else:
  1929. #print " shouldn't happen: d0",d0,"d1",d1
  1930. return 0
  1931. def union(i,path,intersections,sign):
  1932. #
  1933. # return edge to exit intersection i for a union
  1934. #
  1935. #print "union: intersection",i,"in",intersections
  1936. seg0 = intersections[i][0][SEG]
  1937. #print "seg0",seg0
  1938. vert0 = intersections[i][0][VERT]
  1939. x0 = path[seg0][vert0][X]
  1940. y0 = path[seg0][vert0][Y]
  1941. if (vert0 < (len(path[seg0])-1)):
  1942. vert1 = vert0 + 1
  1943. else:
  1944. vert1 = 0
  1945. x1 = path[seg0][vert1][X]
  1946. y1 = path[seg0][vert1][Y]
  1947. dx01 = x1-x0
  1948. dy01 = y1-y0
  1949. sega = intersections[i][A][SEG]
  1950. verta = intersections[i][A][VERT]
  1951. xa = path[sega][verta][X]
  1952. ya = path[sega][verta][Y]
  1953. if (verta < (len(path[sega])-1)):
  1954. vertb = verta + 1
  1955. else:
  1956. vertb = 0
  1957. xb = path[sega][vertb][X]
  1958. yb = path[sega][vertb][Y]
  1959. dxab = xb-xa
  1960. dyab = yb-ya
  1961. dot = dxab*dy01 - dyab*dx01
  1962. #print " dot",dot
  1963. if (abs(dot) <= EPS):
  1964. print " colinear"
  1965. seg = []
  1966. vert= []
  1967. elif (dot > EPS):
  1968. seg = intersections[i][(1-sign)/2][SEG]
  1969. vert = intersections[i][(1-sign)/2][VERT]
  1970. else:
  1971. seg = intersections[i][(1+sign)/2][SEG]
  1972. vert = intersections[i][(1+sign)/2][VERT]
  1973. return [seg,vert]
  1974. # MODIFIED
  1975. def read(filename): #MOD
  1976. event = None #MOD
  1977. print "read(event)"
  1978. global vertices, faces, boundarys, toolpaths, contours, slices,\
  1979. xmin, xmax, ymin, ymax, zmin, zmax, noise_flag
  1980. #
  1981. # read file
  1982. #
  1983. faces = []
  1984. contours = [[]]
  1985. boundarys = [[]]
  1986. toolpaths = [[]]
  1987. slices = [[]]
  1988. #filename = infile.get() #MOD
  1989. if ((find(filename,".cmp") != -1) | (find(filename,".CMP")!= -1) \
  1990. | (find(filename,".sol")!= -1) | (find(filename,".SOL") != -1) \
  1991. | (find(filename,".plc")!= -1) | (find(filename,".PLC")!= -1) \
  1992. | (find(filename,".sts")!= -1) | (find(filename,".STS")!= -1) \
  1993. | (find(filename,".gtl")!= -1) | (find(filename,".GTL")!= -1) \
  1994. | (find(filename,".stc")!= -1) | (find(filename,".STC")!= -1)):
  1995. print "reading Gerber file",filename
  1996. read_Gerber(filename)
  1997. elif ((find(filename,".drl") != -1) | (find(filename,".DRL") != -1) | \
  1998. (find(filename,".drd") != -1) | (find(filename,".DRD") != -1)):
  1999. print "reading Excellon file",filename
  2000. read_Excellon(filename)
  2001. elif ((find(filename,".dxf") != -1) | (find(filename,".DXF") != -1)):
  2002. print "reading DXF file",filename
  2003. read_DXF(filename)
  2004. elif (find(filename,".stl") != -1):
  2005. print "reading STL file",filename
  2006. read_STL(filename)
  2007. elif (find(filename,".jpg") != -1):
  2008. print "reading image file",filename
  2009. read_image(filename)
  2010. elif (find(filename,".svg") != -1):
  2011. print "reading SVG file",filename
  2012. read_SVG(filename)
  2013. else:
  2014. print "unsupported file type"
  2015. return
  2016. xmin = HUGE
  2017. xmax = -HUGE
  2018. ymin = HUGE
  2019. ymax = -HUGE
  2020. zmin = HUGE
  2021. zmax = -HUGE
  2022. if (len(boundarys) == 1):
  2023. #
  2024. # 2D file
  2025. #
  2026. boundary = boundarys[0]
  2027. sum = 0
  2028. for segment in range(len(boundary)):
  2029. sum += len(boundary[segment])
  2030. for vertex in range(len(boundary[segment])):
  2031. x = boundary[segment][vertex][X]
  2032. y = boundary[segment][vertex][Y]
  2033. if (x < xmin): xmin = x
  2034. if (x > xmax): xmax = x
  2035. if (y < ymin): ymin = y
  2036. if (y > ymax): ymax = y
  2037. print " found",len(boundary),"polygons,",sum,"vertices"
  2038. print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin)
  2039. print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin)
  2040. if (noise_flag == 1):
  2041. if ((xmax-xmin) < (ymax-ymin)):
  2042. delta = (xmax-xmin)*NOISE
  2043. else:
  2044. delta = (ymax-ymin)*NOISE
  2045. for segment in range(len(boundary)):
  2046. for vertex in range(len(boundary[segment])):
  2047. boundary[segment][vertex][X] += gauss(0,delta)
  2048. boundary[segment][vertex][Y] += gauss(0,delta)
  2049. print " added %.3g perturbation"%delta
  2050. boundarys[0] = boundary
  2051. elif (len(boundarys) > 1):
  2052. #
  2053. # 3D layers
  2054. #
  2055. for layer in range(len(boundarys)):
  2056. boundary = boundarys[layer]
  2057. sum = 0
  2058. for segment in range(len(boundary)):
  2059. sum += len(boundary[segment])
  2060. for vertex in range(len(boundary[segment])):
  2061. x = boundary[segment][vertex][X3]
  2062. y = boundary[segment][vertex][Y3]
  2063. z = boundary[segment][vertex][Z3]
  2064. if (x < xmin): xmin = x
  2065. if (x > xmax): xmax = x
  2066. if (y < ymin): ymin = y
  2067. if (y > ymax): ymax = y
  2068. if (z < zmin): zmin = z
  2069. if (z > zmax): zmax = z
  2070. print " layer",layer,"found",len(boundary),"polygon(s),",sum,"vertices"
  2071. if (noise_flag == 1):
  2072. if ((xmax-xmin) < (ymax-ymin)):
  2073. delta = (xmax-xmin)*NOISE
  2074. else:
  2075. delta = (ymax-ymin)*NOISE
  2076. for segment in range(len(boundary)):
  2077. for vertex in range(len(boundary[segment])):
  2078. boundary[segment][vertex][X3] += gauss(0,delta)
  2079. boundary[segment][vertex][Y3] += gauss(0,delta)
  2080. boundary[segment][vertex][Z3] += gauss(0,delta)
  2081. boundarys[layer] = boundary
  2082. print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin)
  2083. print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin)
  2084. print " zmin: %0.3g "%zmin,"zmax: %0.3g "%zmax,"dy: %0.3g "%(zmax-zmin)
  2085. print " added %.3g perturbation"%delta
  2086. elif (faces != []):
  2087. #
  2088. # 3D faces
  2089. #
  2090. for vertex in range(len(vertices)):
  2091. x = vertices[vertex][X]
  2092. y = vertices[vertex][Y]
  2093. z = vertices[vertex][Z]
  2094. if (x < xmin): xmin = x
  2095. if (x > xmax): xmax = x
  2096. if (y < ymin): ymin = y
  2097. if (y > ymax): ymax = y
  2098. if (z < zmin): zmin = z
  2099. if (z > zmax): zmax = z
  2100. print " found",len(vertices),"vertices,",len(faces),"faces"
  2101. print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"dx: %0.3g "%(xmax-xmin)
  2102. print " ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax,"dy: %0.3g "%(ymax-ymin)
  2103. print " zmin: %0.3g "%zmin,"zmax: %0.3g "%zmax,"dz: %0.3g "%(zmax-zmin)
  2104. if (noise_flag == 1):
  2105. delta = (zmax-zmin)*NOISE
  2106. for vertex in range(len(vertices)):
  2107. vertices[vertex][X] += gauss(0,delta)
  2108. vertices[vertex][Y] += gauss(0,delta)
  2109. vertices[vertex][Z] += gauss(0,delta)
  2110. print " added %.3g perturbation"%delta
  2111. else:
  2112. print "shouldn't happen in read"
  2113. #camselect(event) MOD
  2114. print "End read(event)"
  2115. def write_G(boundarys, toolpaths, scale=1.0, thickness=1.0, feed=1, zclear=0.1, zcut=-0.005):
  2116. X = 0
  2117. Y = 1
  2118. #global boundarys, toolpaths, xmin, ymin, zmin, zmax
  2119. #
  2120. # G code output
  2121. #
  2122. #xyscale = float(sxyscale.get())
  2123. xyscale = scale
  2124. #zscale = float(sxyscale.get())
  2125. #zscale = scale
  2126. #dlayer = float(sthickness.get())/zscale
  2127. #dlayer = thickness/zscale
  2128. #feed = float(sfeed.get())
  2129. #xoff = float(sxmin.get()) - xmin*xyscale
  2130. #yoff = float(symin.get()) - ymin*xyscale
  2131. #cool = icool.get()
  2132. #text = outfile.get()
  2133. output = "" #file = open(text, 'w')
  2134. output += "%\n" #file.write("%\n")
  2135. output += "O1234\n" #file.write("O1234\n")
  2136. #file.write("T"+stool.get()+"M06\n") # tool
  2137. output += "G90G54\n" #file.write("G90G54\n") # absolute positioning with respect to set origin
  2138. output += "F%0.3f\n"%feed #file.write("F%0.3f\n"%feed) # feed rate
  2139. #file.write("S"+sspindle.get()+"\n") # spindle speed
  2140. #if (cool == TRUE): file.write("M08\n") # coolant on
  2141. output += "G00Z%.4f\n"%zclear #file.write("G00Z"+szup.get()+"\n") # move up before starting spindle
  2142. output += "M03\n" #file.write("M03\n") # spindle on clockwise
  2143. nsegment = 0
  2144. for layer in range((len(boundarys)-1),-1,-1):
  2145. if (toolpaths[layer] == []):
  2146. path = boundarys[layer]
  2147. else:
  2148. path = toolpaths[layer]
  2149. #if (szdown.get() == " "):
  2150. # zdown = zoff + zmin + (layer-0.50)*dlayer
  2151. #else:
  2152. # zdown = float(szdown.get())
  2153. for segment in range(len(path)):
  2154. nsegment += 1
  2155. vertex = 0
  2156. x = path[segment][vertex][X]*xyscale #+ xoff
  2157. y = path[segment][vertex][Y]*xyscale #+ yoff
  2158. output += "G00X%0.4f"%x+"Y%0.4f"%y+"Z%.4f"%zclear+"\n" #file.write("G00X%0.4f"%x+"Y%0.4f"%y+"Z"+szup.get()+"\n") # rapid motion
  2159. output += "G01Z%0.4f"%zcut+"\n" #file.write("G01Z%0.4f"%zdown+"\n") # linear motion
  2160. for vertex in range(1,len(path[segment])):
  2161. x = path[segment][vertex][X]*xyscale #+ xoff
  2162. y = path[segment][vertex][Y]*xyscale #+ yoff
  2163. output += "X%0.4f"%x+"Y%0.4f"%y+"\n" #file.write("X%0.4f"%x+"Y%0.4f"%y+"\n")
  2164. output += "Z%.4f\n"%zclear #file.write("Z"+szup.get()+"\n")
  2165. output += "G00Z%.4f\n"%zclear #file.write("G00Z"+szup.get()+"\n") # move up before stopping spindle
  2166. output += "M05\n" #file.write("M05\n") # spindle stop
  2167. #if (cool == TRUE): file.write("M09\n") # coolant off
  2168. output += "M30\n" #file.write("M30\n") # program end and reset
  2169. output += "%\n" #file.write("%\n")
  2170. #file.close()
  2171. print "wrote",nsegment,"G code toolpath segments"
  2172. return output
  2173. ################ end of cam.py #############