cirkuix.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. from gi.repository import Gtk
  2. import matplotlib.pyplot as plt
  3. plt.ioff()
  4. from matplotlib.figure import Figure
  5. from numpy import arange, sin, pi
  6. from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
  7. #from matplotlib.backends.backend_gtk3 import NavigationToolbar2GTK3 as NavigationToolbar
  8. #from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
  9. #from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
  10. #import cairo
  11. from camlib import *
  12. class CirkuixObj:
  13. def __init__(self, name, kind):
  14. self.name = name
  15. self.kind = kind
  16. class CirkuixGerber(CirkuixObj, Gerber):
  17. def __init__(self, name):
  18. Gerber.__init__(self)
  19. CirkuixObj.__init__(self, name, "gerber")
  20. self.fields = [{"name":"plot",
  21. "type":bool,
  22. "value":True,
  23. "get":None,
  24. "set":None,
  25. "onchange":None},
  26. {}]
  27. class CirkuixExcellon(CirkuixObj, Excellon):
  28. def __init__(self, name):
  29. Excellon.__init__(self)
  30. CirkuixObj.__init__(self, name, "excellon")
  31. self.options = {"plot": True,
  32. "solid": False,
  33. "multicolored": False}
  34. class CirkuixCNCjob(CirkuixObj, CNCjob):
  35. def __init__(self, name):
  36. CNCjob.__init__(self)
  37. CirkuixObj.__init__(self, name, "cncjob")
  38. self.options = {"plot": True}
  39. class CirkuixGeometry(CirkuixObj, Geometry):
  40. def __init__(self, name):
  41. CirkuixObj.__init__(self, name, "geometry")
  42. self.options = {"plot": True,
  43. "solid": False,
  44. "multicolored": False}
  45. class CirkuixObjForm:
  46. def __init__(self, container, Cobj):
  47. self.Cobj = Cobj
  48. self.container = container
  49. self.fields = {}
  50. def populate(self):
  51. return
  52. def save(self):
  53. return
  54. #class CirkuixGerberForm(CirkuixObjForm)
  55. def get_entry_text(entry):
  56. return entry.get_text()
  57. def get_entry_int(entry):
  58. return int(entry.get_text())
  59. def get_entry_float(entry):
  60. return float(entry.get_text())
  61. def get_entry_eval(entry):
  62. return eval(entry.get_text)
  63. getters = {"entry_text":get_entry_text,
  64. "entry_int":get_entry_int,
  65. "entry_float":get_entry_float,
  66. "entry_eval":get_entry_eval}
  67. setters = {"entry"}
  68. class App:
  69. def __init__(self):
  70. ########################################
  71. ## GUI ##
  72. ########################################
  73. self.gladefile = "cirkuix.ui"
  74. self.builder = Gtk.Builder()
  75. self.builder.add_from_file(self.gladefile)
  76. self.window = self.builder.get_object("window1")
  77. self.window.set_title("Cirkuix")
  78. self.positionLabel = self.builder.get_object("label3")
  79. self.grid = self.builder.get_object("grid1")
  80. self.notebook = self.builder.get_object("notebook1")
  81. ## Event handling ##
  82. self.builder.connect_signals(self)
  83. ## Make plot area ##
  84. self.figure = None
  85. self.axes = None
  86. self.canvas = None
  87. self.plot_setup()
  88. self.setup_component_viewer()
  89. self.setup_component_editor()
  90. ########################################
  91. ## DATA ##
  92. ########################################
  93. self.stuff = {} # CirkuixObj's by name
  94. self.mouse = None
  95. # What is selected by the user. It is
  96. # a key if self.stuff
  97. self.selected_item_name = None
  98. ########################################
  99. ## START ##
  100. ########################################
  101. self.window.show_all()
  102. Gtk.main()
  103. def plot_setup(self):
  104. self.figure = Figure(dpi=50)
  105. self.axes = self.figure.add_axes([0.05,0.05,0.9,0.9])
  106. self.axes.set_aspect(1)
  107. #t = arange(0.0,5.0,0.01)
  108. #s = sin(2*pi*t)
  109. #self.axes.plot(t,s)
  110. self.axes.grid()
  111. self.figure.patch.set_visible(False)
  112. self.canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
  113. self.canvas.set_hexpand(1)
  114. self.canvas.set_vexpand(1)
  115. ########################################
  116. ## EVENTS ##
  117. ########################################
  118. self.canvas.mpl_connect('button_press_event', self.on_click_over_plot)
  119. self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot)
  120. self.canvas.set_can_focus(True) # For key press
  121. self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
  122. self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
  123. self.grid.attach(self.canvas,0,0,600,400)
  124. def zoom(self, factor, center=None):
  125. '''
  126. Zooms the plot by factor around a given
  127. center point. Takes care of re-drawing.
  128. '''
  129. xmin, xmax = self.axes.get_xlim()
  130. ymin, ymax = self.axes.get_ylim()
  131. width = xmax-xmin
  132. height = ymax-ymin
  133. if center == None:
  134. center = [(xmin+xmax)/2.0, (ymin+ymax)/2.0]
  135. # For keeping the point at the pointer location
  136. relx = (xmax-center[0])/width
  137. rely = (ymax-center[1])/height
  138. new_width = width/factor
  139. new_height = height/factor
  140. self.axes.set_xlim((center[0]-new_width*(1-relx), center[0]+new_width*relx))
  141. self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely))
  142. self.canvas.queue_draw()
  143. def plot_gerber(self, gerber):
  144. gerber.create_geometry()
  145. # Options
  146. mergepolys = self.builder.get_object("cb_mergepolys").get_active()
  147. multicolored = self.builder.get_object("cb_multicolored").get_active()
  148. geometry = None
  149. if mergepolys:
  150. geometry = gerber.solid_geometry
  151. else:
  152. geometry = gerber.buffered_paths + \
  153. [poly['polygon'] for poly in gerber.regions] + \
  154. gerber.flash_geometry
  155. linespec = None
  156. if multicolored:
  157. linespec = '-'
  158. else:
  159. linespec = 'k-'
  160. for poly in geometry:
  161. x, y = poly.exterior.xy
  162. #a.plot(x, y)
  163. self.axes.plot(x, y, linespec)
  164. for ints in poly.interiors:
  165. x, y = ints.coords.xy
  166. self.axes.plot(x, y, linespec)
  167. self.canvas.queue_draw()
  168. def plot_excellon(self, excellon):
  169. excellon.create_geometry()
  170. # Plot excellon
  171. for geo in excellon.solid_geometry:
  172. x, y = geo.exterior.coords.xy
  173. self.axes.plot(x, y, 'r-')
  174. for ints in geo.interiors:
  175. x, y = ints.coords.xy
  176. self.axes.plot(x, y, 'g-')
  177. self.canvas.queue_draw()
  178. def plot_cncjob(self, job):
  179. #job.gcode_parse()
  180. tooldia_text = self.builder.get_object("entry_tooldia").get_text()
  181. tooldia_val = eval(tooldia_text)
  182. job.plot2(self.axes, tooldia=tooldia_val)
  183. self.canvas.queue_draw()
  184. def plot_geometry(self, geometry):
  185. for geo in geometry.solid_geometry:
  186. x, y = geo.exterior.coords.xy
  187. self.axes.plot(x, y, 'r-')
  188. for ints in geo.interiors:
  189. x, y = ints.coords.xy
  190. self.axes.plot(x, y, 'r-')
  191. self.canvas.queue_draw()
  192. def setup_component_viewer(self):
  193. '''
  194. List or Tree where whatever has been loaded or created is
  195. displayed.
  196. '''
  197. self.store = Gtk.ListStore(str)
  198. self.tree = Gtk.TreeView(self.store)
  199. select = self.tree.get_selection()
  200. self.signal_id = select.connect("changed", self.on_tree_selection_changed)
  201. renderer = Gtk.CellRendererText()
  202. column = Gtk.TreeViewColumn("Title", renderer, text=0)
  203. self.tree.append_column(column)
  204. self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
  205. def setup_component_editor(self):
  206. box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
  207. label1 = Gtk.Label("Choose an item from Project")
  208. box1.pack_start(label1, False, False, 1)
  209. self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
  210. def build_list(self):
  211. self.store.clear()
  212. for key in self.stuff:
  213. self.store.append([key])
  214. def build_gerber_ui(self):
  215. print "build_gerber_ui()"
  216. osw = self.builder.get_object("offscrwindow_gerber")
  217. box1 = self.builder.get_object("box_gerber")
  218. osw.remove(box1)
  219. self.notebook.append_page(box1, Gtk.Label("Selection"))
  220. gerber = self.stuff[self.selected_item_name]
  221. entry_name = self.builder.get_object("entry_gerbername")
  222. entry_name.set_text(self.selected_item_name)
  223. entry_name.connect("activate", self.on_activate_name)
  224. box1.show()
  225. def build_excellon_ui(self):
  226. print "build_excellon_ui()"
  227. osw = self.builder.get_object("offscrwindow_excellon")
  228. box1 = self.builder.get_object("box_excellon")
  229. osw.remove(box1)
  230. self.notebook.append_page(box1, Gtk.Label("Selection"))
  231. entry_name = self.builder.get_object("entry_excellonname")
  232. entry_name.set_text(self.selected_item_name)
  233. entry_name.connect("activate", self.on_activate_name)
  234. box1.show()
  235. def build_cncjob_ui(self):
  236. print "build_cncjob_ui()"
  237. osw = self.builder.get_object("offscrwindow_cncjob")
  238. box1 = self.builder.get_object("box_cncjob")
  239. osw.remove(box1)
  240. self.notebook.append_page(box1, Gtk.Label("Selection"))
  241. entry_name = self.builder.get_object("entry_cncjobname")
  242. entry_name.set_text(self.selected_item_name)
  243. entry_name.connect("activate", self.on_activate_name)
  244. box1.show()
  245. def build_geometry_ui(self):
  246. print "build_geometry_ui()"
  247. osw = self.builder.get_object("offscrwindow_geometry")
  248. box1 = self.builder.get_object("box_geometry")
  249. osw.remove(box1)
  250. self.notebook.append_page(box1, Gtk.Label("Selection"))
  251. entry_name = self.builder.get_object("entry_geometryname")
  252. entry_name.set_text(self.selected_item_name)
  253. entry_name.connect("activate", self.on_activate_name)
  254. box1.show()
  255. def plot_all(self):
  256. self.clear_plots()
  257. plotter = {"gerber":self.plot_gerber,
  258. "excellon":self.plot_excellon,
  259. "cncjob":self.plot_cncjob,
  260. "geometry":self.plot_geometry}
  261. for i in self.stuff:
  262. kind = self.stuff[i].kind
  263. plotter[kind](self.stuff[i])
  264. self.on_zoom_fit(None)
  265. self.axes.grid()
  266. self.canvas.queue_draw()
  267. def clear_plots(self):
  268. self.axes.cla()
  269. self.canvas.queue_draw()
  270. ########################################
  271. ## EVENT HANDLERS ##
  272. ########################################
  273. def on_eval_update(self, widget):
  274. # TODO: error handling here
  275. widget.set_text(str(eval(widget.get_text())))
  276. def on_generate_isolation(self, widget):
  277. print "Generating Isolation Geometry:"
  278. # Get required info
  279. tooldia = self.builder.get_object("entry_gerberisotooldia").get_text()
  280. tooldia = eval(tooldia)
  281. print "tooldia:", tooldia
  282. # Generate
  283. iso = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
  284. # TODO: This will break if there is something with this name already
  285. iso_name = self.selected_item_name + "_iso"
  286. geo = CirkuixGeometry(iso_name)
  287. geo.solid_geometry = iso
  288. # Add to App and update.
  289. self.stuff[iso_name] = geo
  290. self.build_list()
  291. def on_delete(self, widget):
  292. self.stuff.pop(self.selected_item_name)
  293. #self.tree.get_selection().disconnect(self.signal_id)
  294. self.build_list() # Update the items list
  295. #self.signal_id = self.tree.get_selection().connect(
  296. # "changed", self.on_tree_selection_changed)
  297. self.plot_all()
  298. #self.notebook.set_current_page(1)
  299. def on_replot(self, widget):
  300. self.plot_all()
  301. def on_clear_plots(self, widget):
  302. self.clear_plots()
  303. def on_activate_name(self, entry):
  304. '''
  305. Hitting 'Enter' after changing the name of an item
  306. updates the item dictionary and re-builds the item list.
  307. '''
  308. print "Changing name"
  309. self.tree.get_selection().disconnect(self.signal_id)
  310. new_name = entry.get_text() # Get from form
  311. self.stuff[new_name] = self.stuff.pop(self.selected_item_name) # Update dictionary
  312. self.selected_item_name = new_name # Update selection name
  313. self.build_list() # Update the items list
  314. self.signal_id = self.tree.get_selection().connect(
  315. "changed", self.on_tree_selection_changed)
  316. def on_tree_selection_changed(self, selection):
  317. model, treeiter = selection.get_selected()
  318. if treeiter != None:
  319. print "You selected", model[treeiter][0]
  320. else:
  321. return # TODO: Clear "Selected" page
  322. self.selected_item_name = model[treeiter][0]
  323. # Remove the current selection page
  324. # from the notebook
  325. # TODO: Assuming it was last page or #2. Find the right page
  326. self.builder.get_object("notebook1").remove_page(2)
  327. # Determine the kind of item selected
  328. kind = self.stuff[model[treeiter][0]].kind
  329. # Build the UI
  330. builder = {"gerber": self.build_gerber_ui,
  331. "excellon": self.build_excellon_ui,
  332. "cncjob": self.build_cncjob_ui,
  333. "geometry": self.build_geometry_ui}
  334. builder[kind]()
  335. def on_filequit(self, param):
  336. print "quit from menu"
  337. self.window.destroy()
  338. Gtk.main_quit()
  339. def on_closewindow(self, param):
  340. print "quit from X"
  341. self.window.destroy()
  342. Gtk.main_quit()
  343. def file_chooser_action(self, on_success):
  344. '''
  345. Opens the file chooser and runs on_success
  346. upon completion of valid file choice.
  347. '''
  348. dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
  349. Gtk.FileChooserAction.OPEN,
  350. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  351. Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
  352. response = dialog.run()
  353. if response == Gtk.ResponseType.OK:
  354. on_success(self, dialog.get_filename())
  355. elif response == Gtk.ResponseType.CANCEL:
  356. print("Cancel clicked")
  357. dialog.destroy()
  358. def on_fileopengerber(self, param):
  359. def on_success(self, filename):
  360. name = filename.split('/')[-1].split('\\')[-1]
  361. gerber = CirkuixGerber(name)
  362. gerber.parse_file(filename)
  363. self.store.append([name])
  364. #self.gerbers.append(gerber)
  365. self.stuff[name] = gerber
  366. self.plot_gerber(gerber)
  367. self.on_zoom_fit(None)
  368. self.file_chooser_action(on_success)
  369. def on_fileopenexcellon(self, param):
  370. def on_success(self, filename):
  371. name = filename.split('/')[-1].split('\\')[-1]
  372. excellon = CirkuixExcellon(name)
  373. excellon.parse_file(filename)
  374. self.store.append([name])
  375. #self.excellons.append(excellon)
  376. self.stuff[name] = excellon
  377. self.plot_excellon(excellon)
  378. self.on_zoom_fit(None)
  379. self.file_chooser_action(on_success)
  380. def on_fileopengcode(self, param):
  381. def on_success(self, filename):
  382. name = filename.split('/')[-1].split('\\')[-1]
  383. f = open(filename)
  384. gcode = f.read()
  385. f.close()
  386. job = CirkuixCNCjob(name)
  387. job.gcode = gcode
  388. job.gcode_parse()
  389. job.create_geometry()
  390. self.store.append([name])
  391. #self.cncjobs.append(job)
  392. self.stuff[name] = job
  393. self.plot_cncjob(job)
  394. self.on_zoom_fit(None)
  395. self.file_chooser_action(on_success)
  396. def on_mouse_move_over_plot(self, event):
  397. try: # May fail in case mouse not within axes
  398. self.positionLabel.set_label("X: %.4f Y: %.4f"%(
  399. event.xdata, event.ydata))
  400. self.mouse = [event.xdata, event.ydata]
  401. except:
  402. self.positionLabel.set_label("")
  403. self.mouse = None
  404. def on_click_over_plot(self, event):
  405. # For key presses
  406. self.canvas.grab_focus()
  407. print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
  408. event.button, event.x, event.y, event.xdata, event.ydata)
  409. def on_zoom_in(self, event):
  410. self.zoom(1.5)
  411. return
  412. def on_zoom_out(self, event):
  413. self.zoom(1/1.5)
  414. def on_zoom_fit(self, event):
  415. xmin, ymin, xmax, ymax = get_bounds(self.stuff)
  416. width = xmax-xmin
  417. height = ymax-ymin
  418. r = width/height
  419. Fw, Fh = self.canvas.get_width_height()
  420. Fr = float(Fw)/Fh
  421. print "Window aspect ratio:", Fr
  422. print "Data aspect ratio:", r
  423. #self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
  424. #self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
  425. if r > Fr:
  426. self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
  427. ycenter = (ymin+ymax)/2.0
  428. newheight = height*r/Fr
  429. self.axes.set_ylim((ycenter-newheight/2.0, ycenter+newheight/2.0))
  430. else:
  431. self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
  432. xcenter = (xmax+ymin)/2.0
  433. newwidth = width*Fr/r
  434. self.axes.set_xlim((xcenter-newwidth/2.0, xcenter+newwidth/2.0))
  435. self.canvas.queue_draw()
  436. return
  437. def on_scroll_over_plot(self, event):
  438. print "Scroll"
  439. center = [event.xdata, event.ydata]
  440. if sign(event.step):
  441. self.zoom(1.5, center=center)
  442. else:
  443. self.zoom(1/1.5, center=center)
  444. def on_window_scroll(self, event):
  445. print "Scroll"
  446. def on_key_over_plot(self, event):
  447. print 'you pressed', event.key, event.xdata, event.ydata
  448. if event.key == '1': # 1
  449. self.on_zoom_fit(None)
  450. return
  451. if event.key == '2': # 2
  452. self.zoom(1/1.5, self.mouse)
  453. return
  454. if event.key == '3': # 3
  455. self.zoom(1.5, self.mouse)
  456. return
  457. app = App()