Major modifications to data/gui interactions. In progress.

This commit is contained in:
Juan Pablo Caram 2014-04-25 01:24:03 -04:00
parent a20a7e1d8c
commit 0bdc3b19f0
12 changed files with 4260 additions and 7136 deletions

2965
FlatCAM.py

File diff suppressed because it is too large Load Diff

4011
FlatCAM.ui

File diff suppressed because it is too large Load Diff

3025
FlatCAMApp.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,32 @@ from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GObject
import inspect # TODO: Remove
from FlatCAMApp import *
from camlib import *
from ObjectUI import *
class LoudDict(dict):
def __init__(self, *args, **kwargs):
super(LoudDict, self).__init__(*args, **kwargs)
self.callback = lambda x: None
self.silence = False
def set_change_callback(self, callback):
if self.silence:
return
self.callback = callback
def __setitem__(self, key, value):
super(LoudDict, self).__setitem__(key, value)
try:
if self.__getitem__(key) == value:
return
except KeyError:
pass
self.callback(key)
########################################
@ -28,19 +53,63 @@ class FlatCAMObj(GObject.GObject, object):
# The app should set this value.
app = None
def __init__(self, name):
# name = GObject.property(type=str)
def __init__(self, name, ui):
"""
:param name: Name of the object given by the user.
:param ui: User interface to interact with the object.
:type ui: ObjectUI
:return: FlatCAMObj
"""
GObject.GObject.__init__(self)
self.options = {"name": name}
self.form_kinds = {"name": "entry_text"} # Kind of form element for each option
# View
self.ui = ui
self.options = LoudDict(name=name)
self.options.set_change_callback(self.on_options_change)
self.form_fields = {"name": self.ui.name_entry}
self.radios = {} # Name value pairs for radio sets
self.radios_inv = {} # Inverse of self.radios
self.axes = None # Matplotlib axes
self.kind = None # Override with proper name
self.muted_ui = False
self.ui.name_entry.connect('activate', self.on_name_activate)
self.ui.offset_button.connect('clicked', self.on_offset_button_click)
self.ui.offset_button.connect('activate', self.on_offset_button_click)
self.ui.scale_button.connect('clicked', self.on_scale_button_click)
self.ui.scale_button.connect('activate', self.on_scale_button_click)
def __str__(self):
return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
def on_name_activate(self, *args):
old_name = copy(self.options["name"])
new_name = self.ui.name_entry.get_text()
self.options["name"] = self.ui.name_entry.get_text()
self.app.info("Name changed from %s to %s" % (old_name, new_name))
def on_offset_button_click(self, *args):
self.read_form()
vect = self.ui.offsetvector_entry.get_value()
self.offset(vect)
self.plot()
def on_scale_button_click(self, *args):
self.read_form()
factor = self.ui.scale_entry.get_value()
self.scale(factor)
self.plot()
def on_options_change(self, key):
self.form_fields[key].set_value(self.options[key])
return
def setup_axes(self, figure):
"""
1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
@ -89,6 +158,7 @@ class FlatCAMObj(GObject.GObject, object):
:return: None
:rtype: None
"""
print inspect.stack()[1][3], "--> FlatCAMObj.read_form()"
for option in self.options:
self.read_form_item(option)
@ -100,27 +170,25 @@ class FlatCAMObj(GObject.GObject, object):
:rtype: None
"""
self.muted_ui = True
print inspect.stack()[1][3], "--> FlatCAMObj.build_ui()"
# Where the UI for this object is drawn
box_selected = self.app.builder.get_object("box_selected")
# box_selected = self.app.builder.get_object("box_selected")
box_selected = self.app.builder.get_object("vp_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_" + self.kind) # offscreenwindow
sw = self.app.builder.get_object("sw_" + self.kind) # scrollwindows
osw.remove(sw) # TODO: Is this needed ?
vp = self.app.builder.get_object("vp_" + self.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_text_" + self.kind + "_name")
# entry_name.connect("activate", self.app.on_activate_name)
# box_selected.pack_start(sw, True, True, 0)
box_selected.add(self.ui)
self.to_form()
sw.show()
box_selected.show_all()
self.ui.show()
self.muted_ui = False
def set_form_item(self, option):
"""
@ -130,19 +198,11 @@ class FlatCAMObj(GObject.GObject, object):
:type option: str
:return: None
"""
fkind = self.form_kinds[option]
fname = fkind + "_" + self.kind + "_" + option
if fkind == 'entry_eval' or fkind == 'entry_text':
self.app.builder.get_object(fname).set_text(str(self.options[option]))
return
if fkind == 'cb':
self.app.builder.get_object(fname).set_active(self.options[option])
return
if fkind == 'radio':
self.app.builder.get_object(self.radios_inv[option][self.options[option]]).set_active(True)
return
print "Unknown kind of form item:", fkind
try:
self.form_fields[option].set_value(self.options[option])
except KeyError:
App.log.warn("Tried to set an option or field that does not exist: %s" % option)
def read_form_item(self, option):
"""
@ -152,22 +212,11 @@ class FlatCAMObj(GObject.GObject, object):
:type option: str
:return: None
"""
fkind = self.form_kinds[option]
fname = fkind + "_" + self.kind + "_" + option
if fkind == 'entry_text':
self.options[option] = self.app.builder.get_object(fname).get_text()
return
if fkind == 'entry_eval':
self.options[option] = self.app.get_eval(fname)
return
if fkind == 'cb':
self.options[option] = self.app.builder.get_object(fname).get_active()
return
if fkind == 'radio':
self.options[option] = self.app.get_radio_value(self.radios[option])
return
print "Unknown kind of form item:", fkind
try:
self.options[option] = self.form_fields[option].get_value()
except KeyError:
App.log.warning("Failed to read option from field: %s" % option)
def plot(self):
"""
@ -221,14 +270,31 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def __init__(self, name):
Gerber.__init__(self)
FlatCAMObj.__init__(self, name)
FlatCAMObj.__init__(self, name, GerberObjectUI())
self.kind = "gerber"
self.form_fields.update({
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"solid": self.ui.solid_cb,
"isotooldia": self.ui.iso_tool_dia_entry,
"isopasses": self.ui.iso_width_entry,
"isooverlap": self.ui.iso_overlap_entry,
"cutouttooldia": self.ui.cutout_tooldia_entry,
"cutoutmargin": self.ui.cutout_margin_entry,
"cutoutgapsize": self.ui.cutout_gap_entry,
"gaps": self.ui.gaps_radio,
"noncoppermargin": self.ui.noncopper_margin_entry,
"noncopperrounded": self.ui.noncopper_rounded_cb,
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb
})
# The 'name' is already in self.options from FlatCAMObj
# Automatically updates the UI
self.options.update({
"plot": True,
"mergepolys": True,
"multicolored": False,
"solid": False,
"isotooldia": 0.016,
@ -244,33 +310,137 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
"bboxrounded": False
})
# The 'name' is already in self.form_kinds from FlatCAMObj
self.form_kinds.update({
"plot": "cb",
"mergepolys": "cb",
"multicolored": "cb",
"solid": "cb",
"isotooldia": "entry_eval",
"isopasses": "entry_eval",
"isooverlap": "entry_eval",
"cutouttooldia": "entry_eval",
"cutoutmargin": "entry_eval",
"cutoutgapsize": "entry_eval",
"gaps": "radio",
"noncoppermargin": "entry_eval",
"noncopperrounded": "cb",
"bboxmargin": "entry_eval",
"bboxrounded": "cb"
})
self.radios = {"gaps": {"rb_2tb": "tb", "rb_2lr": "lr", "rb_4": "4"}}
self.radios_inv = {"gaps": {"tb": "rb_2tb", "lr": "rb_2lr", "4": "rb_4"}}
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
self.ser_attrs += ['options', 'kind']
assert isinstance(self.ui, GerberObjectUI)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click)
self.ui.solid_cb.connect('activate', self.on_solid_cb_click)
self.ui.multicolored_cb.connect('clicked', self.on_multicolored_cb_click)
self.ui.multicolored_cb.connect('activate', self.on_multicolored_cb_click)
self.ui.generate_iso_button.connect('clicked', self.on_iso_button_click)
self.ui.generate_iso_button.connect('activate', self.on_iso_button_click)
self.ui.generate_cutout_button.connect('clicked', self.on_generatecutout_button_click)
self.ui.generate_cutout_button.connect('activate', self.on_generatecutout_button_click)
self.ui.generate_bb_button.connect('clicked', self.on_generatebb_button_click)
self.ui.generate_bb_button.connect('activate', self.on_generatebb_button_click)
self.ui.generate_noncopper_button.connect('clicked', self.on_generatenoncopper_button_click)
self.ui.generate_noncopper_button.connect('activate', self.on_generatenoncopper_button_click)
def on_generatenoncopper_button_click(self, *args):
self.read_form()
name = self.options["name"] + "_noncopper"
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
bounding_box = self.solid_geometry.envelope.buffer(self.options["noncoppermargin"])
if not self.options["noncopperrounded"]:
bounding_box = bounding_box.envelope
non_copper = bounding_box.difference(self.solid_geometry)
geo_obj.solid_geometry = non_copper
# TODO: Check for None
self.app.new_object("geometry", name, geo_init)
def on_generatebb_button_click(self, *args):
self.read_form()
name = self.options["name"] + "_bbox"
def geo_init(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
# Bounding box with rounded corners
bounding_box = self.solid_geometry.envelope.buffer(self.options["bboxmargin"])
if not self.options["bboxrounded"]: # Remove rounded corners
bounding_box = bounding_box.envelope
geo_obj.solid_geometry = bounding_box
self.app.new_object("geometry", name, geo_init)
def on_generatecutout_button_click(self, *args):
self.read_form()
name = self.options["name"] + "_cutout"
def geo_init(geo_obj, app_obj):
margin = self.options["cutoutmargin"] + self.options["cutouttooldia"]/2
gap_size = self.options["cutoutgapsize"] + self.options["cutouttooldia"]
minx, miny, maxx, maxy = self.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[self.options['gaps']]
geo_obj.solid_geometry = cascaded_union([LineString(segment) for segment in cuts])
# TODO: Check for None
self.app.new_object("geometry", name, geo_init)
def on_iso_button_click(self, *args):
self.read_form()
dia = self.options["isotooldia"]
passes = int(self.options["isopasses"])
overlap = self.options["isooverlap"] * dia
for i in range(passes):
offset = (2*i + 1)/2.0 * dia - i*overlap
iso_name = self.options["name"] + "_iso%d" % (i+1)
# TODO: This is ugly. Create way to pass data into init function.
def iso_init(geo_obj, app_obj):
# Propagate options
geo_obj.options["cnctooldia"] = self.options["isotooldia"]
geo_obj.solid_geometry = self.isolation_geometry(offset)
app_obj.info("Isolation geometry created: %s" % geo_obj.options["name"])
# TODO: Do something if this is None. Offer changing name?
self.app.new_object("geometry", iso_name, iso_init)
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('plot')
self.plot()
def on_solid_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('solid')
self.plot()
def on_multicolored_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('multicolored')
self.plot()
def convert_units(self, units):
"""
Converts the units of the object by scaling dimensions in all geometry
@ -354,10 +524,20 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
def __init__(self, name):
Excellon.__init__(self)
FlatCAMObj.__init__(self, name)
FlatCAMObj.__init__(self, name, ExcellonObjectUI())
self.kind = "excellon"
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
"solid": self.ui.solid_cb,
"drillz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.feedrate_entry,
"toolselection": self.ui.tools_entry
})
self.options.update({
"plot": True,
"solid": False,
@ -367,14 +547,14 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
"toolselection": ""
})
self.form_kinds.update({
"plot": "cb",
"solid": "cb",
"drillz": "entry_eval",
"travelz": "entry_eval",
"feedrate": "entry_eval",
"toolselection": "entry_text"
})
# self.form_kinds.update({
# "plot": "cb",
# "solid": "cb",
# "drillz": "entry_eval",
# "travelz": "entry_eval",
# "feedrate": "entry_eval",
# "toolselection": "entry_text"
# })
# TODO: Document this.
self.tool_cbs = {}
@ -384,6 +564,23 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# from predecessors.
self.ser_attrs += ['options', 'kind']
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click)
self.ui.solid_cb.connect('activate', self.on_solid_cb_click)
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('plot')
self.plot()
def on_solid_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('solid')
self.plot()
def convert_units(self, units):
factor = Excellon.convert_units(self, units)
@ -457,7 +654,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
feedrate=3.0, z_cut=-0.002, tooldia=0.0):
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
FlatCAMObj.__init__(self, name)
FlatCAMObj.__init__(self, name, CNCObjectUI())
self.kind = "cncjob"
@ -466,16 +663,31 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
"tooldia": 0.4 / 25.4 # 0.4mm in inches
})
self.form_kinds.update({
"plot": "cb",
"tooldia": "entry_eval"
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
"tooldia": self.ui.tooldia_entry
})
# self.form_kinds.update({
# "plot": "cb",
# "tooldia": "entry_eval"
# })
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
self.ser_attrs += ['options', 'kind']
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('plot')
self.plot()
def plot(self):
# Does all the required setup and returns False
@ -501,15 +713,29 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"""
def __init__(self, name):
FlatCAMObj.__init__(self, name)
FlatCAMObj.__init__(self, name, GeometryObjectUI())
Geometry.__init__(self)
self.kind = "geometry"
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
# "solid": self.ui.sol,
# "multicolored": self.ui.,
"cutz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.cncfeedrate_entry,
"cnctooldia": self.ui.cnctooldia_entry,
"painttooldia": self.ui.painttooldia_entry,
"paintoverlap": self.ui.paintoverlap_entry,
"paintmargin": self.ui.paintmargin_entry
})
self.options.update({
"plot": True,
"solid": False,
"multicolored": False,
# "solid": False,
# "multicolored": False,
"cutz": -0.002,
"travelz": 0.1,
"feedrate": 5.0,
@ -519,24 +745,105 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"paintmargin": 0.01
})
self.form_kinds.update({
"plot": "cb",
"solid": "cb",
"multicolored": "cb",
"cutz": "entry_eval",
"travelz": "entry_eval",
"feedrate": "entry_eval",
"cnctooldia": "entry_eval",
"painttooldia": "entry_eval",
"paintoverlap": "entry_eval",
"paintmargin": "entry_eval"
})
# self.form_kinds.update({
# "plot": "cb",
# "solid": "cb",
# "multicolored": "cb",
# "cutz": "entry_eval",
# "travelz": "entry_eval",
# "feedrate": "entry_eval",
# "cnctooldia": "entry_eval",
# "painttooldia": "entry_eval",
# "paintoverlap": "entry_eval",
# "paintmargin": "entry_eval"
# })
# Attributes to be included in serialization
# Always append to it because it carries contents
# from predecessors.
self.ser_attrs += ['options', 'kind']
assert isinstance(self.ui, GeometryObjectUI)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.generate_cnc_button.connect('clicked', self.on_generatecnc_button_click)
self.ui.generate_cnc_button.connect('activate', self.on_generatecnc_button_click)
self.ui.generate_paint_button.connect('clicked', self.on_paint_button_click)
self.ui.generate_paint_button.connect('activate', self.on_paint_button_click)
def on_paint_button_click(self, *args):
self.app.info("Click inside the desired polygon.")
self.read_form()
tooldia = self.options["painttooldia"]
overlap = self.options["paintoverlap"]
# Connection ID for the click event
subscription = None
# To be called after clicking on the plot.
def doit(event):
self.app.plotcanvas.mpl_disconnect(subscription)
point = [event.xdata, event.ydata]
poly = find_polygon(self.solid_geometry, point)
# Initializes the new geometry object
def gen_paintarea(geo_obj, app_obj):
assert isinstance(geo_obj, FlatCAMGeometry)
#assert isinstance(app_obj, App)
cp = clear_poly(poly.buffer(-self.options["paintmargin"]), tooldia, overlap)
geo_obj.solid_geometry = cp
geo_obj.options["cnctooldia"] = tooldia
name = self.options["name"] + "_paint"
self.new_object("geometry", name, gen_paintarea)
subscription = self.app.plotcanvas.mpl_connect('button_press_event', doit)
def on_generatecnc_button_click(self, *args):
self.read_form()
job_name = self.options["name"] + "_cnc"
# Object initialization function for app.new_object()
# RUNNING ON SEPARATE THREAD!
def job_init(job_obj, app_obj):
assert isinstance(job_obj, FlatCAMCNCjob)
# Propagate options
job_obj.options["tooldia"] = self.options["cnctooldia"]
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
job_obj.z_cut = self.options["cutz"]
job_obj.z_move = self.options["travelz"]
job_obj.feedrate = self.options["feedrate"]
GLib.idle_add(lambda: app_obj.set_progress_bar(0.4, "Analyzing Geometry..."))
# TODO: The tolerance should not be hard coded. Just for testing.
job_obj.generate_from_geometry(self, tolerance=0.0005)
GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
job_obj.gcode_parse()
# TODO: job_obj.create_geometry creates stuff that is not used.
#GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
#job_obj.create_geometry()
GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
# To be run in separate thread
def job_thread(app_obj):
app_obj.new_object("cncjob", job_name, job_init)
GLib.idle_add(lambda: app_obj.info("CNCjob created: %s" % job_name))
GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, "Idle"))
# Send to worker
self.app.worker.add_task(job_thread, [self.app])
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
self.read_form_item('plot')
self.plot()
def scale(self, factor):
"""
Scales all geometry by a given factor.

143
GUIElements.py Normal file
View File

@ -0,0 +1,143 @@
from gi.repository import Gtk
import re
from copy import copy
class RadioSet(Gtk.Box):
def __init__(self, choices):
"""
The choices are specified as a list of dictionaries containing:
* 'label': Shown in the UI
* 'value': The value returned is selected
:param choices: List of choices. See description.
:type choices: list
"""
Gtk.Box.__init__(self)
self.choices = copy(choices)
self.group = None
for choice in self.choices:
if self.group is None:
choice['radio'] = Gtk.RadioButton.new_with_label(None, choice['label'])
self.group = choice['radio']
else:
choice['radio'] = Gtk.RadioButton.new_with_label_from_widget(self.group, choice['label'])
self.pack_start(choice['radio'], expand=True, fill=False, padding=2)
# choice['radio'].connect('toggled', self.on_toggle)
# def on_toggle(self, *args):
# return
def get_value(self):
for choice in self.choices:
if choice['radio'].get_active():
return choice['value']
print "ERROR: No button was toggled in RadioSet."
return None
def set_value(self, val):
for choice in self.choices:
if choice['value'] == val:
choice['radio'].set_active(True)
return
print "ERROR: Value given is not part of this RadioSet:", val
class LengthEntry(Gtk.Entry):
def __init__(self, output_units='IN'):
Gtk.Entry.__init__(self)
self.output_units = output_units
self.format_re = re.compile(r"^([^\s]+)(?:\s([a-zA-Z]+))?$")
# Unit conversion table OUTPUT-INPUT
self.scales = {
'IN': {'MM': 1/25.4},
'MM': {'IN': 25.4}
}
self.connect('activate', self.on_activate)
def on_activate(self, *args):
val = self.get_value()
if val is not None:
self.set_text(str(val))
else:
print "WARNING: Could not interpret entry:", self.get_text()
def get_value(self):
raw = self.get_text().strip(' ')
match = self.format_re.search(raw)
if not match:
return None
try:
if match.group(2) is not None and match.group(2).upper() in self.scales:
return float(match.group(1))*self.scales[self.output_units][match.group(2).upper()]
else:
return float(match.group(1))
except:
print "ERROR: Could not parse value in entry:", raw
return None
def set_value(self, val):
self.set_text(str(val))
class FloatEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
self.connect('activate', self.on_activate)
def on_activate(self, *args):
val = self.get_value()
if val is not None:
self.set_text(str(val))
else:
print "WARNING: Could not interpret entry:", self.get_text()
def get_value(self):
raw = self.get_text().strip(' ')
try:
evaled = eval(raw)
except:
print "ERROR: Could not evaluate:", raw
return None
return float(evaled)
def set_value(self, val):
self.set_text(str(val))
class IntEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
def get_value(self):
return int(self.get_text())
def set_value(self, val):
self.set_text(str(val))
class FCEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
def get_value(self):
return self.get_text()
def set_value(self, val):
self.set_text(str(val))
class FCCheckBox(Gtk.CheckButton):
def __init__(self, label=''):
Gtk.CheckButton.__init__(self, label=label)
def get_value(self):
return self.get_active()
def set_value(self, val):
self.set_active(val)

254
ObjectCollection.py Normal file
View File

@ -0,0 +1,254 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 4/20/2014 #
# MIT Licence #
############################################################
from FlatCAMObj import *
from gi.repository import Gtk, GdkPixbuf
import inspect # TODO: Remove
class ObjectCollection:
classdict = {
"gerber": FlatCAMGerber,
"excellon": FlatCAMExcellon,
"cncjob": FlatCAMCNCjob,
"geometry": FlatCAMGeometry
}
icon_files = {
"gerber": "share/flatcam_icon16.png",
"excellon": "share/drill16.png",
"cncjob": "share/cnc16.png",
"geometry": "share/geometry16.png"
}
def __init__(self):
### Icons for the list view
self.icons = {}
for kind in ObjectCollection.icon_files:
self.icons[kind] = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icon_files[kind])
### GUI List components
## Model
self.store = Gtk.ListStore(FlatCAMObj)
## View
self.view = Gtk.TreeView(model=self.store)
#self.view.connect("row_activated", self.on_row_activated)
self.tree_selection = self.view.get_selection()
self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change)
## Renderers
# Icon
renderer_pixbuf = Gtk.CellRendererPixbuf()
column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf)
def _set_cell_icon(column, cell, model, it, data):
obj = model.get_value(it, 0)
cell.set_property('pixbuf', self.icons[obj.kind])
column_pixbuf.set_cell_data_func(renderer_pixbuf, _set_cell_icon)
self.view.append_column(column_pixbuf)
# Name
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Name", renderer_text)
def _set_cell_text(column, cell, model, it, data):
obj = model.get_value(it, 0)
cell.set_property('text', obj.options["name"])
column_text.set_cell_data_func(renderer_text, _set_cell_text)
self.view.append_column(column_text)
def print_list(self):
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
print obj
iterat = self.store.iter_next(iterat)
def delete_all(self):
print "OC.delete_all()"
self.store.clear()
def delete_active(self):
print "OC.delete_active()"
try:
model, treeiter = self.tree_selection.get_selected()
self.store.remove(treeiter)
except:
pass
def on_list_selection_change(self, selection):
"""
Callback for change in selection on the objects' list.
Instructs the new selection to build the UI for its options.
:param selection: Ignored.
:return: None
"""
print inspect.stack()[1][3], "--> OC.on_list_selection_change()"
try:
self.get_active().build_ui()
except AttributeError: # For None being active
pass
def set_active(self, name):
"""
Sets an object as the active object in the program. Same
as `set_list_selection()`.
:param name: Name of the object.
:type name: str
:return: None
"""
print "OC.set_active()"
self.set_list_selection(name)
def get_active(self):
print inspect.stack()[1][3], "--> OC.get_active()"
try:
model, treeiter = self.tree_selection.get_selected()
return model[treeiter][0]
except (TypeError, ValueError):
return None
def set_list_selection(self, name):
"""
Sets which object should be selected in the list.
:param name: Name of the object.
:rtype name: str
:return: None
"""
print inspect.stack()[1][3], "--> OC.set_list_selection()"
iterat = self.store.get_iter_first()
while iterat is not None and self.store[iterat][0].options["name"] != name:
iterat = self.store.iter_next(iterat)
self.tree_selection.select_iter(iterat)
def append(self, obj, active=False):
"""
Add a FlatCAMObj the the collection. This method is thread-safe.
:param obj: FlatCAMObj to append
:type obj: FlatCAMObj
:param active: If it is to become the active object after appending
:type active: bool
:return: None
"""
print inspect.stack()[1][3], "--> OC.append()"
def guitask():
self.store.append([obj])
if active:
self.set_list_selection(obj.options["name"])
GLib.idle_add(guitask)
def get_names(self):
"""
Gets a list of the names of all objects in the collection.
:return: List of names.
:rtype: list
"""
print "OC.get_names()"
names = []
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
names.append(obj.options["name"])
iterat = self.store.iter_next(iterat)
return names
def get_bounds(self):
"""
Finds coordinates bounding all objects in the collection.
:return: [xmin, ymin, xmax, ymax]
:rtype: list
"""
print "OC.get_bounds()"
# TODO: Move the operation out of here.
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
try:
gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
except:
print "DEV WARNING: Tried to get bounds of empty geometry."
iterat = self.store.iter_next(iterat)
return [xmin, ymin, xmax, ymax]
def get_list(self):
"""
Returns a list with all FlatCAMObj.
:return: List with all FlatCAMObj.
:rtype: list
"""
collection_list = []
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
collection_list.append(obj)
iterat = self.store.iter_next(iterat)
return collection_list
def get_by_name(self, name):
"""
Fetches the FlatCAMObj with the given `name`.
:param name: The name of the object.
:type name: str
:return: The requested object or None if no such object.
:rtype: FlatCAMObj or None
"""
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
if obj.options["name"] == name:
return obj
iterat = self.store.iter_next(iterat)
return None
# def change_name(self, old_name, new_name):
# """
# Changes the name of `FlatCAMObj` named `old_name` to `new_name`.
#
# :param old_name: Name of the object to change.
# :type old_name: str
# :param new_name: New name.
# :type new_name: str
# :return: True if name change succeeded, False otherwise. Will fail
# if no object with `old_name` is found.
# :rtype: bool
# """
# print inspect.stack()[1][3], "--> OC.change_name()"
# iterat = self.store.get_iter_first()
# while iterat is not None:
# obj = self.store[iterat][0]
# if obj.options["name"] == old_name:
# obj.options["name"] = new_name
# self.store.row_changed(0, iterat)
# return True
# iterat = self.store.iter_next(iterat)
# return False

375
ObjectUI.py Normal file
View File

@ -0,0 +1,375 @@
from gi.repository import Gtk
import re
from copy import copy
from GUIElements import *
class ObjectUI(Gtk.VBox):
def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object'):
Gtk.VBox.__init__(self, spacing=3, margin=5, vexpand=False)
## Page Title box (spacing between children)
self.title_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)
self.pack_start(self.title_box, expand=False, fill=False, padding=2)
## Page Title icon
self.icon = Gtk.Image.new_from_file(icon_file)
self.title_box.pack_start(self.icon, expand=False, fill=False, padding=2)
## Title label
self.title_label = Gtk.Label()
self.title_label.set_markup("<b>" + title + "</b>")
self.title_label.set_justify(Gtk.Justification.CENTER)
self.title_box.pack_start(self.title_label, expand=False, fill=False, padding=2)
## Object name
self.name_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)
self.pack_start(self.name_box, expand=True, fill=False, padding=2)
name_label = Gtk.Label('Name:')
name_label.set_justify(Gtk.Justification.RIGHT)
self.name_box.pack_start(name_label,
expand=False, fill=False, padding=2)
self.name_entry = FCEntry()
self.name_box.pack_start(self.name_entry, expand=True, fill=False, padding=2)
## Box box for custom widgets
self.custom_box = Gtk.VBox(spacing=3, margin=0, vexpand=False)
self.pack_start(self.custom_box, expand=False, fill=False, padding=0)
## Common to all objects
## Scale
self.scale_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.scale_label.set_markup('<b>Scale:</b>')
self.pack_start(self.scale_label, expand=True, fill=False, padding=2)
grid5 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.pack_start(grid5, expand=True, fill=False, padding=2)
# Factor
l10 = Gtk.Label('Factor:', xalign=1)
grid5.attach(l10, 0, 0, 1, 1)
self.scale_entry = FloatEntry()
self.scale_entry.set_text("1.0")
grid5.attach(self.scale_entry, 1, 0, 1, 1)
# GO Button
self.scale_button = Gtk.Button(label='Scale')
self.pack_start(self.scale_button, expand=True, fill=False, padding=2)
## Offset
self.offset_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.offset_label.set_markup('<b>Offset:</b>')
self.pack_start(self.offset_label, expand=True, fill=False, padding=2)
grid6 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.pack_start(grid6, expand=True, fill=False, padding=2)
# Vector
l11 = Gtk.Label('Offset Vector:', xalign=1)
grid6.attach(l11, 0, 0, 1, 1)
self.offsetvector_entry = FCEntry()
self.offsetvector_entry.set_text("(0.0, 0.0)")
grid6.attach(self.offsetvector_entry, 1, 0, 1, 1)
self.offset_button = Gtk.Button(label='Scale')
self.pack_start(self.offset_button, expand=True, fill=False, padding=2)
def set_field(self, name, value):
getattr(self, name).set_value(value)
def get_field(self, name):
return getattr(self, name).get_value()
class CNCObjectUI(ObjectUI):
def __init__(self):
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
grid0.attach(self.plot_cb, 0, 0, 2, 1)
# Tool dia for plot
l1 = Gtk.Label('Tool dia:', xalign=1)
grid0.attach(l1, 0, 1, 1, 1)
self.tooldia_entry = LengthEntry()
grid0.attach(self.tooldia_entry, 1, 1, 1, 1)
# Update plot button
self.updateplot_button = Gtk.Button(label='Update Plot')
self.pack_start(self.updateplot_button, expand=True, fill=False, padding=2)
## Export G-Code
self.export_gcode_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.export_gcode_label.set_markup("<b>Export G-Code:</b>")
self.pack_start(self.export_gcode_label, expand=True, fill=False, padding=2)
# GO Button
self.export_gcode_button = Gtk.Button(label='Export G-Code')
self.pack_start(self.export_gcode_button, expand=True, fill=False, padding=2)
class GeometryObjectUI(ObjectUI):
def __init__(self):
ObjectUI.__init__(self, title='Geometry Object', icon_file='share/geometry32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
grid0.attach(self.plot_cb, 0, 0, 1, 1)
## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.cncjob_label.set_markup('<b>Create CNC Job:</b>')
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
# Cut Z
l1 = Gtk.Label('Cut Z:', xalign=1)
grid1.attach(l1, 0, 0, 1, 1)
self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1)
# Travel Z
l2 = Gtk.Label('Travel Z:', xalign=1)
grid1.attach(l2, 0, 1, 1, 1)
self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1)
grid1.attach(l3, 0, 2, 1, 1)
self.cncfeedrate_entry = LengthEntry()
grid1.attach(self.cncfeedrate_entry, 1, 2, 1, 1)
l4 = Gtk.Label('Tool dia:', xalign=1)
grid1.attach(l4, 0, 3, 1, 1)
self.cnctooldia_entry = LengthEntry()
grid1.attach(self.cnctooldia_entry, 1, 3, 1, 1)
self.generate_cnc_button = Gtk.Button(label='Generate')
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2)
## Paint Area
self.paint_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.paint_label.set_markup('<b>Paint Area:</b>')
self.custom_box.pack_start(self.paint_label, expand=True, fill=False, padding=2)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
# Tool dia
l5 = Gtk.Label('Tool dia:', xalign=1)
grid2.attach(l5, 0, 0, 1, 1)
self.painttooldia_entry = LengthEntry()
grid2.attach(self.painttooldia_entry, 1, 0, 1, 1)
# Overlap
l6 = Gtk.Label('Overlap:', xalign=1)
grid2.attach(l6, 0, 1, 1, 1)
self.paintoverlap_entry = LengthEntry()
grid2.attach(self.paintoverlap_entry, 1, 1, 1, 1)
# Margin
l7 = Gtk.Label('Margin:', xalign=1)
grid2.attach(l7, 0, 2, 1, 1)
self.paintmargin_entry = LengthEntry()
grid2.attach(self.paintmargin_entry, 1, 2, 1, 1)
# GO Button
self.generate_paint_button = Gtk.Button(label='Generate')
self.custom_box.pack_start(self.generate_paint_button, expand=True, fill=False, padding=2)
class ExcellonObjectUI(ObjectUI):
def __init__(self):
ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
self.plot_cb = FCCheckBox(label='Plot')
grid0.attach(self.plot_cb, 0, 0, 1, 1)
self.solid_cb = FCCheckBox(label='Solid')
grid0.attach(self.solid_cb, 1, 0, 1, 1)
## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.cncjob_label.set_markup('<b>Create CNC Job</b>')
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
l1 = Gtk.Label('Cut Z:', xalign=1)
grid1.attach(l1, 0, 0, 1, 1)
self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1)
l2 = Gtk.Label('Travel Z:', xalign=1)
grid1.attach(l2, 0, 1, 1, 1)
self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1)
grid1.attach(l3, 0, 2, 1, 1)
self.feedrate_entry = LengthEntry()
grid1.attach(self.feedrate_entry, 1, 2, 1, 1)
l4 = Gtk.Label('Tools:', xalign=1)
grid1.attach(l4, 0, 3, 1, 1)
boxt = Gtk.Box()
grid1.attach(boxt, 1, 3, 1, 1)
self.tools_entry = FCEntry()
boxt.pack_start(self.tools_entry, expand=True, fill=False, padding=2)
self.choose_tools_button = Gtk.Button(label='Choose...')
boxt.pack_start(self.choose_tools_button, expand=True, fill=False, padding=2)
self.generate_cnc_button = Gtk.Button(label='Generate')
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2)
class GerberObjectUI(ObjectUI):
def __init__(self):
ObjectUI.__init__(self, title='Gerber Object')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
grid0.attach(self.plot_cb, 0, 0, 1, 1)
# Solid CB
self.solid_cb = FCCheckBox(label='Solid')
grid0.attach(self.solid_cb, 1, 0, 1, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='Multicolored')
grid0.attach(self.multicolored_cb, 2, 0, 1, 1)
## Isolation Routing
self.isolation_routing_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.isolation_routing_label.set_markup("<b>Isolation Routing:</b>")
self.custom_box.pack_start(self.isolation_routing_label, expand=True, fill=False, padding=2)
grid = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid, expand=True, fill=False, padding=2)
l1 = Gtk.Label('Tool diam:', xalign=1)
grid.attach(l1, 0, 0, 1, 1)
self.iso_tool_dia_entry = LengthEntry()
grid.attach(self.iso_tool_dia_entry, 1, 0, 1, 1)
l2 = Gtk.Label('Width (# passes):', xalign=1)
grid.attach(l2, 0, 1, 1, 1)
self.iso_width_entry = IntEntry()
grid.attach(self.iso_width_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Pass overlap:', xalign=1)
grid.attach(l3, 0, 2, 1, 1)
self.iso_overlap_entry = FloatEntry()
grid.attach(self.iso_overlap_entry, 1, 2, 1, 1)
self.generate_iso_button = Gtk.Button(label='Generate Geometry')
self.custom_box.pack_start(self.generate_iso_button, expand=True, fill=False, padding=2)
## Board cuttout
self.isolation_routing_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.isolation_routing_label.set_markup("<b>Board cutout:</b>")
self.custom_box.pack_start(self.isolation_routing_label, expand=True, fill=False, padding=2)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
l4 = Gtk.Label('Tool dia:', xalign=1)
grid2.attach(l4, 0, 0, 1, 1)
self.cutout_tooldia_entry = LengthEntry()
grid2.attach(self.cutout_tooldia_entry, 1, 0, 1, 1)
l5 = Gtk.Label('Margin:', xalign=1)
grid2.attach(l5, 0, 1, 1, 1)
self.cutout_margin_entry = LengthEntry()
grid2.attach(self.cutout_margin_entry, 1, 1, 1, 1)
l6 = Gtk.Label('Gap size:', xalign=1)
grid2.attach(l6, 0, 2, 1, 1)
self.cutout_gap_entry = LengthEntry()
grid2.attach(self.cutout_gap_entry, 1, 2, 1, 1)
l7 = Gtk.Label('Gaps:', xalign=1)
grid2.attach(l7, 0, 3, 1, 1)
self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'},
{'label': '2 (L/R)', 'value': 'lr'},
{'label': '4', 'value': '4'}])
grid2.attach(self.gaps_radio, 1, 3, 1, 1)
self.generate_cutout_button = Gtk.Button(label='Generate Geometry')
self.custom_box.pack_start(self.generate_cutout_button, expand=True, fill=False, padding=2)
## Non-copper regions
self.noncopper_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.noncopper_label.set_markup("<b>Non-copper regions:</b>")
self.custom_box.pack_start(self.noncopper_label, expand=True, fill=False, padding=2)
grid3 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid3, expand=True, fill=False, padding=2)
l8 = Gtk.Label('Boundary margin:', xalign=1)
grid3.attach(l8, 0, 0, 1, 1)
self.noncopper_margin_entry = LengthEntry()
grid3.attach(self.noncopper_margin_entry, 1, 0, 1, 1)
self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
grid3.attach(self.noncopper_rounded_cb, 0, 1, 2, 1)
self.generate_noncopper_button = Gtk.Button(label='Generate Geometry')
self.custom_box.pack_start(self.generate_noncopper_button, expand=True, fill=False, padding=2)
## Bounding box
self.boundingbox_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.boundingbox_label.set_markup('<b>Bounding Box:</b>')
self.custom_box.pack_start(self.boundingbox_label, expand=True, fill=False, padding=2)
grid4 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid4, expand=True, fill=False, padding=2)
l9 = Gtk.Label('Boundary Margin:', xalign=1)
grid4.attach(l9, 0, 0, 1, 1)
self.bbmargin_entry = LengthEntry()
grid4.attach(self.bbmargin_entry, 1, 0, 1, 1)
self.bbrounded_cb = FCCheckBox(label="Rounded corners")
grid4.attach(self.bbrounded_cb, 0, 1, 2, 1)
self.generate_bb_button = Gtk.Button(label='Generate Geometry')
self.custom_box.pack_start(self.generate_bb_button, expand=True, fill=False, padding=2)

118
camlib.py
View File

@ -554,40 +554,6 @@ class Gerber (Geometry):
| others | Depend on ``type`` |
+-----------+-----------------------------------+
* ``paths`` (list): A path is described by a line an aperture that follows that
line. Each paths[i] is a dictionary:
+------------+------------------------------------------------+
| Key | Value |
+============+================================================+
| linestring | (Shapely.LineString) The actual path. |
+------------+------------------------------------------------+
| aperture | (str) The key for an aperture in apertures. |
+------------+------------------------------------------------+
* ``flashes`` (list): Flashes are single-point strokes of an aperture. Each
is a dictionary:
+------------+------------------------------------------------+
| Key | Value |
+============+================================================+
| loc | (Point) Shapely Point indicating location. |
+------------+------------------------------------------------+
| aperture | (str) The key for an aperture in apertures. |
+------------+------------------------------------------------+
* ``regions`` (list): Are surfaces defined by a polygon (Shapely.Polygon),
which have an exterior and zero or more interiors. An aperture is also
associated with a region. Each is a dictionary:
+------------+-----------------------------------------------------+
| Key | Value |
+============+=====================================================+
| polygon | (Shapely.Polygon) The polygon defining the region. |
+------------+-----------------------------------------------------+
| aperture | (str) The key for an aperture in apertures. |
+------------+-----------------------------------------------------+
* ``aperture_macros`` (dictionary): Are predefined geometrical structures
that can be instanciated with different parameters in an aperture
definition. See ``apertures`` above. The key is the name of the macro,
@ -635,23 +601,6 @@ class Gerber (Geometry):
# ['size':float], ['width':float],
# ['height':float]}, ...}
self.apertures = {}
# Paths [{'linestring':LineString, 'aperture':str}]
# self.paths = []
# Buffered Paths [Polygon]
# Paths transformed into Polygons by
# offsetting the aperture size/2
# self.buffered_paths = []
# Polygon regions [{'polygon':Polygon, 'aperture':str}]
# self.regions = []
# Flashes [{'loc':[float,float], 'aperture':str}]
# self.flashes = []
# Geometry from flashes
# self.flash_geometry = []
# Aperture Macros
self.aperture_macros = {}
@ -659,9 +608,8 @@ class Gerber (Geometry):
# Attributes to be included in serialization
# Always append to it because it carries contents
# from Geometry.
self.ser_attrs += ['int_digits', 'frac_digits', 'apertures', 'paths',
'buffered_paths', 'regions', 'flashes',
'flash_geometry', 'aperture_macros']
self.ser_attrs += ['int_digits', 'frac_digits', 'apertures',
'aperture_macros', 'solid_geometry']
#### Parser patterns ####
# FS - Format Specification
@ -1432,8 +1380,8 @@ class Excellon(Geometry):
self.drills = []
# Trailing "T" or leading "L"
self.zeros = ""
# Trailing "T" or leading "L" (default)
self.zeros = "L"
# Attributes to be included in serialization
# Always append to it because it carries contents
@ -1504,6 +1452,9 @@ class Excellon(Geometry):
# Various stop/pause commands
self.stop_re = re.compile(r'^((G04)|(M09)|(M06)|(M00)|(M30))')
# Parse coordinates
self.leadingzeros_re = re.compile(r'^(0*)(\d*)')
def parse_file(self, filename):
"""
@ -1538,7 +1489,7 @@ class Excellon(Geometry):
for eline in elines:
line_num += 1
### Cleanup
### Cleanup lines
eline = eline.strip(' \r\n')
## Header Begin/End ##
@ -1563,13 +1514,15 @@ class Excellon(Geometry):
match = self.coordsnoperiod_re.search(eline)
if match:
try:
x = float(match.group(1))/10000
#x = float(match.group(1))/10000
x = self.parse_number(match.group(1))
current_x = x
except TypeError:
x = current_x
try:
y = float(match.group(2))/10000
#y = float(match.group(2))/10000
y = self.parse_number(match.group(2))
current_y = y
except TypeError:
y = current_y
@ -1581,7 +1534,7 @@ class Excellon(Geometry):
self.drills.append({'point': Point((x, y)), 'tool': current_tool})
continue
## Coordinates with period ##
## Coordinates with period: Use literally. ##
match = self.coordsperiod_re.search(eline)
if match:
try:
@ -1630,6 +1583,21 @@ class Excellon(Geometry):
print "WARNING: Line ignored:", eline
def parse_number(self, number_str):
"""
Parses coordinate numbers without period.
:param number_str: String representing the numerical value.
:type number_str: str
:return: Floating point representation of the number
:rtype: foat
"""
if self.zeros == "L":
match = self.leadingzeros_re.search(number_str)
return float(number_str)/(10**(len(match.group(2))-2+len(match.group(1))))
else: # Trailing
return float(number_str)/10000
def create_geometry(self):
"""
Creates circles of the tool diameter at every point
@ -2443,33 +2411,3 @@ def parse_gerber_number(strnumber, frac_digits):
"""
return int(strnumber)*(10**(-frac_digits))
def parse_gerber_coords(gstr, int_digits, frac_digits):
"""
Parse Gerber coordinates
:param gstr: Line of G-Code containing coordinates.
:type gstr: str
:param int_digits: Number of digits in integer part of a number.
:type int_digits: int
:param frac_digits: Number of digits in frac_digits part of a number.
:type frac_digits: int
:return: [x, y] coordinates.
:rtype: list
"""
global gerbx, gerby
xindex = gstr.find("X")
yindex = gstr.find("Y")
index = gstr.find("D")
if xindex == -1:
x = gerbx
y = int(gstr[(yindex+1):index])*(10**(-frac_digits))
elif yindex == -1:
y = gerby
x = int(gstr[(xindex+1):index])*(10**(-frac_digits))
else:
x = int(gstr[(xindex+1):yindex])*(10**(-frac_digits))
y = int(gstr[(yindex+1):index])*(10**(-frac_digits))
gerbx = x
gerby = y
return [x, y]

View File

@ -1 +1 @@
{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "gerber_mergepolys": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_cutouttooldia": 0.07, "geometry_painttooldia": 0.0625, "gerber_gaps": "4", "gerber_bboxmargin": 0.0, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}
{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_cutouttooldia": 0.07, "geometry_painttooldia": 0.0625, "gerber_gaps": "4", "gerber_bboxmargin": 0.0, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_multicolored": false, "gerber_noncoppermargin": 0.0, "geometry_solid": false}

4
doc/build/app.html vendored
View File

@ -1434,8 +1434,8 @@ side of the main window.</p>
</dd></dl>
<dl class="method">
<dt id="FlatCAM.App.versionCheck">
<tt class="descname">versionCheck</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#FlatCAM.App.versionCheck" title="Permalink to this definition"></a></dt>
<dt id="FlatCAM.App.version_check">
<tt class="descname">version_check</tt><big>(</big><em>*args</em><big>)</big><a class="headerlink" href="#FlatCAM.App.version_check" title="Permalink to this definition"></a></dt>
<dd><p>Checks for the latest version of the program. Alerts the
user if theirs is outdated. This method is meant to be run
in a saeparate thread.</p>

View File

@ -1021,7 +1021,7 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%" valign="top"><dl>
<dt><a href="app.html#FlatCAM.App.versionCheck">versionCheck() (FlatCAM.App method)</a>
<dt><a href="app.html#FlatCAM.App.version_check">version_check() (FlatCAM.App method)</a>
</dt>
</dl></td>

View File

@ -1 +1 @@
[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\KiCad_Bridge2-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\bedini 7 coils capacitor discharge.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Gerbers\\AVR_Transistor_Tester_copper_bottom.GBL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Gerbers\\AVR_Transistor_Tester_copper_top.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\maitest.gtl"}]
[{"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\KiCad_Bridge2.drl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\LockController_v1.0_pcb-RoundHoles.TXT\\LockController_v1.0_pcb-RoundHoles.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\KiCad_Bridge2-F_Cu.gtl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}]