camlib.py 80 KB

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