camlib.py 74 KB

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