cirkuix.py 33 KB

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