camlib.py 86 KB

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