cirkuix.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. import threading
  2. from gi.repository import Gtk
  3. #from gi.repository import Gdk
  4. from gi.repository import GLib
  5. from matplotlib.figure import Figure
  6. from numpy import arange, sin, pi
  7. from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
  8. #from matplotlib.backends.backend_gtk3cairo import FigureCanvasGTK3Cairo as FigureCanvas
  9. #from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
  10. from camlib import *
  11. class CirkuixObj:
  12. def __init__(self, name, kind):
  13. self.name = name
  14. self.kind = kind # TODO: Probably not needed
  15. self.axes = None # Matplotlib axes
  16. self.options = {}
  17. def setup_axes(self, figure):
  18. if self.axes is None:
  19. self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], label=self.name)
  20. elif self.axes not in figure.axes:
  21. figure.add_axes(self.axes)
  22. self.axes.patch.set_visible(False) # No background
  23. self.axes.set_aspect(1)
  24. return self.axes
  25. def set_options(self, options):
  26. for name in options:
  27. self.options[name] = options[name]
  28. return
  29. class CirkuixGerber(CirkuixObj, Gerber):
  30. def __init__(self, name):
  31. Gerber.__init__(self)
  32. CirkuixObj.__init__(self, name, "gerber")
  33. self.options = {
  34. "plot": True,
  35. "mergepolys": True,
  36. "multicolored": False,
  37. "solid": False,
  38. "isotooldia": 0.4/25.4,
  39. "cutoutmargin": 0.2,
  40. "cutoutgapsize": 0.15,
  41. "gaps": "tb"
  42. }
  43. def plot(self, figure):
  44. self.setup_axes(figure)
  45. self.create_geometry()
  46. geometry = None
  47. if self.options["mergepolys"]:
  48. geometry = self.solid_geometry
  49. else:
  50. geometry = self.buffered_paths + \
  51. [poly['polygon'] for poly in self.regions] + \
  52. self.flash_geometry
  53. linespec = None
  54. if self.options["multicolored"]:
  55. linespec = '-'
  56. else:
  57. linespec = 'k-'
  58. for poly in geometry:
  59. x, y = poly.exterior.xy
  60. self.axes.plot(x, y, linespec)
  61. for ints in poly.interiors:
  62. x, y = ints.coords.xy
  63. self.axes.plot(x, y, linespec)
  64. class CirkuixExcellon(CirkuixObj, Excellon):
  65. def __init__(self, name):
  66. Excellon.__init__(self)
  67. CirkuixObj.__init__(self, name, "excellon")
  68. def plot(self, figure):
  69. self.setup_axes(figure)
  70. self.create_geometry()
  71. # Plot excellon
  72. for geo in self.solid_geometry:
  73. x, y = geo.exterior.coords.xy
  74. self.axes.plot(x, y, 'r-')
  75. for ints in geo.interiors:
  76. x, y = ints.coords.xy
  77. self.axes.plot(x, y, 'g-')
  78. class CirkuixCNCjob(CirkuixObj, CNCjob):
  79. def __init__(self, name, units="in", kind="generic", z_move=0.1,
  80. feedrate=3.0, z_cut=-0.002, tooldia=0.0):
  81. CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
  82. feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
  83. CirkuixObj.__init__(self, name, "cncjob")
  84. def plot(self, figure):
  85. self.setup_axes(figure)
  86. self.plot2(self.axes)
  87. class CirkuixGeometry(CirkuixObj, Geometry):
  88. def __init__(self, name):
  89. CirkuixObj.__init__(self, name, "geometry")
  90. self.options = {"plot": True,
  91. "solid": False,
  92. "multicolored": False}
  93. def plot(self, figure):
  94. self.setup_axes(figure)
  95. for geo in self.solid_geometry:
  96. if type(geo) == Polygon:
  97. x, y = geo.exterior.coords.xy
  98. self.axes.plot(x, y, 'r-')
  99. for ints in geo.interiors:
  100. x, y = ints.coords.xy
  101. self.axes.plot(x, y, 'r-')
  102. continue
  103. if type(geo) == LineString or type(geo) == LinearRing:
  104. x, y = geo.coords.xy
  105. self.axes.plot(x, y, 'r-')
  106. continue
  107. class App:
  108. def __init__(self):
  109. # Needed to interact with the GUI from other threads.
  110. GLib.threads_init()
  111. ########################################
  112. ## GUI ##
  113. ########################################
  114. self.gladefile = "cirkuix.ui"
  115. self.builder = Gtk.Builder()
  116. self.builder.add_from_file(self.gladefile)
  117. self.window = self.builder.get_object("window1")
  118. self.window.set_title("Cirkuix")
  119. self.position_label = self.builder.get_object("label3")
  120. self.grid = self.builder.get_object("grid1")
  121. self.notebook = self.builder.get_object("notebook1")
  122. self.info_label = self.builder.get_object("label_status")
  123. self.progress_bar = self.builder.get_object("progressbar")
  124. self.progress_bar.set_show_text(True)
  125. ## Event handling ##
  126. self.builder.connect_signals(self)
  127. ## Make plot area ##
  128. self.figure = None
  129. self.axes = None
  130. self.canvas = None
  131. self.plot_setup()
  132. self.setup_component_viewer()
  133. self.setup_component_editor()
  134. ########################################
  135. ## DATA ##
  136. ########################################
  137. self.stuff = {} # CirkuixObj's by name
  138. self.mouse = None
  139. # What is selected by the user. It is
  140. # a key if self.stuff
  141. self.selected_item_name = None
  142. # For debugging only
  143. def someThreadFunc(self):
  144. print "Hello World!"
  145. t = threading.Thread(target=someThreadFunc, args=(self,))
  146. t.start()
  147. ########################################
  148. ## START ##
  149. ########################################
  150. self.window.show_all()
  151. Gtk.main()
  152. def plot_setup(self):
  153. self.figure = Figure(dpi=50)
  154. self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
  155. self.axes.set_aspect(1)
  156. #t = arange(0.0,5.0,0.01)
  157. #s = sin(2*pi*t)
  158. #self.axes.plot(t,s)
  159. self.axes.grid()
  160. self.figure.patch.set_visible(False)
  161. self.canvas = FigureCanvas(self.figure) # a Gtk.DrawingArea
  162. self.canvas.set_hexpand(1)
  163. self.canvas.set_vexpand(1)
  164. ########################################
  165. ## EVENTS ##
  166. ########################################
  167. self.canvas.mpl_connect('button_press_event', self.on_click_over_plot)
  168. self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot)
  169. self.canvas.set_can_focus(True) # For key press
  170. self.canvas.mpl_connect('key_press_event', self.on_key_over_plot)
  171. #self.canvas.mpl_connect('scroll_event', self.on_scroll_over_plot)
  172. self.grid.attach(self.canvas, 0, 0, 600, 400)
  173. def info(self, text):
  174. """
  175. Show text on the status bar.
  176. """
  177. self.info_label.set_text(text)
  178. def zoom(self, factor, center=None):
  179. """
  180. Zooms the plot by factor around a given
  181. center point. Takes care of re-drawing.
  182. """
  183. xmin, xmax = self.axes.get_xlim()
  184. ymin, ymax = self.axes.get_ylim()
  185. width = xmax-xmin
  186. height = ymax-ymin
  187. if center is None:
  188. center = [(xmin+xmax)/2.0, (ymin+ymax)/2.0]
  189. # For keeping the point at the pointer location
  190. relx = (xmax-center[0])/width
  191. rely = (ymax-center[1])/height
  192. new_width = width/factor
  193. new_height = height/factor
  194. #self.axes.set_xlim((center[0]-new_width*(1-relx), center[0]+new_width*relx))
  195. #self.axes.set_ylim((center[1]-new_height*(1-rely), center[1]+new_height*rely))
  196. xmin = center[0]-new_width*(1-relx)
  197. xmax = center[0]+new_width*relx
  198. ymin = center[1]-new_height*(1-rely)
  199. ymax = center[1]+new_height*rely
  200. for name in self.stuff:
  201. self.stuff[name].axes.set_xlim((xmin, xmax))
  202. self.stuff[name].axes.set_ylim((ymin, ymax))
  203. self.axes.set_xlim((xmin, xmax))
  204. self.axes.set_ylim((ymin, ymax))
  205. self.canvas.queue_draw()
  206. # def plot_gerber(self, gerber):
  207. # gerber.create_geometry()
  208. #
  209. # # Options
  210. # mergepolys = self.builder.get_object("cb_mergepolys").get_active()
  211. # multicolored = self.builder.get_object("cb_multicolored").get_active()
  212. #
  213. # geometry = None
  214. # if mergepolys:
  215. # geometry = gerber.solid_geometry
  216. # else:
  217. # geometry = gerber.buffered_paths + \
  218. # [poly['polygon'] for poly in gerber.regions] + \
  219. # gerber.flash_geometry
  220. #
  221. # linespec = None
  222. # if multicolored:
  223. # linespec = '-'
  224. # else:
  225. # linespec = 'k-'
  226. #
  227. # for poly in geometry:
  228. # x, y = poly.exterior.xy
  229. # #a.plot(x, y)
  230. # self.axes.plot(x, y, linespec)
  231. # for ints in poly.interiors:
  232. # x, y = ints.coords.xy
  233. # self.axes.plot(x, y, linespec)
  234. #
  235. # self.canvas.queue_draw()
  236. #
  237. # def plot_excellon(self, excellon):
  238. # excellon.create_geometry()
  239. #
  240. # # Plot excellon
  241. # for geo in excellon.solid_geometry:
  242. # x, y = geo.exterior.coords.xy
  243. # self.axes.plot(x, y, 'r-')
  244. # for ints in geo.interiors:
  245. # x, y = ints.coords.xy
  246. # self.axes.plot(x, y, 'g-')
  247. #
  248. # self.canvas.queue_draw()
  249. #
  250. # def plot_cncjob(self, job):
  251. # #job.gcode_parse()
  252. # job.plot2(self.axes)
  253. # self.canvas.queue_draw()
  254. #
  255. # def plot_geometry(self, geometry):
  256. # for geo in geometry.solid_geometry:
  257. #
  258. # if type(geo) == Polygon:
  259. # x, y = geo.exterior.coords.xy
  260. # self.axes.plot(x, y, 'r-')
  261. # for ints in geo.interiors:
  262. # x, y = ints.coords.xy
  263. # self.axes.plot(x, y, 'r-')
  264. # continue
  265. #
  266. # if type(geo) == LineString or type(geo) == LinearRing:
  267. # x, y = geo.coords.xy
  268. # self.axes.plot(x, y, 'r-')
  269. # continue
  270. #
  271. # self.canvas.queue_draw()
  272. def setup_component_viewer(self):
  273. """
  274. List or Tree where whatever has been loaded or created is
  275. displayed.
  276. """
  277. self.store = Gtk.ListStore(str)
  278. self.tree = Gtk.TreeView(self.store)
  279. #self.list = Gtk.ListBox()
  280. self.tree_select = self.tree.get_selection()
  281. self.signal_id = self.tree_select.connect("changed", self.on_tree_selection_changed)
  282. renderer = Gtk.CellRendererText()
  283. column = Gtk.TreeViewColumn("Title", renderer, text=0)
  284. self.tree.append_column(column)
  285. self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
  286. def setup_component_editor(self):
  287. box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
  288. label1 = Gtk.Label("Choose an item from Project")
  289. box1.pack_start(label1, False, False, 1)
  290. self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
  291. def build_list(self):
  292. self.store.clear()
  293. for key in self.stuff:
  294. self.store.append([key])
  295. def build_gerber_ui(self):
  296. print "build_gerber_ui()"
  297. osw = self.builder.get_object("offscrwindow_gerber")
  298. box1 = self.builder.get_object("box_gerber")
  299. osw.remove(box1)
  300. self.notebook.append_page(box1, Gtk.Label("Selection"))
  301. gerber = self.stuff[self.selected_item_name]
  302. entry_name = self.builder.get_object("entry_gerbername")
  303. entry_name.set_text(self.selected_item_name)
  304. entry_name.connect("activate", self.on_activate_name)
  305. if self.selected_item_name is not None:
  306. self.selected_object_to_form()
  307. box1.show()
  308. def build_excellon_ui(self):
  309. print "build_excellon_ui()"
  310. osw = self.builder.get_object("offscrwindow_excellon")
  311. box1 = self.builder.get_object("box_excellon")
  312. osw.remove(box1)
  313. self.notebook.append_page(box1, Gtk.Label("Selection"))
  314. entry_name = self.builder.get_object("entry_excellonname")
  315. entry_name.set_text(self.selected_item_name)
  316. entry_name.connect("activate", self.on_activate_name)
  317. if self.selected_item_name is not None:
  318. self.selected_object_to_form()
  319. box1.show()
  320. def build_cncjob_ui(self):
  321. print "build_cncjob_ui()"
  322. osw = self.builder.get_object("offscrwindow_cncjob")
  323. box1 = self.builder.get_object("box_cncjob")
  324. osw.remove(box1)
  325. self.notebook.append_page(box1, Gtk.Label("Selection"))
  326. entry_name = self.builder.get_object("entry_cncjobname")
  327. entry_name.set_text(self.selected_item_name)
  328. entry_name.connect("activate", self.on_activate_name)
  329. if self.selected_item_name is not None:
  330. self.selected_object_to_form()
  331. box1.show()
  332. def build_geometry_ui(self):
  333. print "build_geometry_ui()"
  334. osw = self.builder.get_object("offscrwindow_geometry")
  335. box1 = self.builder.get_object("box_geometry")
  336. osw.remove(box1)
  337. self.notebook.append_page(box1, Gtk.Label("Selection"))
  338. entry_name = self.builder.get_object("entry_geometryname")
  339. entry_name.set_text(self.selected_item_name)
  340. entry_name.connect("activate", self.on_activate_name)
  341. if self.selected_item_name is not None:
  342. self.selected_object_to_form()
  343. box1.show()
  344. def get_radio_value(self, radio_set):
  345. """
  346. Returns the radio_set[key] if the radiobutton
  347. whose name is key is active.
  348. """
  349. for name in radio_set:
  350. if self.builder.get_object(name).get_active():
  351. return radio_set[name]
  352. def selected_object_to_form(self):
  353. print "Object --> Form"
  354. obj = self.stuff[self.selected_item_name]
  355. if obj.__class__.__name__ == 'CirkuixGerber':
  356. setters = {
  357. "plot": self.builder.get_object("cb_gerber_plot").set_active,
  358. "mergepolys": self.builder.get_object("cb_gerber_mergepolys").set_active,
  359. "solid": self.builder.get_object("cb_gerber_solid").set_active,
  360. "multicolored": self.builder.get_object("cb_gerber_multicolored").set_active,
  361. "isotooldia": lambda x: self.builder.get_object("entry_gerberisotooldia").set_text(str(x)),
  362. "cutoutmargin": lambda x: self.builder.get_object("entry_gerber_cutout_margin").set_text(str(x)),
  363. "cutoutgapsize": lambda x: self.builder.get_object("entry_gerber_cutout_gapsize").set_text(str(x)),
  364. "gaps": lambda x: self.builder.get_object("cb_gerber_solid").set_active(
  365. {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}[x])
  366. }
  367. for option in obj.options:
  368. if option in setters:
  369. setters[option](obj.options[option])
  370. return
  371. if obj.__class__.__name__ == 'CirkuixExcellon':
  372. setters = {
  373. "plot": self.builder.get_object("cb_excellon_plot").set_active,
  374. "solid": self.builder.get_object("cb_excellon_solid").set_active,
  375. "multicolored": self.builder.get_object("cb_excellon_multicolored").set_active
  376. }
  377. for option in obj.options:
  378. if option in setters:
  379. setters[option](obj.options[option])
  380. return
  381. if obj.__class__.__name__ == 'CirkuixCNCjob':
  382. obj.set_options({
  383. "plot": self.builder.get_object("cb_cncjob_plot").set_active,
  384. "solid": self.builder.get_object("cb_cncjob_solid").set_active,
  385. "tooldia": lambda x: self.builder.get_object("entry_cncjob_tooldia").set_text(str(x))
  386. })
  387. def form_to_selected_object(self):
  388. obj = self.stuff[self.selected_item_name]
  389. if obj.__class__.__name__ == 'CirkuixGerber':
  390. obj.set_options({
  391. "plot": self.builder.get_object("cb_gerber_plot").get_active(),
  392. "mergepolys": self.builder.get_object("cb_gerber_mergepolys").get_active(),
  393. "solid": self.builder.get_object("cb_gerber_solid").get_active(),
  394. "multicolored": self.builder.get_object("cb_gerber_multicolored").get_active(),
  395. "isotooldia": self.get_eval("entry_gerberisotooldia"),
  396. "cutoutmargin": self.get_eval("entry_gerber_cutout_margin"),
  397. "cutoutgapsize": self.get_eval("entry_gerber_cutout_gapsize"),
  398. "gaps": lambda x: self.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"})
  399. })
  400. return
  401. if obj.__class__.__name__ == 'CirkuixExcellon':
  402. obj.set_options({
  403. "plot": self.builder.get_object("cb_excellon_plot").get_active(),
  404. "solid": self.builder.get_object("cb_excellon_solid").get_active(),
  405. "multicolored": self.builder.get_object("cb_excellon_multicolored").get_active()
  406. })
  407. return
  408. if obj.__class__.__name__ == 'CirkuixCNCjob':
  409. obj.set_options({
  410. "plot": self.builder.get_object("cb_cncjob_plot").get_active(),
  411. "solid": self.builder.get_object("cb_cncjob_solid").get_active(),
  412. "multicolored": self.builder.get_object("cb_cncjob_multicolored").get_active(),
  413. "tooldia": self.get_eval("entry_cncjob_tooldia")
  414. })
  415. if type(obj) is CirkuixGeometry:
  416. obj.set_options({
  417. "plot": self.builder.get_object("cb_geometry_plot").get_active(),
  418. "solid": self.builder.get_object("cb_geometry_solid").get_active(),
  419. "multicolored": self.builder.get_object("cb_geometry_multicolored").get_active(),
  420. "cutz": self.get_eval("entry_geometry_cutz"),
  421. "travelz": self.get_eval("entry_geometry_travelz"),
  422. "feedrate": self.get_eval("entry_geometry_feedrate")
  423. })
  424. def plot_all(self):
  425. self.clear_plots()
  426. #plotter = {"gerber": self.plot_gerber,
  427. # "excellon": self.plot_excellon,
  428. # "cncjob": self.plot_cncjob,
  429. # "geometry": self.plot_geometry}
  430. for i in self.stuff:
  431. #kind = self.stuff[i].kind
  432. #plotter[kind](self.stuff[i])
  433. self.stuff[i].plot(self.figure)
  434. self.on_zoom_fit(None)
  435. self.axes.grid()
  436. self.canvas.queue_draw()
  437. def clear_plots(self):
  438. self.axes.cla()
  439. self.figure.clf()
  440. self.figure.add_axes(self.axes)
  441. self.canvas.queue_draw()
  442. def get_eval(self, widget_name):
  443. value = self.builder.get_object(widget_name).get_text()
  444. return eval(value)
  445. def set_list_selection(self, name):
  446. iter = self.store.get_iter_first()
  447. while iter is not None and self.store[iter][0] != name:
  448. iter = self.store.iter_next(iter)
  449. self.tree_select.unselect_all()
  450. self.tree_select.select_iter(iter)
  451. ########################################
  452. ## EVENT HANDLERS ##
  453. ########################################
  454. def on_gerber_generate_boundary(self, widget):
  455. margin = self.get_eval("entry_gerber_cutout_margin")
  456. gap_size = self.get_eval("entry_gerber_cutout_gapsize")
  457. gerber = self.stuff[self.selected_item_name]
  458. minx, miny, maxx, maxy = gerber.bounds()
  459. minx -= margin
  460. maxx += margin
  461. miny -= margin
  462. maxy += margin
  463. midx = 0.5 * (minx + maxx)
  464. midy = 0.5 * (miny + maxy)
  465. hgap = 0.5 * gap_size
  466. pts = [[midx-hgap, maxy],
  467. [minx, maxy],
  468. [minx, midy+hgap],
  469. [minx, midy-hgap],
  470. [minx, miny],
  471. [midx-hgap, miny],
  472. [midx+hgap, miny],
  473. [maxx, miny],
  474. [maxx, midy-hgap],
  475. [maxx, midy+hgap],
  476. [maxx, maxy],
  477. [midx+hgap, maxy]]
  478. cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
  479. [pts[6], pts[7], pts[10], pts[11]]],
  480. "lr": [[pts[9], pts[10], pts[1], pts[2]],
  481. [pts[3], pts[4], pts[7], pts[8]]],
  482. "4": [[pts[0], pts[1], pts[2]],
  483. [pts[3], pts[4], pts[5]],
  484. [pts[6], pts[7], pts[8]],
  485. [pts[9], pts[10], pts[11]]]}
  486. name = self.selected_item_name + "_cutout"
  487. geometry = CirkuixGeometry(name)
  488. cuts = None
  489. if self.builder.get_object("rb_2tb").get_active():
  490. cuts = cases["tb"]
  491. elif self.builder.get_object("rb_2lr").get_active():
  492. cuts = cases["lr"]
  493. else:
  494. cuts = cases["4"]
  495. geometry.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
  496. # Add to App and update.
  497. self.stuff[name] = geometry
  498. self.build_list()
  499. def on_eval_update(self, widget):
  500. """
  501. Modifies the content of a Gtk.Entry by running
  502. eval() on its contents and puting it back as a
  503. string.
  504. """
  505. # TODO: error handling here
  506. widget.set_text(str(eval(widget.get_text())))
  507. def on_generate_isolation(self, widget):
  508. print "Generating Isolation Geometry:"
  509. # Get required info
  510. tooldia = self.builder.get_object("entry_gerberisotooldia").get_text()
  511. tooldia = eval(tooldia)
  512. print "tooldia:", tooldia
  513. # Generate
  514. iso = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
  515. # TODO: This will break if there is something with this name already
  516. iso_name = self.selected_item_name + "_iso"
  517. geo = CirkuixGeometry(iso_name)
  518. geo.solid_geometry = iso
  519. # Add to App and update.
  520. self.stuff[iso_name] = geo
  521. self.build_list()
  522. def on_generate_cncjob(self, widget):
  523. print "Generating CNC job"
  524. # Get required info
  525. cutz = self.get_eval("entry_geometry_cutz")
  526. travelz = self.get_eval("entry_geometry_travelz")
  527. feedrate = self.get_eval("entry_geometry_feedrate")
  528. geometry = self.stuff[self.selected_item_name]
  529. job_name = self.selected_item_name + "_cnc"
  530. job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
  531. job.generate_from_geometry(geometry.solid_geometry)
  532. job.gcode_parse()
  533. job.create_geometry()
  534. # Add to App and update.
  535. self.stuff[job_name] = job
  536. self.build_list()
  537. def on_cncjob_tooldia_activate(self, widget):
  538. job = self.stuff[self.selected_item_name]
  539. tooldia = self.get_eval("entry_cncjob_tooldia")
  540. job.tooldia = tooldia
  541. print "Changing tool diameter to:", tooldia
  542. def on_cncjob_exportgcode(self, widget):
  543. def on_success(self, filename):
  544. cncjob = self.stuff[self.selected_item_name]
  545. f = open(filename, 'w')
  546. f.write(cncjob.gcode)
  547. f.close()
  548. print "Saved to:", filename
  549. self.file_chooser_save_action(on_success)
  550. def on_delete(self, widget):
  551. self.stuff.pop(self.selected_item_name)
  552. #self.tree.get_selection().disconnect(self.signal_id)
  553. self.build_list() # Update the items list
  554. #self.signal_id = self.tree.get_selection().connect(
  555. # "changed", self.on_tree_selection_changed)
  556. self.plot_all()
  557. #self.notebook.set_current_page(1)
  558. def on_replot(self, widget):
  559. self.plot_all()
  560. def on_clear_plots(self, widget):
  561. self.clear_plots()
  562. def on_activate_name(self, entry):
  563. '''
  564. Hitting 'Enter' after changing the name of an item
  565. updates the item dictionary and re-builds the item list.
  566. '''
  567. print "Changing name"
  568. self.tree.get_selection().disconnect(self.signal_id)
  569. new_name = entry.get_text() # Get from form
  570. self.stuff[new_name] = self.stuff.pop(self.selected_item_name) # Update dictionary
  571. self.selected_item_name = new_name # Update selection name
  572. self.build_list() # Update the items list
  573. self.signal_id = self.tree.get_selection().connect(
  574. "changed", self.on_tree_selection_changed)
  575. def on_tree_selection_changed(self, selection):
  576. model, treeiter = selection.get_selected()
  577. if treeiter is not None:
  578. print "You selected", model[treeiter][0]
  579. else:
  580. return # TODO: Clear "Selected" page
  581. self.selected_item_name = model[treeiter][0]
  582. # Remove the current selection page
  583. # from the notebook
  584. # TODO: Assuming it was last page or #2. Find the right page
  585. self.builder.get_object("notebook1").remove_page(2)
  586. # Determine the kind of item selected
  587. kind = self.stuff[model[treeiter][0]].kind
  588. # Build the UI
  589. builder = {"gerber": self.build_gerber_ui,
  590. "excellon": self.build_excellon_ui,
  591. "cncjob": self.build_cncjob_ui,
  592. "geometry": self.build_geometry_ui}
  593. builder[kind]()
  594. def on_filequit(self, param):
  595. print "quit from menu"
  596. self.window.destroy()
  597. Gtk.main_quit()
  598. def on_closewindow(self, param):
  599. print "quit from X"
  600. self.window.destroy()
  601. Gtk.main_quit()
  602. def file_chooser_action(self, on_success):
  603. '''
  604. Opens the file chooser and runs on_success on a separate thread
  605. upon completion of valid file choice.
  606. '''
  607. dialog = Gtk.FileChooserDialog("Please choose a file", self.window,
  608. Gtk.FileChooserAction.OPEN,
  609. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  610. Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
  611. response = dialog.run()
  612. if response == Gtk.ResponseType.OK:
  613. filename = dialog.get_filename()
  614. dialog.destroy()
  615. t = threading.Thread(target=on_success, args=(self, filename))
  616. t.daemon = True
  617. t.start()
  618. #on_success(self, filename)
  619. elif response == Gtk.ResponseType.CANCEL:
  620. print("Cancel clicked")
  621. dialog.destroy()
  622. def file_chooser_save_action(self, on_success):
  623. """
  624. Opens the file chooser and runs on_success
  625. upon completion of valid file choice.
  626. """
  627. dialog = Gtk.FileChooserDialog("Save file", self.window,
  628. Gtk.FileChooserAction.SAVE,
  629. (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
  630. Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
  631. dialog.set_current_name("Untitled")
  632. response = dialog.run()
  633. if response == Gtk.ResponseType.OK:
  634. filename = dialog.get_filename()
  635. dialog.destroy()
  636. on_success(self, filename)
  637. elif response == Gtk.ResponseType.CANCEL:
  638. print("Cancel clicked")
  639. dialog.destroy()
  640. def on_fileopengerber(self, param):
  641. def on_success(self, filename):
  642. self.progress_bar.set_text("Opening Gerber ...")
  643. self.progress_bar.set_fraction(0.1)
  644. name = filename.split('/')[-1].split('\\')[-1]
  645. gerber = CirkuixGerber(name)
  646. self.progress_bar.set_text("Parsing ...")
  647. self.progress_bar.set_fraction(0.2)
  648. gerber.parse_file(filename)
  649. self.store.append([name])
  650. self.stuff[name] = gerber
  651. self.progress_bar.set_text("Plotting ...")
  652. self.progress_bar.set_fraction(0.6)
  653. #self.plot_gerber(gerber)
  654. gerber.plot(self.figure)
  655. gerber.axes.set_alpha(0.0)
  656. self.on_zoom_fit(None)
  657. self.progress_bar.set_text("Done!")
  658. self.progress_bar.set_fraction(1.0)
  659. self.notebook.set_current_page(1)
  660. self.set_list_selection(name)
  661. def clear_bar(bar):
  662. bar.set_text("")
  663. bar.set_fraction(0.0)
  664. threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
  665. self.file_chooser_action(on_success)
  666. def on_fileopenexcellon(self, param):
  667. def on_success(self, filename):
  668. self.progress_bar.set_text("Opening Excellon ...")
  669. self.progress_bar.set_fraction(0.1)
  670. name = filename.split('/')[-1].split('\\')[-1]
  671. excellon = CirkuixExcellon(name)
  672. self.progress_bar.set_text("Parsing ...")
  673. self.progress_bar.set_fraction(0.2)
  674. excellon.parse_file(filename)
  675. self.store.append([name])
  676. self.stuff[name] = excellon
  677. self.progress_bar.set_text("Plotting ...")
  678. self.progress_bar.set_fraction(0.6)
  679. #self.plot_excellon(excellon)
  680. excellon.plot(self.figure)
  681. self.on_zoom_fit(None)
  682. self.progress_bar.set_text("Done!")
  683. self.progress_bar.set_fraction(1.0)
  684. def clear_bar(bar):
  685. bar.set_text("")
  686. bar.set_fraction(0.0)
  687. threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
  688. self.file_chooser_action(on_success)
  689. def on_fileopengcode(self, param):
  690. def on_success(self, filename):
  691. self.progress_bar.set_text("Opening G-Code ...")
  692. self.progress_bar.set_fraction(0.1)
  693. name = filename.split('/')[-1].split('\\')[-1]
  694. f = open(filename)
  695. gcode = f.read()
  696. f.close()
  697. tooldia = self.get_eval("entry_tooldia")
  698. job = CirkuixCNCjob(name, tooldia=tooldia)
  699. job.gcode = gcode
  700. self.progress_bar.set_text("Parsing ...")
  701. self.progress_bar.set_fraction(0.2)
  702. job.gcode_parse()
  703. job.create_geometry()
  704. self.store.append([name])
  705. self.stuff[name] = job
  706. self.progress_bar.set_text("Plotting ...")
  707. self.progress_bar.set_fraction(0.6)
  708. #self.plot_cncjob(job)
  709. job.plot(self.figure)
  710. self.on_zoom_fit(None)
  711. self.progress_bar.set_text("Done!")
  712. self.progress_bar.set_fraction(1.0)
  713. def clear_bar(bar):
  714. bar.set_text("")
  715. bar.set_fraction(0.0)
  716. threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
  717. self.file_chooser_action(on_success)
  718. def on_mouse_move_over_plot(self, event):
  719. try: # May fail in case mouse not within axes
  720. self.position_label.set_label("X: %.4f Y: %.4f"%(
  721. event.xdata, event.ydata))
  722. self.mouse = [event.xdata, event.ydata]
  723. except:
  724. self.position_label.set_label("")
  725. self.mouse = None
  726. def on_click_over_plot(self, event):
  727. # For key presses
  728. self.canvas.grab_focus()
  729. print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
  730. event.button, event.x, event.y, event.xdata, event.ydata)
  731. def on_zoom_in(self, event):
  732. self.zoom(1.5)
  733. return
  734. def on_zoom_out(self, event):
  735. self.zoom(1/1.5)
  736. def on_zoom_fit(self, event):
  737. xmin, ymin, xmax, ymax = get_bounds(self.stuff)
  738. width = xmax-xmin
  739. height = ymax-ymin
  740. r = width/height
  741. Fw, Fh = self.canvas.get_width_height()
  742. Fr = float(Fw)/Fh
  743. print "Window aspect ratio:", Fr
  744. print "Data aspect ratio:", r
  745. #self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
  746. #self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
  747. if r > Fr:
  748. #self.axes.set_xlim((xmin-0.05*width, xmax+0.05*width))
  749. xmin -= 0.05*width
  750. xmax += 0.05*width
  751. ycenter = (ymin+ymax)/2.0
  752. newheight = height*r/Fr
  753. ymin = ycenter-newheight/2.0
  754. ymax = ycenter+newheight/2.0
  755. #self.axes.set_ylim((ycenter-newheight/2.0, ycenter+newheight/2.0))
  756. else:
  757. #self.axes.set_ylim((ymin-0.05*height, ymax+0.05*height))
  758. ymin -= 0.05*height
  759. ymax += 0.05*height
  760. xcenter = (xmax+ymin)/2.0
  761. newwidth = width*Fr/r
  762. xmin = xcenter-newwidth/2.0
  763. xmax = xcenter+newwidth/2.0
  764. #self.axes.set_xlim((xcenter-newwidth/2.0, xcenter+newwidth/2.0))
  765. for name in self.stuff:
  766. self.stuff[name].axes.set_xlim((xmin, xmax))
  767. self.stuff[name].axes.set_ylim((ymin, ymax))
  768. self.axes.set_xlim((xmin, xmax))
  769. self.axes.set_ylim((ymin, ymax))
  770. self.canvas.queue_draw()
  771. return
  772. # def on_scroll_over_plot(self, event):
  773. # print "Scroll"
  774. # center = [event.xdata, event.ydata]
  775. # if sign(event.step):
  776. # self.zoom(1.5, center=center)
  777. # else:
  778. # self.zoom(1/1.5, center=center)
  779. #
  780. # def on_window_scroll(self, event):
  781. # print "Scroll"
  782. def on_key_over_plot(self, event):
  783. print 'you pressed', event.key, event.xdata, event.ydata
  784. if event.key == '1': # 1
  785. self.on_zoom_fit(None)
  786. return
  787. if event.key == '2': # 2
  788. self.zoom(1/1.5, self.mouse)
  789. return
  790. if event.key == '3': # 3
  791. self.zoom(1.5, self.mouse)
  792. return
  793. app = App()