camlib.py 74 KB

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