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.
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GLib
from gi.repository import GObject
import simplejson as json
@@ -23,6 +24,7 @@ from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCan
from camlib import *
import sys
import urllib
########################################
@@ -129,7 +131,7 @@ class FlatCAMObj:
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``).
:type option: str
@@ -150,6 +152,13 @@ class FlatCAMObj:
print "Unknown kind of form item:", fkind
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]
fname = fkind + "_" + self.kind + "_" + option
@@ -175,25 +184,23 @@ class FlatCAMObj:
# Creates the axes if necessary and sets them up.
self.setup_axes(figure)
# Clear axes.
# self.axes.cla()
# return
def serialize(self):
"""
Returns a representation of the object as a dictionary so
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
def deserialize(self, obj_dict):
"""
Re-builds an object from its serialized version.
@param obj_dict: Dictionary representing a FlatCAMObj
@type obj_dict: dict
@return None
:param obj_dict: Dictionary representing a FlatCAMObj
:type obj_dict: dict
:return None
"""
return
@@ -257,6 +264,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
:return: None
:rtype: None
"""
factor = Gerber.convert_units(self, units)
self.options['isotooldia'] *= factor
@@ -317,7 +325,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
class FlatCAMExcellon(FlatCAMObj, Excellon):
"""
Represents Excellon code.
Represents Excellon/Drill code.
"""
def __init__(self, name):
@@ -367,7 +375,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
if not self.options["plot"]:
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:
x, y = geo.exterior.coords.xy
self.axes.plot(x, y, 'r-')
@@ -446,6 +459,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
print "FlatCAMCNCjob.convert_units()"
self.options["tooldia"] *= factor
class FlatCAMGeometry(FlatCAMObj, Geometry):
"""
Geometric object not associated with a specific
@@ -596,12 +610,11 @@ class App:
# Needed to interact with the GUI from other threads.
GObject.threads_init()
## GUI ##
#### GUI ####
self.gladefile = "FlatCAM.ui"
self.builder = Gtk.Builder()
self.builder.add_from_file(self.gladefile)
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.grid = self.builder.get_object("grid1")
self.notebook = self.builder.get_object("notebook1")
@@ -613,22 +626,23 @@ class App:
# White (transparent) background on the "Options" tab.
self.builder.get_object("vp_options").override_background_color(Gtk.StateType.NORMAL,
Gdk.RGBA(1, 1, 1, 1))
# Combo box to choose between project and application options.
self.combo_options = self.builder.get_object("combo_options")
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)
## Make plot area ##
#### Make plot area ####
self.figure = None
self.axes = None
self.canvas = None
self.setup_plot()
self.setup_project_list() # The "Project" tab
self.setup_component_editor() # The "Selected" tab
self.setup_tooltips()
#### DATA ####
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
@@ -663,8 +677,6 @@ class App:
self.radios_inv = {"units": {"IN": "rb_inch", "MM": "rb_mm"},
"gerber_gaps": {"tb": "rb_app_2tb", "lr": "rb_app_2lr", "4": "rb_app_4"}}
# self.combos = []
# Options for each kind of FlatCAMObj.
# Example: 'gerber_plot': 'cb'. The widget name would be: 'cb_app_gerber_plot'
for FlatCAMClass in [FlatCAMExcellon, FlatCAMGeometry, FlatCAMGerber, FlatCAMCNCjob]:
@@ -677,22 +689,34 @@ class App:
self.plot_click_subscribers = {}
# Initialization
#### Initialization ####
self.load_defaults()
self.options.update(self.defaults) # Copy app defaults to project options
self.options2form() # Populate the app defaults form
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):
print "Hello World!"
t = threading.Thread(target=someThreadFunc, args=(self,))
t.daemon = True
t.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.show_all()
@@ -1309,6 +1333,67 @@ class App:
for obj in self.stuff:
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 ##
########################################

View File

@@ -3045,7 +3045,8 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomfit_toolbutton">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-zoom-100</property>
@@ -3060,7 +3061,9 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomin_toolbutton">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-zoom-in</property>
@@ -3075,7 +3078,9 @@ to application defaults.</property>
<object class="GtkToolButton" id="zoomout_toolbutton">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-zoom-out</property>
@@ -3090,6 +3095,7 @@ to application defaults.</property>
<object class="GtkToolButton" id="Clear">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-stop</property>
@@ -3104,6 +3110,7 @@ to application defaults.</property>
<object class="GtkToolButton" id="toolbutton1">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-redo</property>
@@ -3118,6 +3125,8 @@ to application defaults.</property>
<object class="GtkToolButton" id="toolbutton2">
<property name="visible">True</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="use_underline">True</property>
<property name="stock_id">gtk-delete</property>
@@ -3165,6 +3174,7 @@ to application defaults.</property>
<object class="GtkLabel" id="label1">
<property name="visible">True</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>
</object>
<packing>
@@ -3190,6 +3200,8 @@ to application defaults.</property>
<object class="GtkLabel" id="label2">
<property name="visible">True</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>
</object>
<packing>
@@ -3229,6 +3241,15 @@ to application defaults.</property>
<object class="GtkComboBoxText" id="combo_options">
<property name="visible">True</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_right">10</property>
<property name="margin_top">5</property>
@@ -4444,6 +4465,8 @@ to application defaults.</property>
<object class="GtkLabel" id="label5">
<property name="visible">True</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>
</object>
<packing>
@@ -4468,6 +4491,7 @@ to application defaults.</property>
<object class="GtkLabel" id="label88">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_markup" translatable="yes">Active tool</property>
<property name="label" translatable="yes">Tool</property>
</object>
<packing>

400
camlib.py
View File

@@ -348,6 +348,9 @@ class Gerber (Geometry):
# LP - Level polarity
self.lpol_re = re.compile(r'^%LP([DC])\*%$')
# TODO: This is bad.
self.steps_per_circ = 40
def scale(self, factor):
"""
Scales the objects' geometry on the XY plane by a given factor.
@@ -453,8 +456,12 @@ class Gerber (Geometry):
self.buffered_paths = []
for path in self.paths:
width = self.apertures[path["aperture"]]["size"]
self.buffered_paths.append(path["linestring"].buffer(width/2))
try:
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):
"""
@@ -523,7 +530,7 @@ class Gerber (Geometry):
:rtype: None
"""
path = [] # Coordinates of the current path
path = [] # Coordinates of the current path, each is [x, y]
last_path_aperture = None
current_aperture = None
@@ -540,11 +547,25 @@ class Gerber (Geometry):
current_x = 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)
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
if match.group(2) is not None:
current_x = parse_gerber_number(match.group(2), self.frac_digits)
@@ -553,69 +574,141 @@ class Gerber (Geometry):
# Parse operation code
if match.group(4) is not None:
current_operation_code = match.group(4)
current_operation_code = int(match.group(4))
# Pen down: add segment
if current_operation_code == '1':
if current_operation_code == 1:
path.append([current_x, current_y])
last_path_aperture = current_aperture
# Pen up: finish path
elif current_operation_code == '2':
elif current_operation_code == 2:
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})
path = [[current_x, current_y]]
path = [[current_x, current_y]] # Start new path
# Flash
elif current_operation_code == '3':
elif current_operation_code == 3:
self.flashes.append({"loc": [current_x, current_y],
"aperture": current_aperture})
continue
# if gline.find("D01*") != -1: # pen down
# path.append(parse_gerber_coords(gline, self.int_digits, self.frac_digits))
# last_path_aperture = current_aperture
# continue
#
# 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
## G02/3 - Circular interpolation
# 2-clockwise, 3-counterclockwise
match = self.circ_re.search(gline)
if match:
# End region
if self.regionoff_re.search(gline):
# Only one path defines region?
self.regions.append({"polygon": Polygon(path),
"aperture": last_path_aperture})
path = []
mode, x, y, i, j, d = match.groups()
try:
x = parse_gerber_number(x, self.frac_digits)
except:
x = current_x
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
# if gline.find("G37*") != -1: # end region
# # Only one path defines region?
# self.regions.append({"polygon": Polygon(path),
# "aperture": last_path_aperture})
# path = []
# continue
## G37* - End region
if self.regionoff_re.search(gline):
# Only one path defines region?
if len(path) < 3:
print "ERROR: Path contains less than 3 points:"
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
self.aperture_parse(gline) # adds element to apertures
continue
# Interpolation mode change
## G01/2/3* - Interpolation mode change
# Can occur along with coordinates and operation code but
# sometimes by itself (handled here).
# Example: G01*
@@ -624,22 +717,15 @@ class Gerber (Geometry):
current_interpolation_mode = int(match.group(1))
continue
# Tool/aperture change
## Tool/aperture change
# Example: D12*
match = self.tool_re.search(gline)
if match:
current_aperture = match.group(1)
continue
# indexstar = gline.find("*")
# if gline.find("D") == 0: # Aperture change
# current_aperture = gline[1:indexstar]
# continue
# if gline.find("G54D") == 0: # Aperture change (deprecated)
# current_aperture = gline[4:indexstar]
# continue
# Number format
## Number format
# Example: %FSLAX24Y24*%
# TODO: This is ignoring most of the format. Implement the rest.
match = self.fmt_re.search(gline)
if match:
@@ -647,30 +733,20 @@ class Gerber (Geometry):
self.frac_digits = int(match.group(4))
continue
# if gline.find("%FS") != -1: # Format statement
# indexx = gline.find("X")
# self.int_digits = int(gline[indexx + 1])
# self.frac_digits = int(gline[indexx + 2])
# continue
# Mode (IN/MM)
## Mode (IN/MM)
# Example: %MOIN*%
match = self.mode_re.search(gline)
if match:
self.units = match.group(1)
continue
print "WARNING: Line ignored:", gline
print "WARNING: Line ignored (%d):" % line_num, gline
if len(path) > 1:
# EOF, create shapely LineString if something still in path
self.paths.append({"linestring": LineString(path),
"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):
"""
Creates geometry for Gerber flashes (aperture on a single point).
@@ -778,6 +854,7 @@ class Excellon(Geometry):
def __init__(self):
"""
The constructor takes no parameters.
:return: Excellon object.
:rtype: Excellon
"""
@@ -795,8 +872,74 @@ class Excellon(Geometry):
# Always append to it because it carries contents
# from Geometry.
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):
"""
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')
estr = efile.readlines()
efile.close()
@@ -805,71 +948,79 @@ class Excellon(Geometry):
def parse_lines(self, elines):
"""
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 = ""
in_header = False
for eline in elines:
## Tool definitions ##
# TODO: Verify all this
indexT = eline.find("T")
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
## Tool change
if indexT == 0:
current_tool = eline[1:-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})
## Header Begin/End ##
if self.hbegin_re.search(eline):
in_header = True
continue
# Units and number format
match = units_re.match(eline)
if match:
self.zeros = match.group(2) # "T" or "L"
self.units = {"INCH": "IN", "METRIC": "MM"}[match.group(1)]
if self.hend_re.search(eline):
in_header = False
continue
#### Body ####
if not in_header:
## Tool change ##
match = self.toolsel_re.search(eline)
if match:
current_tool = str(int(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
def create_geometry(self):
self.solid_geometry = []
sizes = {}
for tool in self.tools:
sizes[tool] = float(self.tools[tool])
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 = cascaded_union(self.solid_geometry)
#self.solid_geometry = cascaded_union(self.solid_geometry)
def scale(self, factor):
"""
@@ -910,7 +1061,9 @@ class Excellon(Geometry):
# Tools
for tname in self.tools:
self.tools[tname] *= factor
self.tools[tname]["C"] *= factor
self.create_geometry()
return factor
@@ -1236,8 +1389,7 @@ class CNCjob(Geometry):
arcdir = [None, None, "cw", "ccw"]
if current['G'] in [0, 1]: # line
path.append((x, y))
# geometry.append({'geom': LineString([(current['X'], current['Y']),
# (x, y)]), 'kind': kind})
if current['G'] in [2, 3]: # arc
center = [gobj['I'] + current['X'], gobj['J'] + current['Y']]
radius = sqrt(gobj['I']**2 + gobj['J']**2)
@@ -1246,10 +1398,6 @@ class CNCjob(Geometry):
path += arc(center, radius, start, stop,
arcdir[current['G']],
self.steps_per_circ)
# geometry.append({'geom': arc(center, radius, start, stop,
# arcdir[current['G']],
# self.steps_per_circ),
# 'kind': kind})
# Update current instruction
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