Centralized object creation. Cleaner notebook handling. Centralized form generation. Some threading improvement. Comments.

This commit is contained in:
Juan Pablo Caram 2014-01-15 22:51:06 -05:00
parent f888775b36
commit c3260802df
2 changed files with 344 additions and 301 deletions

View File

@ -1,8 +1,7 @@
import threading
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Gtk, Gdk, GLib, GObject
from matplotlib.figure import Figure
from numpy import arange, sin, pi
@ -14,6 +13,12 @@ from camlib import *
class CirkuixObj:
"""
Base type of objects handled in Cirkuix. These become interactive
in the GUI, can be plotted, and their options can be modified
by the user in their respective forms.
"""
form_getters = {}
form_setters = {}
@ -55,8 +60,42 @@ class CirkuixObj:
for name in self.form_getters:
self.options[name] = self.form_getters[name]()
def build_ui(self, kind):
"""
Sets up the UI/form for this object.
@param kind: Kind of object, i.e. 'gerber'
@type kind: str
@return: None
"""
# Where to the UI for this object
box_selected = self.app.builder.get_object("box_selected")
# Remove anything else in the box
box_children = box_selected.get_children()
for child in box_children:
box_selected.remove(child)
osw = self.app.builder.get_object("offscrwindow_" + kind) # offscreenwindow
sw = self.app.builder.get_object("sw_" + kind) # scrollwindows
osw.remove(sw) # TODO: Is this needed ?
vp = self.app.builder.get_object("vp_" + kind) # Viewport
vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
# Put in the UI
box_selected.pack_start(sw, True, True, 0)
entry_name = self.app.builder.get_object("entry_" + kind + "name")
entry_name.set_text(self.name)
entry_name.connect("activate", self.app.on_activate_name)
self.to_form()
sw.show()
class CirkuixGerber(CirkuixObj, Gerber):
"""
Represents Gerber code.
"""
def __init__(self, name):
Gerber.__init__(self)
@ -75,20 +114,10 @@ class CirkuixGerber(CirkuixObj, Gerber):
}
def build_ui(self):
print "cirkuixgerber.build_ui()"
osw = self.app.builder.get_object("offscrwindow_gerber")
#box1 = self.app.builder.get_object("box_gerber")
#osw.remove(box1)
sw = self.app.builder.get_object("sw_gerber")
osw.remove(sw)
vp = self.app.builder.get_object("vp_gerber")
vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
self.app.notebook.append_page(sw, Gtk.Label("Selection"))
entry_name = self.app.builder.get_object("entry_gerbername")
entry_name.set_text(self.name)
entry_name.connect("activate", self.app.on_activate_name)
self.to_form()
sw.show()
"""
@return: None
"""
CirkuixObj.build_ui(self, "gerber")
def plot(self, figure):
self.setup_axes(figure)
@ -118,6 +147,9 @@ class CirkuixGerber(CirkuixObj, Gerber):
class CirkuixExcellon(CirkuixObj, Excellon):
"""
Represents Excellon code.
"""
def __init__(self, name):
Excellon.__init__(self)
@ -130,20 +162,7 @@ class CirkuixExcellon(CirkuixObj, Excellon):
}
def build_ui(self):
print "build_excellon_ui()"
osw = self.app.builder.get_object("offscrwindow_excellon")
#box1 = self.app.builder.get_object("box_excellon")
#osw.remove(box1)
sw = self.app.builder.get_object("sw_excellon")
osw.remove(sw)
vp = self.app.builder.get_object("vp_excellon")
vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
self.app.notebook.append_page(sw, Gtk.Label("Selection"))
entry_name = self.app.builder.get_object("entry_excellonname")
entry_name.set_text(self.name)
entry_name.connect("activate", self.app.on_activate_name)
self.to_form()
sw.show()
CirkuixObj.build_ui(self, "excellon")
def plot(self, figure):
self.setup_axes(figure)
@ -159,6 +178,9 @@ class CirkuixExcellon(CirkuixObj, Excellon):
class CirkuixCNCjob(CirkuixObj, CNCjob):
"""
Represents G-Code.
"""
def __init__(self, name, units="in", kind="generic", z_move=0.1,
feedrate=3.0, z_cut=-0.002, tooldia=0.0):
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
@ -172,20 +194,7 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
}
def build_ui(self):
print "build_cncjob_ui()"
osw = self.app.builder.get_object("offscrwindow_cncjob")
#box1 = self.app.builder.get_object("box_cncjob")
#osw.remove(box1)
sw = self.app.builder.get_object("sw_cncjob")
osw.remove(sw)
vp = self.app.builder.get_object("vp_cncjob")
vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
self.app.notebook.append_page(sw, Gtk.Label("Selection"))
entry_name = self.app.builder.get_object("entry_cncjobname")
entry_name.set_text(self.name)
entry_name.connect("activate", self.app.on_activate_name)
self.to_form()
sw.show()
CirkuixObj.build_ui(self, "cncjob")
def plot(self, figure):
self.setup_axes(figure)
@ -193,6 +202,11 @@ class CirkuixCNCjob(CirkuixObj, CNCjob):
class CirkuixGeometry(CirkuixObj, Geometry):
"""
Geometric object not associated with a specific
format.
"""
def __init__(self, name):
CirkuixObj.__init__(self, name)
self.options = {"plot": True,
@ -209,20 +223,7 @@ class CirkuixGeometry(CirkuixObj, Geometry):
}
def build_ui(self):
print "build_geometry_ui()"
osw = self.app.builder.get_object("offscrwindow_geometry")
#box1 = self.app.builder.get_object("box_geometry")
#osw.remove(box1)
sw = self.app.builder.get_object("sw_geometry")
osw.remove(sw)
vp = self.app.builder.get_object("vp_geometry")
vp.override_background_color(Gtk.StateType.NORMAL, Gdk.RGBA(1, 1, 1, 1))
self.app.notebook.append_page(sw, Gtk.Label("Selection"))
entry_name = self.app.builder.get_object("entry_geometryname")
entry_name.set_text(self.name)
entry_name.connect("activate", self.app.on_activate_name)
self.to_form()
sw.show()
CirkuixObj.build_ui(self, "geometry")
def plot(self, figure):
self.setup_axes(figure)
@ -244,9 +245,20 @@ class CirkuixGeometry(CirkuixObj, Geometry):
class App:
"""
The main application class. The constructor starts the GUI.
"""
def __init__(self):
"""
Starts the application and the Gtk.main().
@return: app
"""
# Needed to interact with the GUI from other threads.
GLib.threads_init()
GObject.threads_init()
#Gdk.threads_init()
########################################
## GUI ##
@ -296,9 +308,17 @@ class App:
## START ##
########################################
self.window.show_all()
Gtk.main()
#Gtk.main()
def setup_plot(self):
"""
Sets up the main plotting area by creating a matplotlib
figure in self.canvas, adding axes and configuring them.
These axes should not be ploted on and are just there to
display the axes ticks and grid.
@return: None
"""
self.figure = Figure(dpi=50)
self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
self.axes.set_aspect(1)
@ -398,7 +418,9 @@ class App:
"""
List or Tree where whatever has been loaded or created is
displayed.
@return: None
"""
self.store = Gtk.ListStore(str)
self.tree = Gtk.TreeView(self.store)
#self.list = Gtk.ListBox()
@ -407,24 +429,42 @@ class App:
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0)
self.tree.append_column(column)
self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
#self.builder.get_object("notebook1").append_page(self.tree, Gtk.Label("Project"))
self.builder.get_object("box_project").pack_start(self.tree, False, False, 1)
def setup_component_editor(self):
"""
Initial configuration of the component editor. Creates
a page titled "Selection" on the notebook on the left
side of the main window.
@return: None
"""
box_selected = self.builder.get_object("box_selected")
# Remove anything else in the box
box_children = box_selected.get_children()
for child in box_children:
box_selected.remove(child)
box1 = Gtk.Box(Gtk.Orientation.VERTICAL)
label1 = Gtk.Label("Choose an item from Project")
box1.pack_start(label1, False, False, 1)
self.builder.get_object("notebook1").append_page(box1, Gtk.Label("Selection"))
box_selected.pack_start(box1, True, True, 0)
def info(self, text):
"""
Show text on the status bar.
@return: None
"""
self.info_label.set_text(text)
def zoom(self, factor, center=None):
"""
Zooms the plot by factor around a given
center point. Takes care of re-drawing.
@return: None
"""
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
@ -455,6 +495,11 @@ class App:
self.canvas.queue_draw()
def build_list(self):
"""
Clears and re-populates the list of objects in tcurrently
in the project.
@return: None
"""
self.store.clear()
for key in self.stuff:
self.store.append([key])
@ -463,12 +508,19 @@ class App:
"""
Returns the radio_set[key] if the radiobutton
whose name is key is active.
@return: radio_set[key]
"""
for name in radio_set:
if self.builder.get_object(name).get_active():
return radio_set[name]
def plot_all(self):
"""
Re-generates all plots from all objects.
@return: None
"""
self.clear_plots()
for i in self.stuff:
@ -479,88 +531,148 @@ class App:
self.canvas.queue_draw()
def clear_plots(self):
"""
Clears self.axes and self.figure.
@return: None
"""
self.axes.cla()
self.figure.clf()
self.figure.add_axes(self.axes)
self.canvas.queue_draw()
def get_eval(self, widget_name):
"""
Runs eval() on the on the text entry of name 'widget_name'
and returns the results.
@param widget_name: Name of Gtk.Entry
@return: Depends on contents of the entry text.
"""
value = self.builder.get_object(widget_name).get_text()
return eval(value)
def set_list_selection(self, name):
"""
Marks a given object as selected in the list ob objects
in the GUI. This selection will in turn trigger
self.on_tree_selection_changed().
@param name: Name of the object.
@return: None
"""
iter = self.store.get_iter_first()
while iter is not None and self.store[iter][0] != name:
iter = self.store.iter_next(iter)
self.tree_select.unselect_all()
self.tree_select.select_iter(iter)
def new_object(self, kind, name, initialize):
"""
Creates a new specalized CirkuixObj and attaches it to the application,
this is, updates the GUI accordingly, any other records and plots it.
@param kind: Knd of object to create.
@param name: Name for the object.
@param initilize: Function to run after the
object has been created but before attacing it
to the application. Takes the new object and the
app as parameters.
@return: The object requested
@rtype : CirkuixObj extended
"""
# Check for existing name
if name in self.stuff:
return None
# Create object
classdict = {
"gerber": CirkuixGerber,
"excellon": CirkuixExcellon,
"cncjob": CirkuixCNCjob,
"geometry": CirkuixGeometry
}
obj = classdict[kind](name)
# Initialize as per user request
initialize(obj, self)
# Add to our records
self.stuff[name] = obj
# Update GUI list and select it
self.store.append([name])
#self.build_list()
self.set_list_selection(name)
GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
# Plot
obj.plot(self.figure)
obj.axes.set_alpha(0.0)
self.on_zoom_fit(None)
return obj
########################################
## EVENT HANDLERS ##
########################################
def on_gerber_generate_noncopper(self, widget):
gerber = self.stuff[self.selected_item_name]
gerber.read_form()
name = self.selected_item_name + "_noncopper"
bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
non_copper = bounding_box.difference(gerber.solid_geometry)
geometry = CirkuixGeometry(name)
geometry.solid_geometry = non_copper
self.stuff[name] = geometry
self.build_list()
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, CirkuixGeometry)
gerber = app_obj.stuff[self.selected_item_name]
assert isinstance(gerber, CirkuixGerber)
gerber.read_form()
bounding_box = gerber.solid_geometry.envelope.buffer(gerber.options["noncoppermargin"])
non_copper = bounding_box.difference(gerber.solid_geometry)
geo_obj.solid_geometry = non_copper
# TODO: Check for None
self.new_object("geometry", name, geo_init)
def on_gerber_generate_cutout(self, widget):
margin = self.get_eval("entry_gerber_cutout_margin")
gap_size = self.get_eval("entry_gerber_cutout_gapsize")
gerber = self.stuff[self.selected_item_name]
minx, miny, maxx, maxy = gerber.bounds()
minx -= margin
maxx += margin
miny -= margin
maxy += margin
midx = 0.5 * (minx + maxx)
midy = 0.5 * (miny + maxy)
hgap = 0.5 * gap_size
pts = [[midx-hgap, maxy],
[minx, maxy],
[minx, midy+hgap],
[minx, midy-hgap],
[minx, miny],
[midx-hgap, miny],
[midx+hgap, miny],
[maxx, miny],
[maxx, midy-hgap],
[maxx, midy+hgap],
[maxx, maxy],
[midx+hgap, maxy]]
cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
[pts[6], pts[7], pts[10], pts[11]]],
"lr": [[pts[9], pts[10], pts[1], pts[2]],
[pts[3], pts[4], pts[7], pts[8]]],
"4": [[pts[0], pts[1], pts[2]],
[pts[3], pts[4], pts[5]],
[pts[6], pts[7], pts[8]],
[pts[9], pts[10], pts[11]]]}
name = self.selected_item_name + "_cutout"
geometry = CirkuixGeometry(name)
cuts = None
if self.builder.get_object("rb_2tb").get_active():
cuts = cases["tb"]
elif self.builder.get_object("rb_2lr").get_active():
cuts = cases["lr"]
else:
cuts = cases["4"]
geometry.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
# Add to App and update.
self.stuff[name] = geometry
self.build_list()
def geo_init(geo_obj, app_obj):
# TODO: get from object
margin = app_obj.get_eval("entry_gerber_cutout_margin")
gap_size = app_obj.get_eval("entry_gerber_cutout_gapsize")
gerber = app_obj.stuff[app_obj.selected_item_name]
minx, miny, maxx, maxy = gerber.bounds()
minx -= margin
maxx += margin
miny -= margin
maxy += margin
midx = 0.5 * (minx + maxx)
midy = 0.5 * (miny + maxy)
hgap = 0.5 * gap_size
pts = [[midx-hgap, maxy],
[minx, maxy],
[minx, midy+hgap],
[minx, midy-hgap],
[minx, miny],
[midx-hgap, miny],
[midx+hgap, miny],
[maxx, miny],
[maxx, midy-hgap],
[maxx, midy+hgap],
[maxx, maxy],
[midx+hgap, maxy]]
cases = {"tb": [[pts[0], pts[1], pts[4], pts[5]],
[pts[6], pts[7], pts[10], pts[11]]],
"lr": [[pts[9], pts[10], pts[1], pts[2]],
[pts[3], pts[4], pts[7], pts[8]]],
"4": [[pts[0], pts[1], pts[2]],
[pts[3], pts[4], pts[5]],
[pts[6], pts[7], pts[8]],
[pts[9], pts[10], pts[11]]]}
cuts = cases[app.get_radio_value({"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"})]
geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
# TODO: Check for None
self.new_object("geometry", name, geo_init)
def on_eval_update(self, widget):
"""
@ -573,39 +685,40 @@ class App:
def on_generate_isolation(self, widget):
print "Generating Isolation Geometry:"
# Get required info
tooldia = self.builder.get_object("entry_gerberisotooldia").get_text()
tooldia = eval(tooldia)
print "tooldia:", tooldia
# Generate
iso = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
# TODO: This will break if there is something with this name already
iso_name = self.selected_item_name + "_iso"
geo = CirkuixGeometry(iso_name)
geo.solid_geometry = iso
# Add to App and update.
self.stuff[iso_name] = geo
self.build_list()
def iso_init(geo_obj, app_obj):
# TODO: Object must be updated on form change and the options
# TODO: read from the object.
tooldia = app_obj.get_eval("entry_gerberisotooldia")
geo_obj.solid_geometry = self.stuff[self.selected_item_name].isolation_geometry(tooldia/2.0)
# TODO: Do something if this is None. Offer changing name?
self.new_object("geometry", iso_name, iso_init)
def on_generate_cncjob(self, widget):
print "Generating CNC job"
# Get required info
cutz = self.get_eval("entry_geometry_cutz")
travelz = self.get_eval("entry_geometry_travelz")
feedrate = self.get_eval("entry_geometry_feedrate")
geometry = self.stuff[self.selected_item_name]
job_name = self.selected_item_name + "_cnc"
job = CirkuixCNCjob(job_name, z_move=travelz, z_cut=cutz, feedrate=feedrate)
job.generate_from_geometry(geometry.solid_geometry)
job.gcode_parse()
job.create_geometry()
# Add to App and update.
self.stuff[job_name] = job
self.build_list()
def job_init(job_obj, app_obj):
# TODO: Object must be updated on form change and the options
# TODO: read from the object.
z_cut = app_obj.get_eval("entry_geometry_cutz")
z_move = app_obj.get_eval("entry_geometry_travelz")
feedrate = app_obj.get_eval("entry_geometry_feedrate")
geometry = app_obj.stuff[app_obj.selected_item_name]
assert isinstance(job_obj, CirkuixCNCjob)
job_obj.z_cut = z_cut
job_obj.z_move = z_move
job_obj.feedrate = feedrate
job_obj.generate_from_geometry(geometry.solid_geometry)
job_obj.gcode_parse()
job_obj.create_geometry()
self.new_object("cncjob", job_name, job_init)
def on_cncjob_tooldia_activate(self, widget):
job = self.stuff[self.selected_item_name]
@ -626,7 +739,7 @@ class App:
self.stuff.pop(self.selected_item_name)
#self.tree.get_selection().disconnect(self.signal_id)
self.build_list() # Update the items list
self.build_list() # Update the items list
#self.signal_id = self.tree.get_selection().connect(
# "changed", self.on_tree_selection_changed)
@ -658,27 +771,17 @@ class App:
if treeiter is not None:
print "You selected", model[treeiter][0]
self.selected_item_name = model[treeiter][0]
#self.stuff[self.selected_item_name].build_ui()
GLib.timeout_add(100, lambda: self.stuff[self.selected_item_name].build_ui())
else:
return # TODO: Clear "Selected" page
self.selected_item_name = model[treeiter][0]
# Remove the current selection page
# from the notebook
# TODO: Assuming it was last page or #2. Find the right page
self.builder.get_object("notebook1").remove_page(2)
print "Nothing selected"
self.selected_item_name = None
self.setup_component_editor()
self.stuff[self.selected_item_name].build_ui()
def on_file_new(self, param):
print "File->New not implemented yet."
# Determine the kind of item selected
#kind = self.stuff[model[treeiter][0]].kind
# Build the UI
# builder = {"gerber": self.build_gerber_ui,
# "excellon": self.build_excellon_ui,
# "cncjob": self.build_cncjob_ui,
# "geometry": self.build_geometry_ui}
# builder[kind]()
def on_filequit(self, param):
print "quit from menu"
self.window.destroy()
@ -755,14 +858,18 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
self.notebook.set_current_page(1)
#self.notebook.set_current_page(0)
self.set_list_selection(name)
#self.notebook.set_current_page(1)
GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
return False
threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
#threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
def on_fileopenexcellon(self, param):
@ -790,10 +897,17 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
#self.notebook.set_current_page(0)
self.set_list_selection(name)
#self.notebook.set_current_page(1)
GLib.timeout_add(100, lambda: self.notebook.set_current_page(1))
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
return False
#threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
@ -828,10 +942,16 @@ class App:
self.progress_bar.set_text("Done!")
self.progress_bar.set_fraction(1.0)
#self.notebook.set_current_page(0)
self.set_list_selection(name)
#self.notebook.set_current_page(1)
def clear_bar(bar):
bar.set_text("")
bar.set_fraction(0.0)
threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
return False
#threading.Timer(1, clear_bar, args=(self.progress_bar,)).start()
GLib.timeout_add_seconds(1, clear_bar, self.progress_bar)
self.file_chooser_action(on_success)
def on_mouse_move_over_plot(self, event):
@ -846,9 +966,12 @@ class App:
def on_click_over_plot(self, event):
# For key presses
self.canvas.grab_focus()
print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
event.button, event.x, event.y, event.xdata, event.ydata)
try:
print 'button=%d, x=%d, y=%d, xdata=%f, ydata=%f'%(
event.button, event.x, event.y, event.xdata, event.ydata)
except:
print "Outside plot!"
def on_zoom_in(self, event):
self.zoom(1.5)
@ -926,3 +1049,4 @@ class App:
return
app = App()
Gtk.main()

View File

@ -1203,6 +1203,7 @@
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_file_new" swapped="no"/>
</object>
</child>
<child>
@ -1448,6 +1449,7 @@
<property name="can_focus">True</property>
<child>
<object class="GtkNotebook" id="notebook1">
<property name="width_request">250</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">3</property>
@ -1456,135 +1458,12 @@
<property name="margin_bottom">3</property>
<property name="scrollable">True</property>
<child>
<object class="GtkBox" id="box3">
<object class="GtkBox" id="box_project">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">3</property>
<property name="margin_right">3</property>
<property name="margin_top">3</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">4</property>
<property name="label" translatable="yes">GERBER</property>
<property name="use_underline">True</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="cb_mergepolys">
<property name="label" translatable="yes">Merge Polygons</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton1">
<property name="label" translatable="yes">Solid</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="cb_multicolored">
<property name="label" translatable="yes">Multi-colored</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="xalign">0</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">4</property>
<property name="label" translatable="yes">G-CODE</property>
<attributes>
<attribute name="weight" value="semibold"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="box4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Tool dia: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_tooldia">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="text" translatable="yes">0.0</property>
<signal name="activate" handler="on_eval_update" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
@ -1594,23 +1473,63 @@
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Defaults</property>
<property name="label" translatable="yes">Project</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkBox" id="box_selected">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<placeholder/>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Selected</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
<object class="GtkBox" id="box_options">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child type="tab">
<placeholder/>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Options</property>
</object>
<packing>
<property name="position">2</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>