camlib.py 87 KB

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