camlib.py 79 KB

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