Excellon parser bug fixed, improved Gerber parse, added icons, added some tooltips.

This commit is contained in:
Juan Pablo Caram
2014-02-27 00:12:49 -05:00
parent 42f3652668
commit 68a275e042
7 changed files with 409 additions and 152 deletions

View File

@@ -11,6 +11,7 @@ import threading
# TODO: Bundle together. This is just for debugging. # TODO: Bundle together. This is just for debugging.
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GObject from gi.repository import GObject
import simplejson as json import simplejson as json
@@ -23,6 +24,7 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan
from camlib import * from camlib import *
import sys import sys
import urllib
######################################## ########################################
@@ -129,7 +131,7 @@ class FlatCAMObj:
def set_form_item(self, option): def set_form_item(self, option):
""" """
Copies the specified options to the UI form. Copies the specified option to the UI form.
:param option: Name of the option (Key in ``self.options``). :param option: Name of the option (Key in ``self.options``).
:type option: str :type option: str
@@ -150,6 +152,13 @@ class FlatCAMObj:
print "Unknown kind of form item:", fkind print "Unknown kind of form item:", fkind
def read_form_item(self, option): def read_form_item(self, option):
"""
Reads the specified option from the UI form into ``self.options``.
:param option: Name of the option.
:type option: str
:return: None
"""
fkind = self.form_kinds[option] fkind = self.form_kinds[option]
fname = fkind + "_" + self.kind + "_" + option fname = fkind + "_" + self.kind + "_" + option
@@ -175,25 +184,23 @@ class FlatCAMObj:
# Creates the axes if necessary and sets them up. # Creates the axes if necessary and sets them up.
self.setup_axes(figure) self.setup_axes(figure)
# Clear axes.
# self.axes.cla()
# return
def serialize(self): def serialize(self):
""" """
Returns a representation of the object as a dictionary so Returns a representation of the object as a dictionary so
it can be later exported as JSON. Override this method. it can be later exported as JSON. Override this method.
@return: Dictionary representing the object
@rtype: dict :return: Dictionary representing the object
:rtype: dict
""" """
return return
def deserialize(self, obj_dict): def deserialize(self, obj_dict):
""" """
Re-builds an object from its serialized version. Re-builds an object from its serialized version.
@param obj_dict: Dictionary representing a FlatCAMObj
@type obj_dict: dict :param obj_dict: Dictionary representing a FlatCAMObj
@return None :type obj_dict: dict
:return None
""" """
return return
@@ -257,6 +264,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:return: None :return: None
:rtype: None :rtype: None
""" """
factor = Gerber.convert_units(self, units) factor = Gerber.convert_units(self, units)
self.options['isotooldia'] *= factor self.options['isotooldia'] *= factor
@@ -317,7 +325,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
class FlatCAMExcellon(FlatCAMObj, Excellon): class FlatCAMExcellon(FlatCAMObj, Excellon):
""" """
Represents Excellon code. Represents Excellon/Drill code.
""" """
def __init__(self, name): def __init__(self, name):
@@ -367,7 +375,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
if not self.options["plot"]: if not self.options["plot"]:
return return
# Plot excellon try:
_ = iter(self.solid_geometry)
except TypeError:
self.solid_geometry = [self.solid_geometry]
# Plot excellon (All polygons?)
for geo in self.solid_geometry: for geo in self.solid_geometry:
x, y = geo.exterior.coords.xy x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-') self.axes.plot(x, y, 'r-')
@@ -446,6 +459,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
print "FlatCAMCNCjob.convert_units()" print "FlatCAMCNCjob.convert_units()"
self.options["tooldia"] *= factor self.options["tooldia"] *= factor
class FlatCAMGeometry(FlatCAMObj, Geometry): class FlatCAMGeometry(FlatCAMObj, Geometry):
""" """
Geometric object not associated with a specific Geometric object not associated with a specific
@@ -596,12 +610,11 @@ class App:
# Needed to interact with the GUI from other threads. # Needed to interact with the GUI from other threads.
GObject.threads_init() GObject.threads_init()
## GUI ## #### GUI ####
self.gladefile = "FlatCAM.ui" self.gladefile = "FlatCAM.ui"
self.builder = Gtk.Builder() self.builder = Gtk.Builder()
self.builder.add_from_file(self.gladefile) self.builder.add_from_file(self.gladefile)
self.window = self.builder.get_object("window1") self.window = self.builder.get_object("window1")
self.window.set_title("FlatCAM - Alpha 1 UNSTABLE - Check for updates!")
self.position_label = self.builder.get_object("label3") self.position_label = self.builder.get_object("label3")
self.grid = self.builder.get_object("grid1") self.grid = self.builder.get_object("grid1")
self.notebook = self.builder.get_object("notebook1") self.notebook = self.builder.get_object("notebook1")
@@ -613,22 +626,23 @@ class App:
# White (transparent) background on the "Options" tab. # White (transparent) background on the "Options" tab.
self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL, self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL,
Gdk.RGBA(1, 1, 1, 1)) Gdk.RGBA(1, 1, 1, 1))
# Combo box to choose between project and application options. # Combo box to choose between project and application options.
self.combo_options = self.builder.get_object("combo_options") self.combo_options = self.builder.get_object("combo_options")
self.combo_options.set_active(1) self.combo_options.set_active(1)
## Event handling ## self.setup_project_list() # The "Project" tab
self.setup_component_editor() # The "Selected" tab
#### Event handling ####
self.builder.connect_signals(self) self.builder.connect_signals(self)
## Make plot area ## #### Make plot area ####
self.figure = None self.figure = None
self.axes = None self.axes = None
self.canvas = None self.canvas = None
self.setup_plot() self.setup_plot()
self.setup_project_list() # The "Project" tab self.setup_tooltips()
self.setup_component_editor() # The "Selected" tab
#### DATA #### #### DATA ####
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
@@ -663,8 +677,6 @@ class App:
self.radios_inv = {"units": {"IN": "rb_inch", "MM": "rb_mm"}, self.radios_inv = {"units": {"IN": "rb_inch", "MM": "rb_mm"},
"gerber_gaps": {"tb": "rb_app_2tb", "lr": "rb_app_2lr", "4": "rb_app_4"}} "gerber_gaps": {"tb": "rb_app_2tb", "lr": "rb_app_2lr", "4": "rb_app_4"}}
# self.combos = []
# Options for each kind of FlatCAMObj. # Options for each kind of FlatCAMObj.
# Example: 'gerber_plot': 'cb'. The widget name would be: 'cb_app_gerber_plot' # Example: 'gerber_plot': 'cb'. The widget name would be: 'cb_app_gerber_plot'
for FlatCAMClass in [FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber, FlatCAMCNCjob]: for FlatCAMClass in [FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber, FlatCAMCNCjob]:
@@ -677,22 +689,34 @@ class App:
self.plot_click_subscribers = {} self.plot_click_subscribers = {}
# Initialization #### Initialization ####
self.load_defaults() self.load_defaults()
self.options.update(self.defaults) # Copy app defaults to project options self.options.update(self.defaults) # Copy app defaults to project options
self.options2form() # Populate the app defaults form self.options2form() # Populate the app defaults form
self.units_label.set_text("[" + self.options["units"] + "]") self.units_label.set_text("[" + self.options["units"] + "]")
# For debugging only #### Check for updates ####
self.version = 1
t1 = threading.Thread(target=self.versionCheck)
t1.daemon = True
t1.start()
#### For debugging only ###
def someThreadFunc(self): def someThreadFunc(self):
print "Hello World!" print "Hello World!"
t = threading.Thread(target=someThreadFunc, args=(self,)) t = threading.Thread(target=someThreadFunc, args=(self,))
t.daemon = True
t.start() t.start()
######################################## ########################################
## START ## ## START ##
######################################## ########################################
self.icon256 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon256.png')
self.icon48 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon48.png')
self.icon16 = GdkPixbuf.Pixbuf.new_from_file('share/flatcam_icon16.png')
Gtk.Window.set_default_icon_list([self.icon16, self.icon48, self.icon256])
self.window.set_title("FlatCAM - Alpha 2 UNSTABLE - Check for updates!")
self.window.set_default_size(900, 600) self.window.set_default_size(900, 600)
self.window.show_all() self.window.show_all()
@@ -1309,6 +1333,67 @@ class App:
for obj in self.stuff: for obj in self.stuff:
combo.append_text(obj) combo.append_text(obj)
def versionCheck(self):
"""
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.
:return: None
"""
try:
f = urllib.urlopen("http://caram.cl/flatcam/VERSION") # TODO: Hardcoded.
except:
GLib.idle_add(lambda: self.info("ERROR trying to check for latest version."))
return
try:
data = json.load(f)
except:
GLib.idle_add(lambda: self.info("ERROR trying to check for latest version."))
f.close()
return
f.close()
if self.version >= data["version"]:
GLib.idle_add(lambda: self.info("FlatCAM is up to date!"))
return
label = Gtk.Label("There is a newer version of FlatCAM\n" +
"available for download:\n\n" +
data["name"] + "\n\n" + data["message"])
dialog = Gtk.Dialog("Newer Version Available", self.window, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
dialog.set_default_size(150, 100)
dialog.set_modal(True)
box = dialog.get_content_area()
box.set_border_width(10)
box.add(label)
def do_dialog():
dialog.show_all()
response = dialog.run()
dialog.destroy()
GLib.idle_add(lambda: do_dialog())
return
def setup_tooltips(self):
tooltips = {
"cb_gerber_plot": "Plot this object on the main window.",
"cb_gerber_mergepolys": "Show overlapping polygons as single.",
"cb_gerber_solid": "Paint inside polygons.",
"cb_gerber_multicolored": "Draw polygons with different polygons.",
"button1": ""
}
for widget in tooltips:
self.builder.get_object(widget).set_tooltip_markup(tooltips[widget])
######################################## ########################################
## EVENT HANDLERS ## ## EVENT HANDLERS ##
######################################## ########################################

View File

@@ -3045,7 +3045,8 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomfit_toolbutton"> <object class="GtkToolButton" id="zoomfit_toolbutton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Zoom Fit</property> <property name="tooltip_markup" translatable="yes">Zoom Fit.
(Click on plot and hit &lt;b&gt;1&lt;/b&gt;)</property>
<property name="label" translatable="yes">Fit</property> <property name="label" translatable="yes">Fit</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-zoom-100</property> <property name="stock_id">gtk-zoom-100</property>
@@ -3060,7 +3061,9 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomin_toolbutton"> <object class="GtkToolButton" id="zoomin_toolbutton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Zoom+</property> <property name="tooltip_markup" translatable="yes">Zoom in.
(Click on plot and hit &lt;b&gt;3&lt;/b&gt;
to zoom around a point)</property>
<property name="label" translatable="yes">Zoom+</property> <property name="label" translatable="yes">Zoom+</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-zoom-in</property> <property name="stock_id">gtk-zoom-in</property>
@@ -3075,7 +3078,9 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomout_toolbutton"> <object class="GtkToolButton" id="zoomout_toolbutton">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Zoom-</property> <property name="tooltip_markup" translatable="yes">Zoom Out.
(Click on plot and hit &lt;b&gt;2&lt;/b&gt;
to zoom around a point)</property>
<property name="label" translatable="yes">Zoom-</property> <property name="label" translatable="yes">Zoom-</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-zoom-out</property> <property name="stock_id">gtk-zoom-out</property>
@@ -3090,6 +3095,7 @@ to application defaults.</property>
<object class="GtkToolButton" id="Clear"> <object class="GtkToolButton" id="Clear">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Clear Plot</property>
<property name="label" translatable="yes">Clear Plots</property> <property name="label" translatable="yes">Clear Plots</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-stop</property> <property name="stock_id">gtk-stop</property>
@@ -3104,6 +3110,7 @@ to application defaults.</property>
<object class="GtkToolButton" id="toolbutton1"> <object class="GtkToolButton" id="toolbutton1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Re-plot all</property>
<property name="label" translatable="yes">Re-plot</property> <property name="label" translatable="yes">Re-plot</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-redo</property> <property name="stock_id">gtk-redo</property>
@@ -3118,6 +3125,8 @@ to application defaults.</property>
<object class="GtkToolButton" id="toolbutton2"> <object class="GtkToolButton" id="toolbutton2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Delete selected
object.</property>
<property name="label" translatable="yes">Delete Object</property> <property name="label" translatable="yes">Delete Object</property>
<property name="use_underline">True</property> <property name="use_underline">True</property>
<property name="stock_id">gtk-delete</property> <property name="stock_id">gtk-delete</property>
@@ -3165,6 +3174,7 @@ to application defaults.</property>
<object class="GtkLabel" id="label1"> <object class="GtkLabel" id="label1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Objects in the project.</property>
<property name="label" translatable="yes">Project</property> <property name="label" translatable="yes">Project</property>
</object> </object>
<packing> <packing>
@@ -3190,6 +3200,8 @@ to application defaults.</property>
<object class="GtkLabel" id="label2"> <object class="GtkLabel" id="label2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Options and action
for the current object.</property>
<property name="label" translatable="yes">Selected</property> <property name="label" translatable="yes">Selected</property>
</object> </object>
<packing> <packing>
@@ -3229,6 +3241,15 @@ to application defaults.</property>
<object class="GtkComboBoxText" id="combo_options"> <object class="GtkComboBoxText" id="combo_options">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Application defaults get transfered
to every new project. Project options
get inherited by new project objects.
&lt;b&gt;Save&lt;/b&gt; application defaults
by choosing &lt;i&gt;File + Save defaults&lt;/i&gt;.
Project obtions are saved with the
project.</property>
<property name="margin_left">10</property> <property name="margin_left">10</property>
<property name="margin_right">10</property> <property name="margin_right">10</property>
<property name="margin_top">5</property> <property name="margin_top">5</property>
@@ -4444,6 +4465,8 @@ to application defaults.</property>
<object class="GtkLabel" id="label5"> <object class="GtkLabel" id="label5">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Project and application
defaults.</property>
<property name="label" translatable="yes">Options</property> <property name="label" translatable="yes">Options</property>
</object> </object>
<packing> <packing>
@@ -4468,6 +4491,7 @@ to application defaults.</property>
<object class="GtkLabel" id="label88"> <object class="GtkLabel" id="label88">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Active tool</property>
<property name="label" translatable="yes">Tool</property> <property name="label" translatable="yes">Tool</property>
</object> </object>
<packing> <packing>

390
camlib.py
View File

@@ -348,6 +348,9 @@ class Gerber (Geometry):
# LP - Level polarity # LP - Level polarity
self.lpol_re = re.compile(r'^%LP([DC])\*%$') self.lpol_re = re.compile(r'^%LP([DC])\*%$')
# TODO: This is bad.
self.steps_per_circ = 40
def scale(self, factor): def scale(self, factor):
""" """
Scales the objects' geometry on the XY plane by a given factor. Scales the objects' geometry on the XY plane by a given factor.
@@ -453,8 +456,12 @@ class Gerber (Geometry):
self.buffered_paths = [] self.buffered_paths = []
for path in self.paths: for path in self.paths:
width = self.apertures[path["aperture"]]["size"] try:
self.buffered_paths.append(path["linestring"].buffer(width/2)) width = self.apertures[path["aperture"]]["size"]
self.buffered_paths.append(path["linestring"].buffer(width/2))
except KeyError:
print "ERROR: Failed to buffer path: ", path
print "Apertures: ", self.apertures
def aperture_parse(self, gline): def aperture_parse(self, gline):
""" """
@@ -523,7 +530,7 @@ class Gerber (Geometry):
:rtype: None :rtype: None
""" """
path = [] # Coordinates of the current path path = [] # Coordinates of the current path, each is [x, y]
last_path_aperture = None last_path_aperture = None
current_aperture = None current_aperture = None
@@ -540,11 +547,25 @@ class Gerber (Geometry):
current_x = None current_x = None
current_y = None current_y = None
for gline in glines: # How to interprest circular interpolation: SINGLE or MULTI
quadrant_mode = None
# Linear interpolation plus flashes line_num = 0
for gline in glines:
line_num += 1
## G01 - Linear interpolation plus flashes
# Operation code (D0x) missing is deprecated... oh well I will support it.
match = self.lin_re.search(gline) match = self.lin_re.search(gline)
if match: if match:
# Dxx alone? Will ignore for now.
if match.group(1) is None and match.group(2) is None and match.group(3) is None:
try:
current_operation_code = int(match.group(4))
except:
pass # A line with just * will match too.
continue
# Parse coordinates # Parse coordinates
if match.group(2) is not None: if match.group(2) is not None:
current_x = parse_gerber_number(match.group(2), self.frac_digits) current_x = parse_gerber_number(match.group(2), self.frac_digits)
@@ -553,69 +574,141 @@ class Gerber (Geometry):
# Parse operation code # Parse operation code
if match.group(4) is not None: if match.group(4) is not None:
current_operation_code = match.group(4) current_operation_code = int(match.group(4))
# Pen down: add segment # Pen down: add segment
if current_operation_code == '1': if current_operation_code == 1:
path.append([current_x, current_y]) path.append([current_x, current_y])
last_path_aperture = current_aperture last_path_aperture = current_aperture
# Pen up: finish path # Pen up: finish path
elif current_operation_code == '2': elif current_operation_code == 2:
if len(path) > 1: if len(path) > 1:
if last_path_aperture is None:
print "Warning: No aperture defined for curent path. (%d)" % line_num
self.paths.append({"linestring": LineString(path), self.paths.append({"linestring": LineString(path),
"aperture": last_path_aperture}) "aperture": last_path_aperture})
path = [[current_x, current_y]] path = [[current_x, current_y]] # Start new path
# Flash # Flash
elif current_operation_code == '3': elif current_operation_code == 3:
self.flashes.append({"loc": [current_x, current_y], self.flashes.append({"loc": [current_x, current_y],
"aperture": current_aperture}) "aperture": current_aperture})
continue continue
# if gline.find("D01*") != -1: # pen down ## G02/3 - Circular interpolation
# path.append(parse_gerber_coords(gline, self.int_digits, self.frac_digits)) # 2-clockwise, 3-counterclockwise
# last_path_aperture = current_aperture match = self.circ_re.search(gline)
# continue if match:
#
# if gline.find("D02*") != -1: # pen up
# if len(path) > 1:
# # Path completed, create shapely LineString
# self.paths.append({"linestring": LineString(path),
# "aperture": last_path_aperture})
# path = [parse_gerber_coords(gline, self.int_digits, self.frac_digits)]
# continue
#
# indexd3 = gline.find("D03*")
# if indexd3 > 0: # Flash
# self.flashes.append({"loc": parse_gerber_coords(gline, self.int_digits, self.frac_digits),
# "aperture": current_aperture})
# continue
# if indexd3 == 0: # Flash?
# print "WARNING: Uninplemented flash style:", gline
# continue
# End region mode, x, y, i, j, d = match.groups()
if self.regionoff_re.search(gline): try:
# Only one path defines region? x = parse_gerber_number(x, self.frac_digits)
self.regions.append({"polygon": Polygon(path), except:
"aperture": last_path_aperture}) x = current_x
path = [] try:
y = parse_gerber_number(y, self.frac_digits)
except:
y = current_y
try:
i = parse_gerber_number(i, self.frac_digits)
except:
i = 0
try:
j = parse_gerber_number(j, self.frac_digits)
except:
j = 0
if quadrant_mode is None:
print "ERROR: Found arc without preceding quadrant specification G74 or G75. (%d)" % line_num
print gline
continue
if mode is None and current_interpolation_mode not in [2, 3]:
print "ERROR: Found arc without circular interpolation mode defined. (%d)" % line_num
print gline
continue
elif mode is not None:
current_interpolation_mode = int(mode)
# Set operation code if provided
if d is not None:
current_operation_code = int(d)
# Nothing created! Pen Up.
if current_operation_code == 2:
print "Warning: Arc with D2. (%d)" % line_num
if len(path) > 1:
if last_path_aperture is None:
print "Warning: No aperture defined for curent path. (%d)" % line_num
self.paths.append({"linestring": LineString(path),
"aperture": last_path_aperture})
current_x = x
current_y = y
path = [[current_x, current_y]] # Start new path
continue
# Flash should not happen here
if current_operation_code == 3:
print "ERROR: Trying to flash within arc. (%d)" % line_num
continue
if quadrant_mode == 'MULTI':
center = [i + current_x, j + current_y]
radius = sqrt(i**2 + j**2)
start = arctan2(-j, -i)
stop = arctan2(-center[1] + y, -center[0] + x)
arcdir = [None, None, "cw", "ccw"]
this_arc = arc(center, radius, start, stop,
arcdir[current_interpolation_mode],
self.steps_per_circ)
# Last point in path is current point
current_x = this_arc[-1][0]
current_y = this_arc[-1][1]
# Append
path += this_arc
last_path_aperture = current_aperture
continue
if quadrant_mode == 'SINGLE':
print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num
## G74/75* - Single or multiple quadrant arcs
match = self.quad_re.search(gline)
if match:
if match.group(1) == '4':
quadrant_mode = 'SINGLE'
else:
quadrant_mode = 'MULTI'
continue continue
# if gline.find("G37*") != -1: # end region ## G37* - End region
# # Only one path defines region? if self.regionoff_re.search(gline):
# self.regions.append({"polygon": Polygon(path), # Only one path defines region?
# "aperture": last_path_aperture}) if len(path) < 3:
# path = [] print "ERROR: Path contains less than 3 points:"
# continue print path
print "Line (%d): " % line_num, gline
path = []
continue
# For regions we may ignore an aperture that is None
self.regions.append({"polygon": Polygon(path),
"aperture": last_path_aperture})
#path = []
path = [[current_x, current_y]] # Start new path
continue
if gline.find("%ADD") != -1: # aperture definition if gline.find("%ADD") != -1: # aperture definition
self.aperture_parse(gline) # adds element to apertures self.aperture_parse(gline) # adds element to apertures
continue continue
# Interpolation mode change ## G01/2/3* - Interpolation mode change
# Can occur along with coordinates and operation code but # Can occur along with coordinates and operation code but
# sometimes by itself (handled here). # sometimes by itself (handled here).
# Example: G01* # Example: G01*
@@ -624,22 +717,15 @@ class Gerber (Geometry):
current_interpolation_mode = int(match.group(1)) current_interpolation_mode = int(match.group(1))
continue continue
# Tool/aperture change ## Tool/aperture change
# Example: D12* # Example: D12*
match = self.tool_re.search(gline) match = self.tool_re.search(gline)
if match: if match:
current_aperture = match.group(1) current_aperture = match.group(1)
continue continue
# indexstar = gline.find("*") ## Number format
# if gline.find("D") == 0: # Aperture change # Example: %FSLAX24Y24*%
# current_aperture = gline[1:indexstar]
# continue
# if gline.find("G54D") == 0: # Aperture change (deprecated)
# current_aperture = gline[4:indexstar]
# continue
# Number format
# TODO: This is ignoring most of the format. Implement the rest. # TODO: This is ignoring most of the format. Implement the rest.
match = self.fmt_re.search(gline) match = self.fmt_re.search(gline)
if match: if match:
@@ -647,30 +733,20 @@ class Gerber (Geometry):
self.frac_digits = int(match.group(4)) self.frac_digits = int(match.group(4))
continue continue
# if gline.find("%FS") != -1: # Format statement ## Mode (IN/MM)
# indexx = gline.find("X") # Example: %MOIN*%
# self.int_digits = int(gline[indexx + 1])
# self.frac_digits = int(gline[indexx + 2])
# continue
# Mode (IN/MM)
match = self.mode_re.search(gline) match = self.mode_re.search(gline)
if match: if match:
self.units = match.group(1) self.units = match.group(1)
continue continue
print "WARNING: Line ignored:", gline print "WARNING: Line ignored (%d):" % line_num, gline
if len(path) > 1: if len(path) > 1:
# EOF, create shapely LineString if something still in path # EOF, create shapely LineString if something still in path
self.paths.append({"linestring": LineString(path), self.paths.append({"linestring": LineString(path),
"aperture": last_path_aperture}) "aperture": last_path_aperture})
# if len(path) > 1:
# # EOF, create shapely LineString if something still in path
# self.paths.append({"linestring": LineString(path),
# "aperture": current_aperture})
def do_flashes(self): def do_flashes(self):
""" """
Creates geometry for Gerber flashes (aperture on a single point). Creates geometry for Gerber flashes (aperture on a single point).
@@ -778,6 +854,7 @@ class Excellon(Geometry):
def __init__(self): def __init__(self):
""" """
The constructor takes no parameters. The constructor takes no parameters.
:return: Excellon object. :return: Excellon object.
:rtype: Excellon :rtype: Excellon
""" """
@@ -796,7 +873,73 @@ class Excellon(Geometry):
# from Geometry. # from Geometry.
self.ser_attrs += ['tools', 'drills', 'zeros'] self.ser_attrs += ['tools', 'drills', 'zeros']
#### Patterns ####
# Regex basics:
# ^ - beginning
# $ - end
# *: 0 or more, +: 1 or more, ?: 0 or 1
# M48 - Beggining of Part Program Header
self.hbegin_re = re.compile(r'^M48$')
# M95 or % - End of Part Program Header
# NOTE: % has different meaning in the body
self.hend_re = re.compile(r'^(?:M95|%)$')
# FMAT Excellon format
self.fmat_re = re.compile(r'^FMAT,([12])$')
# Number format and units
# INCH uses 6 digits
# METRIC uses 5/6
self.units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?$')
# Tool definition/parameters (?= is look-ahead
# NOTE: This might be an overkill!
self.toolset_re = re.compile(r'^T(0?\d|\d\d)(?=.*C(\d*\.?\d*))?' +
r'(?=.*F(\d*\.?\d*))?(?=.*S(\d*\.?\d*))?' +
r'(?=.*B(\d*\.?\d*))?(?=.*H(\d*\.?\d*))?' +
r'(?=.*Z(-?\d*\.?\d*))?[CFSBHT]')
# Tool select
# Can have additional data after tool number but
# is ignored if present in the header.
# Warning: This will match toolset_re too.
self.toolsel_re = re.compile(r'^T((?:\d\d)|(?:\d))')
# Comment
self.comm_re = re.compile(r'^;(.*)$')
# Absolute/Incremental G90/G91
self.absinc_re = re.compile(r'^G9([01])$')
# Modes of operation
# 1-linear, 2-circCW, 3-cirCCW, 4-vardwell, 5-Drill
self.modes_re = re.compile(r'^G0([012345])')
# Measuring mode
# 1-metric, 2-inch
self.meas_re = re.compile(r'^M7([12])$')
# Coordinates
self.xcoord_re = re.compile(r'^X(\d*\.?\d*)(?:Y\d*\.?\d*)?$')
self.ycoord_re = re.compile(r'^(?:X\d*\.?\d*)?Y(\d*\.?\d*)$')
# R - Repeat hole (# times, X offset, Y offset)
self.rep_re = re.compile(r'^R(\d+)(?=.*[XY])+(?:X(\d*\.?\d*))?(?:Y(\d*\.?\d*))?$')
# Various stop/pause commands
self.stop_re = re.compile(r'^((G04)|(M09)|(M06)|(M00)|(M30))')
def parse_file(self, filename): def parse_file(self, filename):
"""
Reads the specified file as array of lines as
passes it to ``parse_lines()``.
:param filename: The file to be read and parsed.
:type filename: str
:return: None
"""
efile = open(filename, 'r') efile = open(filename, 'r')
estr = efile.readlines() estr = efile.readlines()
efile.close() efile.close()
@@ -805,71 +948,79 @@ class Excellon(Geometry):
def parse_lines(self, elines): def parse_lines(self, elines):
""" """
Main Excellon parser. Main Excellon parser.
:param elines: List of strings, each being a line of Excellon code.
:type elines: list
:return: None
""" """
units_re = re.compile(r'^(INCH|METRIC)(?:,([TL])Z)?$')
current_tool = "" current_tool = ""
in_header = False
for eline in elines: for eline in elines:
## Tool definitions ## ## Header Begin/End ##
# TODO: Verify all this if self.hbegin_re.search(eline):
indexT = eline.find("T") in_header = True
indexC = eline.find("C")
indexF = eline.find("F")
# Type 1
if indexT != -1 and indexC > indexT and indexF > indexC:
tool = eline[1:indexC]
spec = eline[indexC+1:indexF]
self.tools[tool] = float(spec)
continue
# Type 2
# TODO: Is this inches?
#indexsp = eline.find(" ")
#indexin = eline.find("in")
#if indexT != -1 and indexsp > indexT and indexin > indexsp:
# tool = eline[1:indexsp]
# spec = eline[indexsp+1:indexin]
# self.tools[tool] = spec
# continue
# Type 3
if indexT != -1 and indexC > indexT:
tool = eline[1:indexC]
spec = eline[indexC+1:-1]
self.tools[tool] = float(spec)
continue continue
## Tool change if self.hend_re.search(eline):
if indexT == 0: in_header = False
current_tool = eline[1:-1]
continue continue
## Drill #### Body ####
indexx = eline.find("X") if not in_header:
indexy = eline.find("Y")
if indexx != -1 and indexy != -1:
x = float(int(eline[indexx+1:indexy])/10000.0)
y = float(int(eline[indexy+1:-1])/10000.0)
self.drills.append({'point': Point((x, y)), 'tool': current_tool})
continue
# Units and number format ## Tool change ##
match = units_re.match(eline) match = self.toolsel_re.search(eline)
if match: if match:
self.zeros = match.group(2) # "T" or "L" current_tool = str(int(match.group(1)))
self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)] continue
## Drill ##
indexx = eline.find("X")
indexy = eline.find("Y")
if indexx != -1 and indexy != -1:
x = float(int(eline[indexx+1:indexy])/10000.0)
y = float(int(eline[indexy+1:-1])/10000.0)
self.drills.append({'point': Point((x, y)), 'tool': current_tool})
continue
#### Header ####
if in_header:
## Tool definitions ##
match = self.toolset_re.search(eline)
if match:
name = str(int(match.group(1)))
spec = {
"C": float(match.group(2)),
# "F": float(match.group(3)),
# "S": float(match.group(4)),
# "B": float(match.group(5)),
# "H": float(match.group(6)),
# "Z": float(match.group(7))
}
self.tools[name] = spec
continue
## Units and number format ##
match = self.units_re.match(eline)
if match:
self.zeros = match.group(2) # "T" or "L"
self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)]
continue
print "WARNING: Line ignored:", eline print "WARNING: Line ignored:", eline
def create_geometry(self): def create_geometry(self):
self.solid_geometry = [] self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
for drill in self.drills: for drill in self.drills:
poly = Point(drill['point']).buffer(sizes[drill['tool']]/2.0) poly = Point(drill['point']).buffer(self.tools[drill['tool']]["C"]/2.0)
self.solid_geometry.append(poly) self.solid_geometry.append(poly)
self.solid_geometry = cascaded_union(self.solid_geometry)
#self.solid_geometry = cascaded_union(self.solid_geometry)
def scale(self, factor): def scale(self, factor):
""" """
@@ -910,7 +1061,9 @@ class Excellon(Geometry):
# Tools # Tools
for tname in self.tools: for tname in self.tools:
self.tools[tname] *= factor self.tools[tname]["C"] *= factor
self.create_geometry()
return factor return factor
@@ -1236,8 +1389,7 @@ class CNCjob(Geometry):
arcdir = [None, None, "cw", "ccw"] arcdir = [None, None, "cw", "ccw"]
if current['G'] in [0, 1]: # line if current['G'] in [0, 1]: # line
path.append((x, y)) path.append((x, y))
# geometry.append({'geom': LineString([(current['X'], current['Y']),
# (x, y)]), 'kind': kind})
if current['G'] in [2, 3]: # arc if current['G'] in [2, 3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']] center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2) radius = sqrt(gobj['I']**2 + gobj['J']**2)
@@ -1246,10 +1398,6 @@ class CNCjob(Geometry):
path += arc(center, radius, start, stop, path += arc(center, radius, start, stop,
arcdir[current['G']], arcdir[current['G']],
self.steps_per_circ) self.steps_per_circ)
# geometry.append({'geom': arc(center, radius, start, stop,
# arcdir[current['G']],
# self.steps_per_circ),
# 'kind': kind})
# Update current instruction # Update current instruction
for code in gobj: for code in gobj:

BIN
share/flatcam_icon16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

BIN
share/flatcam_icon256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
share/flatcam_icon32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
share/flatcam_icon48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 833 B