cirkuix.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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 setup_component_viewer(self):
  185. '''
  186. List or Tree where whatever has been loaded or created is
  187. displayed.
  188. '''
  189. self.store = Gtk.ListStore(str)
  190. self.tree = Gtk.TreeView(self.store)
  191. select = self.tree.get_selection()
  192. self.signal_id = select.connect("changed", self.on_tree_selection_changed)
  193. renderer = Gtk.CellRendererText()
  194. column = Gtk.TreeViewColumn("Title", renderer, text=0)
  195. self.tree.append_column(column)
  196. self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
  197. def setup_component_editor(self):
  198. box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
  199. label1 = Gtk.Label("Choose an item from Project")
  200. box1.pack_start(label1, False, False, 1)
  201. self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
  202. def build_list(self):
  203. self.store.clear()
  204. for key in self.stuff:
  205. self.store.append([key])
  206. def build_gerber_ui(self):
  207. print "build_gerber_ui()"
  208. osw = self.builder.get_object("offscrwindow_gerber")
  209. box1 = self.builder.get_object("box_gerber")
  210. osw.remove(box1)
  211. self.notebook.append_page(box1, Gtk.Label("Selection"))
  212. gerber = self.stuff[self.selected_item_name]
  213. entry_name = self.builder.get_object("entry_gerbername")
  214. entry_name.set_text(self.selected_item_name)
  215. entry_name.connect("activate", self.on_activate_name)
  216. box1.show()
  217. def build_excellon_ui(self):
  218. print "build_excellon_ui()"
  219. osw = self.builder.get_object("offscrwindow_excellon")
  220. box1 = self.builder.get_object("box_excellon")
  221. osw.remove(box1)
  222. self.notebook.append_page(box1, Gtk.Label("Selection"))
  223. entry_name = self.builder.get_object("entry_excellonname")
  224. entry_name.set_text(self.selected_item_name)
  225. entry_name.connect("activate", self.on_activate_name)
  226. box1.show()
  227. def build_cncjob_ui(self):
  228. print "build_cncjob_ui()"
  229. osw = self.builder.get_object("offscrwindow_cncjob")
  230. box1 = self.builder.get_object("box_cncjob")
  231. osw.remove(box1)
  232. self.notebook.append_page(box1, Gtk.Label("Selection"))
  233. entry_name = self.builder.get_object("entry_cncjobname")
  234. entry_name.set_text(self.selected_item_name)
  235. entry_name.connect("activate", self.on_activate_name)
  236. box1.show()
  237. def plot_all(self):
  238. self.clear_plots()
  239. plotter = {"gerber":self.plot_gerber,
  240. "excellon":self.plot_excellon,
  241. "cncjob":self.plot_cncjob}
  242. for i in self.stuff:
  243. kind = self.stuff[i].kind
  244. plotter[kind](self.stuff[i])
  245. self.on_zoom_fit(None)
  246. self.axes.grid()
  247. self.canvas.queue_draw()
  248. def clear_plots(self):
  249. self.axes.cla()
  250. self.canvas.queue_draw()
  251. ########################################
  252. ## EVENT HANDLERS ##
  253. ########################################
  254. def on_delete(self, widget):
  255. self.stuff.pop(self.selected_item_name)
  256. #self.tree.get_selection().disconnect(self.signal_id)
  257. self.build_list() # Update the items list
  258. #self.signal_id = self.tree.get_selection().connect(
  259. # "changed", self.on_tree_selection_changed)
  260. self.plot_all()
  261. #self.notebook.set_current_page(1)
  262. def on_replot(self, widget):
  263. self.plot_all()
  264. def on_clear_plots(self, widget):
  265. self.clear_plots()
  266. def on_activate_name(self, entry):
  267. '''
  268. Hitting 'Enter' after changing the name of an item
  269. updates the item dictionary and re-builds the item list.
  270. '''
  271. print "Changing name"
  272. self.tree.get_selection().disconnect(self.signal_id)
  273. new_name = entry.get_text() # Get from form
  274. self.stuff[new_name] = self.stuff.pop(self.selected_item_name) # Update dictionary
  275. self.selected_item_name = new_name # Update selection name
  276. self.build_list() # Update the items list
  277. self.signal_id = self.tree.get_selection().connect(
  278. "changed", self.on_tree_selection_changed)
  279. def on_tree_selection_changed(self, selection):
  280. model, treeiter = selection.get_selected()
  281. if treeiter != None:
  282. print "You selected", model[treeiter][0]
  283. else:
  284. return # TODO: Clear "Selected" page
  285. self.selected_item_name = model[treeiter][0]
  286. # Remove the current selection page
  287. # from the notebook
  288. # TODO: Assuming it was last page or #2. Find the right page
  289. self.builder.get_object("notebook1").remove_page(2)
  290. # Determine the kind of item selected
  291. kind = self.stuff[model[treeiter][0]].kind
  292. # Build the UI
  293. builder = {"gerber": self.build_gerber_ui,
  294. "excellon": self.build_excellon_ui,
  295. "cncjob": self.build_cncjob_ui}
  296. builder[kind]()
  297. def on_filequit(self, param):
  298. print "quit from menu"
  299. self.window.destroy()
  300. Gtk.main_quit()
  301. def on_closewindow(self, param):
  302. print "quit from X"
  303. self.window.destroy()
  304. Gtk.main_quit()
  305. def file_chooser_action(self, on_success):
  306. '''
  307. Opens the file chooser and runs on_success
  308. upon completion of valid file choice.
  309. '''
  310. dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
  311. Gtk.FileChooserAction.OPEN,
  312. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  313. Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
  314. response = dialog.run()
  315. if response == Gtk.ResponseType.OK:
  316. on_success(self, dialog.get_filename())
  317. elif response == Gtk.ResponseType.CANCEL:
  318. print("Cancel clicked")
  319. dialog.destroy()
  320. def on_fileopengerber(self, param):
  321. def on_success(self, filename):
  322. name = filename.split('/')[-1].split('\\')[-1]
  323. gerber = CirkuixGerber(name)
  324. gerber.parse_file(filename)
  325. self.store.append([name])
  326. #self.gerbers.append(gerber)
  327. self.stuff[name] = gerber
  328. self.plot_gerber(gerber)
  329. self.on_zoom_fit(None)
  330. self.file_chooser_action(on_success)
  331. def on_fileopenexcellon(self, param):
  332. def on_success(self, filename):
  333. name = filename.split('/')[-1].split('\\')[-1]
  334. excellon = CirkuixExcellon(name)
  335. excellon.parse_file(filename)
  336. self.store.append([name])
  337. #self.excellons.append(excellon)
  338. self.stuff[name] = excellon
  339. self.plot_excellon(excellon)
  340. self.on_zoom_fit(None)
  341. self.file_chooser_action(on_success)
  342. def on_fileopengcode(self, param):
  343. def on_success(self, filename):
  344. name = filename.split('/')[-1].split('\\')[-1]
  345. f = open(filename)
  346. gcode = f.read()
  347. f.close()
  348. job = CirkuixCNCjob(name)
  349. job.gcode = gcode
  350. job.gcode_parse()
  351. job.create_geometry()
  352. self.store.append([name])
  353. #self.cncjobs.append(job)
  354. self.stuff[name] = job
  355. self.plot_cncjob(job)
  356. self.on_zoom_fit(None)
  357. self.file_chooser_action(on_success)
  358. def on_mouse_move_over_plot(self, event):
  359. try: # May fail in case mouse not within axes
  360. self.positionLabel.set_label("X: %.4f Y: %.4f"%(
  361. event.xdata, event.ydata))
  362. self.mouse = [event.xdata, event.ydata]
  363. except:
  364. self.positionLabel.set_label("")
  365. self.mouse = None
  366. def on_click_over_plot(self, event):
  367. # For key presses
  368. self.canvas.grab_focus()
  369. print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
  370. event.button, event.x, event.y, event.xdata, event.ydata)
  371. def on_zoom_in(self, event):
  372. self.zoom(1.5)
  373. return
  374. def on_zoom_out(self, event):
  375. self.zoom(1/1.5)
  376. def on_zoom_fit(self, event):
  377. xmin, ymin, xmax, ymax = get_bounds(self.stuff)
  378. width = xmax-xmin
  379. height = ymax-ymin
  380. self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
  381. self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
  382. self.canvas.queue_draw()
  383. return
  384. def on_scroll_over_plot(self, event):
  385. print "Scroll"
  386. center = [event.xdata, event.ydata]
  387. if sign(event.step):
  388. self.zoom(1.5, center=center)
  389. else:
  390. self.zoom(1/1.5, center=center)
  391. def on_window_scroll(self, event):
  392. print "Scroll"
  393. def on_key_over_plot(self, event):
  394. print 'you pressed', event.key, event.xdata, event.ydata
  395. if event.key == '1': # 1
  396. self.on_zoom_fit(None)
  397. return
  398. if event.key == '2': # 2
  399. self.zoom(1/1.5, self.mouse)
  400. return
  401. if event.key == '3': # 3
  402. self.zoom(1.5, self.mouse)
  403. return
  404. app = App()