diff --git a/FlatCAM.py b/FlatCAM.py
index 1c1b1f7f..1cb60c9f 100644
--- a/FlatCAM.py
+++ b/FlatCAM.py
@@ -1,5 +1,6 @@
import sys
from PyQt4 import QtGui
+from PyQt4 import QtCore
from FlatCAMApp import App
def debug_trace():
@@ -10,6 +11,10 @@ def debug_trace():
#set_trace()
debug_trace()
+
+# all X11 calling should be thread safe otherwise we have strange issues
+QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
+
app = QtGui.QApplication(sys.argv)
fc = App()
sys.exit(app.exec_())
\ No newline at end of file
diff --git a/FlatCAMApp.py b/FlatCAMApp.py
index c510b742..872a6e4f 100644
--- a/FlatCAMApp.py
+++ b/FlatCAMApp.py
@@ -1,4 +1,4 @@
-import sys
+import sys, traceback
import urllib
import getopt
import random
@@ -27,7 +27,7 @@ from FlatCAMDraw import FlatCAMDraw
from FlatCAMProcess import *
from MeasurementTool import Measurement
from DblSidedTool import DblSidedTool
-
+import tclCommands
########################################
## App ##
@@ -107,6 +107,9 @@ class App(QtCore.QObject):
message = QtCore.pyqtSignal(str, str, str)
+ # Emmited when shell command is finished(one command only)
+ shell_command_finished = QtCore.pyqtSignal(object)
+
# Emitted when an unhandled exception happens
# in the worker task.
thread_exception = QtCore.pyqtSignal(object)
@@ -283,6 +286,8 @@ class App(QtCore.QObject):
"cncjob_tooldia": 0.016,
"cncjob_prepend": "",
"cncjob_append": "",
+ "background_timeout": 300000, #default value is 5 minutes
+ "verbose_error_level": 0, # shell verbosity 0 = default(python trace only for unknown errors), 1 = show trace(show trace allways), 2 = (For the future).
# Persistence
"last_folder": None,
@@ -528,8 +533,8 @@ class App(QtCore.QObject):
self.shell.resize(*self.defaults["shell_shape"])
self.shell.append_output("FlatCAM %s\n(c) 2014-2015 Juan Pablo Caram\n\n" % self.version)
self.shell.append_output("Type help to get started.\n\n")
- self.tcl = Tkinter.Tcl()
- self.setup_shell()
+
+ self.init_tcl()
self.ui.shell_dock = QtGui.QDockWidget("FlatCAM TCL Shell")
self.ui.shell_dock.setWidget(self.shell)
@@ -559,6 +564,17 @@ class App(QtCore.QObject):
App.log.debug("END of constructor. Releasing control.")
+ def init_tcl(self):
+ if hasattr(self,'tcl'):
+ # self.tcl = None
+ # TODO we need to clean non default variables and procedures here
+ # new object cannot be used here as it will not remember values created for next passes,
+ # because tcl was execudted in old instance of TCL
+ pass
+ else:
+ self.tcl = Tkinter.Tcl()
+ self.setup_shell()
+
def defaults_read_form(self):
for option in self.defaults_form_fields:
self.defaults[option] = self.defaults_form_fields[option].get_value()
@@ -661,36 +677,111 @@ class App(QtCore.QObject):
else:
self.defaults['stats'][resource] = 1
- def raiseTclError(self, text):
+ class TclErrorException(Exception):
+ """
+ this exception is deffined here, to be able catch it if we sucessfully handle all errors from shell command
+ """
+ pass
+
+ def raise_tcl_unknown_error(self, unknownException):
+ """
+ raise Exception if is different type than TclErrorException
+ this is here mainly to show unknown errors inside TCL shell console
+ :param unknownException:
+ :return:
+ """
+
+ if not isinstance(unknownException, self.TclErrorException):
+ self.raise_tcl_error("Unknown error: %s" % str(unknownException))
+ else:
+ raise unknownException
+
+ def display_tcl_error(self, error, error_info=None):
+ """
+ escape bracket [ with \ otherwise there is error
+ "ERROR: missing close-bracket" instead of real error
+ :param error: it may be text or exception
+ :return: None
+ """
+
+ if isinstance(error, Exception):
+
+ exc_type, exc_value, exc_traceback = error_info
+ if not isinstance(error, self.TclErrorException):
+ show_trace = 1
+ else:
+ show_trace = int(self.defaults['verbose_error_level'])
+
+ if show_trace > 0:
+ trc=traceback.format_list(traceback.extract_tb(exc_traceback))
+ trc_formated=[]
+ for a in reversed(trc):
+ trc_formated.append(a.replace(" ", " > ").replace("\n",""))
+ text="%s\nPython traceback: %s\n%s" % (exc_value,
+ exc_type,
+ "\n".join(trc_formated))
+
+ else:
+ text="%s" % error
+ else:
+ text=error
+
+ text = text.replace('[', '\\[').replace('"','\\"')
+
+ self.tcl.eval('return -code error "%s"' % text)
+
+ def raise_tcl_error(self, text):
"""
this method pass exception from python into TCL as error, so we get stacktrace and reason
:param text: text of error
:return: raise exception
"""
- self.tcl.eval('return -code error "%s"' % text)
- raise Exception(text)
+
+ self.display_tcl_error(text)
+ raise self.TclErrorException(text)
def exec_command(self, text):
"""
Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
+ Also handles execution in separated threads
+
+ :param text:
+ :return: output if there was any
+ """
+
+ self.report_usage('exec_command')
+
+ result = self.exec_command_test(text, False)
+ return result
+
+ def exec_command_test(self, text, reraise=True):
+ """
+ Handles input from the shell. See FlatCAMApp.setup_shell for shell commands.
:param text: Input command
- :return: None
+ :param reraise: raise exception and not hide it, used mainly in unittests
+ :return: output if there was any
"""
- self.report_usage('exec_command')
text = str(text)
try:
+ self.shell.open_proccessing()
result = self.tcl.eval(str(text))
- self.shell.append_output(result + '\n')
+ if result!='None':
+ self.shell.append_output(result + '\n')
except Tkinter.TclError, e:
#this will display more precise answer if something in TCL shell fail
result = self.tcl.eval("set errorInfo")
self.log.error("Exec command Exception: %s" % (result + '\n'))
self.shell.append_error('ERROR: ' + result + '\n')
- #show error in console and just return
- return
+ #show error in console and just return or in test raise exception
+ if reraise:
+ raise e
+ finally:
+ self.shell.close_proccessing()
+ pass
+ return result
"""
Code below is unsused. Saved for later.
@@ -1024,6 +1115,7 @@ class App(QtCore.QObject):
toggle shell if is visible close it if closed open it
:return:
"""
+
if self.ui.shell_dock.isVisible():
self.ui.shell_dock.hide()
else:
@@ -1036,6 +1128,7 @@ class App(QtCore.QObject):
:return: None
"""
+
objs = self.collection.get_selected()
def initialize(obj, app):
@@ -1483,6 +1576,9 @@ class App(QtCore.QObject):
self.plotcanvas.clear()
+ # tcl needs to be reinitialized, otherwise old shell variables etc remains
+ self.init_tcl()
+
self.collection.delete_all()
self.setup_component_editor()
@@ -1744,6 +1840,7 @@ class App(QtCore.QObject):
:param outname:
:return:
"""
+
self.log.debug("export_svg()")
try:
@@ -1770,7 +1867,7 @@ class App(QtCore.QObject):
svg_header = ''
svg_elem = svg_header + exported_svg + svg_footer
@@ -2409,85 +2506,96 @@ class App(QtCore.QObject):
return 'Ok'
- def geocutout(name, *args):
- """
+ def geocutout(name=None, *args):
+ '''
+ TCL shell command - see help section
+
Subtract gaps from geometry, this will not create new object
- :param name:
- :param args:
- :return:
- """
- a, kwa = h(*args)
- types = {'dia': float,
- 'gapsize': float,
- 'gaps': str}
-
- # How gaps wil be rendered:
- # lr - left + right
- # tb - top + bottom
- # 4 - left + right +top + bottom
- # 2lr - 2*left + 2*right
- # 2tb - 2*top + 2*bottom
- # 8 - 2*left + 2*right +2*top + 2*bottom
-
- for key in kwa:
- if key not in types:
- return 'Unknown parameter: %s' % key
- kwa[key] = types[key](kwa[key])
+ :param name: name of object
+ :param args: array of arguments
+ :return: "Ok" if completed without errors
+ '''
try:
- obj = self.collection.get_by_name(str(name))
- except:
- return "Could not retrieve object: %s" % name
+ a, kwa = h(*args)
+ types = {'dia': float,
+ 'gapsize': float,
+ 'gaps': str}
- # Get min and max data for each object as we just cut rectangles across X or Y
- xmin, ymin, xmax, ymax = obj.bounds()
- px = 0.5 * (xmin + xmax)
- py = 0.5 * (ymin + ymax)
- lenghtx = (xmax - xmin)
- lenghty = (ymax - ymin)
- gapsize = kwa['gapsize'] + kwa['dia'] / 2
-
- if kwa['gaps'] == '8' or kwa['gaps']=='2lr':
-
- subtract_rectangle(name,
- xmin - gapsize,
- py - gapsize + lenghty / 4,
- xmax + gapsize,
- py + gapsize + lenghty / 4)
- subtract_rectangle(name,
- xmin-gapsize,
- py - gapsize - lenghty / 4,
- xmax + gapsize,
- py + gapsize - lenghty / 4)
-
- if kwa['gaps'] == '8' or kwa['gaps']=='2tb':
- subtract_rectangle(name,
- px - gapsize + lenghtx / 4,
- ymin-gapsize,
- px + gapsize + lenghtx / 4,
- ymax + gapsize)
- subtract_rectangle(name,
- px - gapsize - lenghtx / 4,
- ymin - gapsize,
- px + gapsize - lenghtx / 4,
- ymax + gapsize)
-
- if kwa['gaps'] == '4' or kwa['gaps']=='lr':
- subtract_rectangle(name,
- xmin - gapsize,
- py - gapsize,
- xmax + gapsize,
- py + gapsize)
-
- if kwa['gaps'] == '4' or kwa['gaps']=='tb':
- subtract_rectangle(name,
- px - gapsize,
- ymin - gapsize,
- px + gapsize,
- ymax + gapsize)
-
- return 'Ok'
+ # How gaps wil be rendered:
+ # lr - left + right
+ # tb - top + bottom
+ # 4 - left + right +top + bottom
+ # 2lr - 2*left + 2*right
+ # 2tb - 2*top + 2*bottom
+ # 8 - 2*left + 2*right +2*top + 2*bottom
+
+ if name is None:
+ self.raise_tcl_error('Argument name is missing.')
+
+ for key in kwa:
+ if key not in types:
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ kwa[key] = types[key](kwa[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
+
+ try:
+ obj = self.collection.get_by_name(str(name))
+ except:
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
+
+ # Get min and max data for each object as we just cut rectangles across X or Y
+ xmin, ymin, xmax, ymax = obj.bounds()
+ px = 0.5 * (xmin + xmax)
+ py = 0.5 * (ymin + ymax)
+ lenghtx = (xmax - xmin)
+ lenghty = (ymax - ymin)
+ gapsize = kwa['gapsize'] + kwa['dia'] / 2
+
+ if kwa['gaps'] == '8' or kwa['gaps']=='2lr':
+
+ subtract_rectangle(name,
+ xmin - gapsize,
+ py - gapsize + lenghty / 4,
+ xmax + gapsize,
+ py + gapsize + lenghty / 4)
+ subtract_rectangle(name,
+ xmin-gapsize,
+ py - gapsize - lenghty / 4,
+ xmax + gapsize,
+ py + gapsize - lenghty / 4)
+
+ if kwa['gaps'] == '8' or kwa['gaps']=='2tb':
+ subtract_rectangle(name,
+ px - gapsize + lenghtx / 4,
+ ymin-gapsize,
+ px + gapsize + lenghtx / 4,
+ ymax + gapsize)
+ subtract_rectangle(name,
+ px - gapsize - lenghtx / 4,
+ ymin - gapsize,
+ px + gapsize - lenghtx / 4,
+ ymax + gapsize)
+
+ if kwa['gaps'] == '4' or kwa['gaps']=='lr':
+ subtract_rectangle(name,
+ xmin - gapsize,
+ py - gapsize,
+ xmax + gapsize,
+ py + gapsize)
+
+ if kwa['gaps'] == '4' or kwa['gaps']=='tb':
+ subtract_rectangle(name,
+ px - gapsize,
+ ymin - gapsize,
+ px + gapsize,
+ ymax + gapsize)
+
+ except Exception as unknown:
+ self.raise_tcl_unknown_error(unknown)
def mirror(name, *args):
a, kwa = h(*args)
@@ -2761,59 +2869,63 @@ class App(QtCore.QObject):
:param args: array of arguments
:return: "Ok" if completed without errors
'''
- a, kwa = h(*args)
- types = {'tools': str,
- 'outname': str,
- 'drillz': float,
- 'travelz': float,
- 'feedrate': float,
- 'spindlespeed': int,
- 'toolchange': int
- }
- if name is None:
- self.raiseTclError('Argument name is missing.')
+ try:
+ a, kwa = h(*args)
+ types = {'tools': str,
+ 'outname': str,
+ 'drillz': float,
+ 'travelz': float,
+ 'feedrate': float,
+ 'spindlespeed': int,
+ 'toolchange': int
+ }
+
+ if name is None:
+ self.raise_tcl_error('Argument name is missing.')
+
+ for key in kwa:
+ if key not in types:
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ kwa[key] = types[key](kwa[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
- for key in kwa:
- if key not in types:
- self.raiseTclError('Unknown parameter: %s' % key)
try:
- kwa[key] = types[key](kwa[key])
+ obj = self.collection.get_by_name(str(name))
+ except:
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
+
+ if obj is None:
+ self.raise_tcl_error('Object not found: %s' % name)
+
+ if not isinstance(obj, FlatCAMExcellon):
+ self.raise_tcl_error('Only Excellon objects can be drilled, got %s %s.' % (name, type(obj)))
+
+ try:
+ # Get the tools from the list
+ job_name = kwa["outname"]
+
+ # Object initialization function for app.new_object()
+ def job_init(job_obj, app_obj):
+ job_obj.z_cut = kwa["drillz"]
+ job_obj.z_move = kwa["travelz"]
+ job_obj.feedrate = kwa["feedrate"]
+ job_obj.spindlespeed = kwa["spindlespeed"] if "spindlespeed" in kwa else None
+ toolchange = True if "toolchange" in kwa and kwa["toolchange"] == 1 else False
+ job_obj.generate_from_excellon_by_tool(obj, kwa["tools"], toolchange)
+ job_obj.gcode_parse()
+ job_obj.create_geometry()
+
+ obj.app.new_object("cncjob", job_name, job_init)
+
except Exception, e:
- self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, str(types[key])))
+ self.raise_tcl_error("Operation failed: %s" % str(e))
- try:
- obj = self.collection.get_by_name(str(name))
- except:
- self.raiseTclError("Could not retrieve object: %s" % name)
+ except Exception as unknown:
+ self.raise_tcl_unknown_error(unknown)
- if obj is None:
- self.raiseTclError('Object not found: %s' % name)
-
- if not isinstance(obj, FlatCAMExcellon):
- self.raiseTclError('Only Excellon objects can be drilled, got %s %s.' % (name, type(obj)))
-
- try:
- # Get the tools from the list
- job_name = kwa["outname"]
-
- # Object initialization function for app.new_object()
- def job_init(job_obj, app_obj):
- job_obj.z_cut = kwa["drillz"]
- job_obj.z_move = kwa["travelz"]
- job_obj.feedrate = kwa["feedrate"]
- job_obj.spindlespeed = kwa["spindlespeed"] if "spindlespeed" in kwa else None
- toolchange = True if "toolchange" in kwa and kwa["toolchange"] == 1 else False
- job_obj.generate_from_excellon_by_tool(obj, kwa["tools"], toolchange)
- job_obj.gcode_parse()
- job_obj.create_geometry()
-
- obj.app.new_object("cncjob", job_name, job_init)
-
- except Exception, e:
- self.raiseTclError("Operation failed: %s" % str(e))
-
- return 'Ok'
def millholes(name=None, *args):
'''
@@ -2822,48 +2934,51 @@ class App(QtCore.QObject):
:param args: array of arguments
:return: "Ok" if completed without errors
'''
- a, kwa = h(*args)
- types = {'tooldia': float,
- 'tools': str,
- 'outname': str}
- if name is None:
- self.raiseTclError('Argument name is missing.')
+ try:
+ a, kwa = h(*args)
+ types = {'tooldia': float,
+ 'tools': str,
+ 'outname': str}
+
+ if name is None:
+ self.raise_tcl_error('Argument name is missing.')
+
+ for key in kwa:
+ if key not in types:
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ kwa[key] = types[key](kwa[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
- for key in kwa:
- if key not in types:
- self.raiseTclError('Unknown parameter: %s' % key)
try:
- kwa[key] = types[key](kwa[key])
- except Exception, e:
- self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+ if 'tools' in kwa:
+ kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
+ except Exception as e:
+ self.raise_tcl_error("Bad tools: %s" % str(e))
- try:
- if 'tools' in kwa:
- kwa['tools'] = [x.strip() for x in kwa['tools'].split(",")]
- except Exception as e:
- self.raiseTclError("Bad tools: %s" % str(e))
+ try:
+ obj = self.collection.get_by_name(str(name))
+ except:
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
- try:
- obj = self.collection.get_by_name(str(name))
- except:
- self.raiseTclError("Could not retrieve object: %s" % name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
- if obj is None:
- self.raiseTclError("Object not found: %s" % name)
+ if not isinstance(obj, FlatCAMExcellon):
+ self.raise_tcl_error('Only Excellon objects can be mill drilled, got %s %s.' % (name, type(obj)))
- if not isinstance(obj, FlatCAMExcellon):
- self.raiseTclError('Only Excellon objects can be mill drilled, got %s %s.' % (name, type(obj)))
+ try:
+ success, msg = obj.generate_milling(**kwa)
+ except Exception as e:
+ self.raise_tcl_error("Operation failed: %s" % str(e))
- try:
- success, msg = obj.generate_milling(**kwa)
- except Exception as e:
- self.raiseTclError("Operation failed: %s" % str(e))
+ if not success:
+ self.raise_tcl_error(msg)
- if not success:
- self.raiseTclError(msg)
-
- return 'Ok'
+ except Exception as unknown:
+ self.raise_tcl_unknown_error(unknown)
def exteriors(name=None, *args):
'''
@@ -2872,46 +2987,49 @@ class App(QtCore.QObject):
:param args: array of arguments
:return: "Ok" if completed without errors
'''
- a, kwa = h(*args)
- types = {'outname': str}
- if name is None:
- self.raiseTclError('Argument name is missing.')
+ try:
+ a, kwa = h(*args)
+ types = {'outname': str}
+
+ if name is None:
+ self.raise_tcl_error('Argument name is missing.')
+
+ for key in kwa:
+ if key not in types:
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ kwa[key] = types[key](kwa[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
- for key in kwa:
- if key not in types:
- self.raiseTclError('Unknown parameter: %s' % key)
try:
- kwa[key] = types[key](kwa[key])
- except Exception, e:
- self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+ obj = self.collection.get_by_name(str(name))
+ except:
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
- try:
- obj = self.collection.get_by_name(str(name))
- except:
- self.raiseTclError("Could not retrieve object: %s" % name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
- if obj is None:
- self.raiseTclError("Object not found: %s" % name)
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
- if not isinstance(obj, Geometry):
- self.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+ def geo_init(geo_obj, app_obj):
+ geo_obj.solid_geometry = obj_exteriors
- def geo_init(geo_obj, app_obj):
- geo_obj.solid_geometry = obj_exteriors
+ if 'outname' in kwa:
+ outname = kwa['outname']
+ else:
+ outname = name + ".exteriors"
- if 'outname' in kwa:
- outname = kwa['outname']
- else:
- outname = name + ".exteriors"
+ try:
+ obj_exteriors = obj.get_exteriors()
+ self.new_object('geometry', outname, geo_init)
+ except Exception as e:
+ self.raise_tcl_error("Failed: %s" % str(e))
- try:
- obj_exteriors = obj.get_exteriors()
- self.new_object('geometry', outname, geo_init)
- except Exception as e:
- self.raiseTclError("Failed: %s" % str(e))
-
- return 'Ok'
+ except Exception as unknown:
+ self.raise_tcl_unknown_error(unknown)
def interiors(name=None, *args):
'''
@@ -2920,46 +3038,49 @@ class App(QtCore.QObject):
:param args: array of arguments
:return: "Ok" if completed without errors
'''
- a, kwa = h(*args)
- types = {'outname': str}
- for key in kwa:
- if key not in types:
- self.raiseTclError('Unknown parameter: %s' % key)
+ try:
+ a, kwa = h(*args)
+ types = {'outname': str}
+
+ for key in kwa:
+ if key not in types:
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ kwa[key] = types[key](kwa[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
+
+ if name is None:
+ self.raise_tcl_error('Argument name is missing.')
+
try:
- kwa[key] = types[key](kwa[key])
- except Exception, e:
- self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+ obj = self.collection.get_by_name(str(name))
+ except:
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
- if name is None:
- self.raiseTclError('Argument name is missing.')
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
- try:
- obj = self.collection.get_by_name(str(name))
- except:
- self.raiseTclError("Could not retrieve object: %s" % name)
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
- if obj is None:
- self.raiseTclError("Object not found: %s" % name)
+ def geo_init(geo_obj, app_obj):
+ geo_obj.solid_geometry = obj_interiors
- if not isinstance(obj, Geometry):
- self.raiseTclError('Expected Geometry, got %s %s.' % (name, type(obj)))
+ if 'outname' in kwa:
+ outname = kwa['outname']
+ else:
+ outname = name + ".interiors"
- def geo_init(geo_obj, app_obj):
- geo_obj.solid_geometry = obj_interiors
+ try:
+ obj_interiors = obj.get_interiors()
+ self.new_object('geometry', outname, geo_init)
+ except Exception as e:
+ self.raise_tcl_error("Failed: %s" % str(e))
- if 'outname' in kwa:
- outname = kwa['outname']
- else:
- outname = name + ".interiors"
-
- try:
- obj_interiors = obj.get_interiors()
- self.new_object('geometry', outname, geo_init)
- except Exception as e:
- self.raiseTclError("Failed: %s" % str(e))
-
- return 'Ok'
+ except Exception as unknown:
+ self.raise_tcl_unknown_error(unknown)
def isolate(name=None, *args):
'''
@@ -2977,29 +3098,29 @@ class App(QtCore.QObject):
for key in kwa:
if key not in types:
- self.raiseTclError('Unknown parameter: %s' % key)
+ self.raise_tcl_error('Unknown parameter: %s' % key)
try:
kwa[key] = types[key](kwa[key])
except Exception, e:
- self.raiseTclError("Cannot cast argument '%s' to type %s." % (key, types[key]))
+ self.raise_tcl_error("Cannot cast argument '%s' to type %s." % (key, types[key]))
try:
obj = self.collection.get_by_name(str(name))
except:
- self.raiseTclError("Could not retrieve object: %s" % name)
+ self.raise_tcl_error("Could not retrieve object: %s" % name)
if obj is None:
- self.raiseTclError("Object not found: %s" % name)
+ self.raise_tcl_error("Object not found: %s" % name)
assert isinstance(obj, FlatCAMGerber), \
"Expected a FlatCAMGerber, got %s" % type(obj)
if not isinstance(obj, FlatCAMGerber):
- self.raiseTclError('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
+ self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
try:
obj.isolate(**kwa)
except Exception, e:
- self.raiseTclError("Operation failed: %s" % str(e))
+ self.raise_tcl_error("Operation failed: %s" % str(e))
return 'Ok'
@@ -3390,11 +3511,11 @@ class App(QtCore.QObject):
Test it like this:
if name is None:
- self.raiseTclError('Argument name is missing.')
+ self.raise_tcl_error('Argument name is missing.')
- When error ocurre, always use raiseTclError, never return "sometext" on error,
+ When error ocurre, always use raise_tcl_error, never return "sometext" on error,
otherwise we will miss it and processing will silently continue.
- Method raiseTclError pass error into TCL interpreter, then raise python exception,
+ Method raise_tcl_error pass error into TCL interpreter, then raise python exception,
which is catched in exec_command and displayed in TCL shell console with red background.
Error in console is displayed with TCL trace.
@@ -3776,6 +3897,9 @@ class App(QtCore.QObject):
}
}
+ #import/overwrite tcl commands as objects of TclCommand descendants
+ tclCommands.register_all_commands(self, commands)
+
# Add commands to the tcl interpreter
for cmd in commands:
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
diff --git a/FlatCAMObj.py b/FlatCAMObj.py
index b8315fc5..b4756920 100644
--- a/FlatCAMObj.py
+++ b/FlatCAMObj.py
@@ -1040,6 +1040,10 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.app.inform.emit("Saved to: " + filename)
+ def get_gcode(self, preamble='', postamble=''):
+ #we need this to beable get_gcode separatelly for shell command export_code
+ return preamble + '\n' + self.gcode + "\n" + postamble
+
def on_plot_cb_click(self, *args):
if self.muted_ui:
return
@@ -1243,7 +1247,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
outname=None,
spindlespeed=None,
multidepth=None,
- depthperpass=None):
+ depthperpass=None,
+ use_thread=True):
"""
Creates a CNCJob out of this Geometry object. The actual
work is done by the target FlatCAMCNCjob object's
@@ -1304,18 +1309,22 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
app_obj.progress.emit(80)
- # To be run in separate thread
- def job_thread(app_obj):
- with self.app.proc_container.new("Generating CNC Job."):
- app_obj.new_object("cncjob", outname, job_init)
- app_obj.inform.emit("CNCjob created: %s" % outname)
- app_obj.progress.emit(100)
- # Create a promise with the name
- self.app.collection.promise(outname)
+ if use_thread:
+ # To be run in separate thread
+ def job_thread(app_obj):
+ with self.app.proc_container.new("Generating CNC Job."):
+ app_obj.new_object("cncjob", outname, job_init)
+ app_obj.inform.emit("CNCjob created: %s" % outname)
+ app_obj.progress.emit(100)
- # Send to worker
- self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+ # Create a promise with the name
+ self.app.collection.promise(outname)
+
+ # Send to worker
+ self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+ else:
+ self.app.new_object("cncjob", outname, job_init)
def on_plot_cb_click(self, *args): # TODO: args not needed
if self.muted_ui:
diff --git a/FlatCAMWorker.py b/FlatCAMWorker.py
index e97d9d11..8c13f4b1 100644
--- a/FlatCAMWorker.py
+++ b/FlatCAMWorker.py
@@ -1,5 +1,4 @@
from PyQt4 import QtCore
-#import FlatCAMApp
class Worker(QtCore.QObject):
@@ -8,15 +7,34 @@ class Worker(QtCore.QObject):
in a single independent thread.
"""
+ # avoid multiple tests for debug availability
+ pydevd_failed = False
+
def __init__(self, app, name=None):
super(Worker, self).__init__()
self.app = app
self.name = name
+ def allow_debug(self):
+ """
+ allow debuging/breakpoints in this threads
+ should work from PyCharm and PyDev
+ :return:
+ """
+
+ if not self.pydevd_failed:
+ try:
+ import pydevd
+ pydevd.settrace(suspend=False, trace_only_current_thread=True)
+ except ImportError:
+ self.pydevd_failed=True
+
def run(self):
self.app.log.debug("Worker Started!")
+ self.allow_debug()
+
# Tasks are queued in the event listener.
self.app.worker_task.connect(self.do_worker_task)
@@ -24,10 +42,10 @@ class Worker(QtCore.QObject):
self.app.log.debug("Running task: %s" % str(task))
- # 'worker_name' property of task allows to target
- # specific worker.
+ self.allow_debug()
+
if ('worker_name' in task and task['worker_name'] == self.name) or \
- ('worker_name' not in task and self.name is None):
+ ('worker_name' not in task and self.name is None):
try:
task['fcn'](*task['params'])
@@ -37,5 +55,4 @@ class Worker(QtCore.QObject):
return
- # FlatCAMApp.App.log.debug("Task ignored.")
- self.app.log.debug("Task ignored.")
\ No newline at end of file
+ self.app.log.debug("Task ignored.")
diff --git a/camlib.py b/camlib.py
index 23bf1bcf..7c0cd11a 100644
--- a/camlib.py
+++ b/camlib.py
@@ -136,6 +136,27 @@ class Geometry(object):
log.error("Failed to run union on polygons.")
raise
+ def add_polyline(self, points):
+ """
+ Adds a polyline to the object (by union)
+
+ :param points: The vertices of the polyline.
+ :return: None
+ """
+ if self.solid_geometry is None:
+ self.solid_geometry = []
+
+ if type(self.solid_geometry) is list:
+ self.solid_geometry.append(LineString(points))
+ return
+
+ try:
+ self.solid_geometry = self.solid_geometry.union(LineString(points))
+ except:
+ #print "Failed to run union on polygons."
+ log.error("Failed to run union on polylines.")
+ raise
+
def subtract_polygon(self, points):
"""
Subtract polygon from the given object. This only operates on the paths in the original geometry, i.e. it converts polygons into paths.
@@ -2756,7 +2777,7 @@ class CNCjob(Geometry):
# so we actually are sorting the tools by diameter
sorted_tools = sorted(exobj.tools.items(), key = lambda x: x[1])
if tools == "all":
- tools = str([i[0] for i in sorted_tools]) # we get a string of ordered tools
+ tools = [i[0] for i in sorted_tools] # we get a array of ordered tools
log.debug("Tools 'all' and sorted are: %s" % str(tools))
else:
selected_tools = [x.strip() for x in tools.split(",")] # we strip spaces and also separate the tools by ','
@@ -2797,24 +2818,26 @@ class CNCjob(Geometry):
for tool in tools:
- # Tool change sequence (optional)
- if toolchange:
- gcode += "G00 Z%.4f\n" % toolchangez
- gcode += "T%d\n" % int(tool) # Indicate tool slot (for automatic tool changer)
- gcode += "M5\n" # Spindle Stop
- gcode += "M6\n" # Tool change
- gcode += "(MSG, Change to tool dia=%.4f)\n" % exobj.tools[tool]["C"]
- gcode += "M0\n" # Temporary machine stop
- if self.spindlespeed is not None:
- gcode += "M03 S%d\n" % int(self.spindlespeed) # Spindle start with configured speed
- else:
- gcode += "M03\n" # Spindle start
+ # only if tool have some points, otherwise thre may be error and this part is useless
+ if tool in points:
+ # Tool change sequence (optional)
+ if toolchange:
+ gcode += "G00 Z%.4f\n" % toolchangez
+ gcode += "T%d\n" % int(tool) # Indicate tool slot (for automatic tool changer)
+ gcode += "M5\n" # Spindle Stop
+ gcode += "M6\n" # Tool change
+ gcode += "(MSG, Change to tool dia=%.4f)\n" % exobj.tools[tool]["C"]
+ gcode += "M0\n" # Temporary machine stop
+ if self.spindlespeed is not None:
+ gcode += "M03 S%d\n" % int(self.spindlespeed) # Spindle start with configured speed
+ else:
+ gcode += "M03\n" # Spindle start
- # Drillling!
- for point in points[tool]:
- x, y = point.coords.xy
- gcode += t % (x[0], y[0])
- gcode += down + up
+ # Drillling!
+ for point in points[tool]:
+ x, y = point.coords.xy
+ gcode += t % (x[0], y[0])
+ gcode += down + up
gcode += t % (0, 0)
gcode += "M05\n" # Spindle stop
diff --git a/camlib.pyc b/camlib.pyc
index c70f7cec..7f788a0a 100644
Binary files a/camlib.pyc and b/camlib.pyc differ
diff --git a/descartes/__init__.pyc b/descartes/__init__.pyc
index 2433c763..8fec3939 100644
Binary files a/descartes/__init__.pyc and b/descartes/__init__.pyc differ
diff --git a/descartes/patch.pyc b/descartes/patch.pyc
index c8b49633..ca587af8 100644
Binary files a/descartes/patch.pyc and b/descartes/patch.pyc differ
diff --git a/tclCommands/TclCommand.py b/tclCommands/TclCommand.py
new file mode 100644
index 00000000..b93ec752
--- /dev/null
+++ b/tclCommands/TclCommand.py
@@ -0,0 +1,394 @@
+import sys
+import re
+import FlatCAMApp
+import abc
+import collections
+from PyQt4 import QtCore
+from contextlib import contextmanager
+
+
+class TclCommand(object):
+
+ # FlatCAMApp
+ app = None
+
+ # logger
+ log = None
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = []
+
+ # dictionary of types from Tcl command, needs to be ordered
+ # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
+ option_types = collections.OrderedDict()
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ # OrderedDict should be like collections.OrderedDict([(key,value),(key2,value2)])
+ help = {
+ 'main': "undefined help.",
+ 'args': collections.OrderedDict([
+ ('argumentname', 'undefined help.'),
+ ('optionname', 'undefined help.')
+ ]),
+ 'examples': []
+ }
+
+ # original incoming arguments into command
+ original_args = None
+
+ def __init__(self, app):
+ self.app = app
+ if self.app is None:
+ raise TypeError('Expected app to be FlatCAMApp instance.')
+ if not isinstance(self.app, FlatCAMApp.App):
+ raise TypeError('Expected FlatCAMApp, got %s.' % type(app))
+ self.log = self.app.log
+
+ def raise_tcl_error(self, text):
+ """
+ this method pass exception from python into TCL as error, so we get stacktrace and reason
+ this is only redirect to self.app.raise_tcl_error
+ :param text: text of error
+ :return: none
+ """
+
+ self.app.raise_tcl_error(text)
+
+ def get_current_command(self):
+ """
+ get current command, we are not able to get it from TCL we have to reconstruct it
+ :return: current command
+ """
+ command_string = []
+ command_string.append(self.aliases[0])
+ if self.original_args is not None:
+ for arg in self.original_args:
+ command_string.append(arg)
+ return " ".join(command_string)
+
+ def get_decorated_help(self):
+ """
+ Decorate help for TCL console output.
+
+ :return: decorated help from structure
+ """
+
+ def get_decorated_command(alias_name):
+ command_string = []
+ for arg_key, arg_type in self.help['args'].items():
+ command_string.append(get_decorated_argument(arg_key, arg_type, True))
+ return "> " + alias_name + " " + " ".join(command_string)
+
+ def get_decorated_argument(help_key, help_text, in_command=False):
+ option_symbol = ''
+ if help_key in self.arg_names:
+ arg_type = self.arg_names[help_key]
+ type_name = str(arg_type.__name__)
+ in_command_name = "<" + type_name + ">"
+ elif help_key in self.option_types:
+ option_symbol = '-'
+ arg_type = self.option_types[help_key]
+ type_name = str(arg_type.__name__)
+ in_command_name = option_symbol + help_key + " <" + type_name + ">"
+ else:
+ option_symbol = ''
+ type_name = '?'
+ in_command_name = option_symbol + help_key + " <" + type_name + ">"
+
+ if in_command:
+ if help_key in self.required:
+ return in_command_name
+ else:
+ return '[' + in_command_name + "]"
+ else:
+ if help_key in self.required:
+ return "\t" + option_symbol + help_key + " <" + type_name + ">: " + help_text
+ else:
+ return "\t[" + option_symbol + help_key + " <" + type_name + ">: " + help_text + "]"
+
+ def get_decorated_example(example_item):
+ return "> "+example_item
+
+ help_string = [self.help['main']]
+ for alias in self.aliases:
+ help_string.append(get_decorated_command(alias))
+
+ for key, value in self.help['args'].items():
+ help_string.append(get_decorated_argument(key, value))
+
+ # timeout is unique for signaled commands (this is not best oop practice, but much easier for now)
+ if isinstance(self, TclCommandSignaled):
+ help_string.append("\t[-timeout : Max wait for job timeout before error.]")
+
+ for example in self.help['examples']:
+ help_string.append(get_decorated_example(example))
+
+ return "\n".join(help_string)
+
+ @staticmethod
+ def parse_arguments(args):
+ """
+ Pre-processes arguments to detect '-keyword value' pairs into dictionary
+ and standalone parameters into list.
+
+ This is copy from FlatCAMApp.setup_shell().h() just for accessibility,
+ original should be removed after all commands will be converted
+
+ :param args: arguments from tcl to parse
+ :return: arguments, options
+ """
+
+ options = {}
+ arguments = []
+ n = len(args)
+ name = None
+ for i in range(n):
+ match = re.search(r'^-([a-zA-Z].*)', args[i])
+ if match:
+ assert name is None
+ name = match.group(1)
+ continue
+
+ if name is None:
+ arguments.append(args[i])
+ else:
+ options[name] = args[i]
+ name = None
+
+ return arguments, options
+
+ def check_args(self, args):
+ """
+ Check arguments and options for right types
+
+ :param args: arguments from tcl to check
+ :return: named_args, unnamed_args
+ """
+
+ arguments, options = self.parse_arguments(args)
+
+ named_args = {}
+ unnamed_args = []
+
+ # check arguments
+ idx = 0
+ arg_names_items = self.arg_names.items()
+ for argument in arguments:
+ if len(self.arg_names) > idx:
+ key, arg_type = arg_names_items[idx]
+ try:
+ named_args[key] = arg_type(argument)
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast named argument '%s' to type %s with exception '%s'."
+ % (key, arg_type, str(e)))
+ else:
+ unnamed_args.append(argument)
+ idx += 1
+
+ # check options
+ for key in options:
+ if key not in self.option_types and key != 'timeout':
+ self.raise_tcl_error('Unknown parameter: %s' % key)
+ try:
+ if key != 'timeout':
+ named_args[key] = self.option_types[key](options[key])
+ else:
+ named_args[key] = int(options[key])
+ except Exception, e:
+ self.raise_tcl_error("Cannot cast argument '-%s' to type '%s' with exception '%s'."
+ % (key, self.option_types[key], str(e)))
+
+ # check required arguments
+ for key in self.required:
+ if key not in named_args:
+ self.raise_tcl_error("Missing required argument '%s'." % key)
+
+ return named_args, unnamed_args
+
+
+ def raise_tcl_unknown_error(self, unknownException):
+ """
+ raise Exception if is different type than TclErrorException
+ this is here mainly to show unknown errors inside TCL shell console
+ :param unknownException:
+ :return:
+ """
+
+ #if not isinstance(unknownException, self.TclErrorException):
+ # self.raise_tcl_error("Unknown error: %s" % str(unknownException))
+ #else:
+ raise unknownException
+
+ def raise_tcl_error(self, text):
+ """
+ this method pass exception from python into TCL as error, so we get stacktrace and reason
+ :param text: text of error
+ :return: raise exception
+ """
+
+ # becouse of signaling we cannot call error to TCL from here but when task is finished
+ # also nonsiglaned arwe handled here to better exception handling and diplay after command is finished
+ raise self.app.TclErrorException(text)
+
+ def execute_wrapper(self, *args):
+ """
+ Command which is called by tcl console when current commands aliases are hit.
+ Main catch(except) is implemented here.
+ This method should be reimplemented only when initial checking sequence differs
+
+ :param args: arguments passed from tcl command console
+ :return: None, output text or exception
+ """
+
+ #self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
+
+ try:
+ self.log.debug("TCL command '%s' executed." % str(self.__class__))
+ self.original_args=args
+ args, unnamed_args = self.check_args(args)
+ return self.execute(args, unnamed_args)
+ except Exception as unknown:
+ error_info=sys.exc_info()
+ self.log.error("TCL command '%s' failed." % str(self))
+ self.app.display_tcl_error(unknown, error_info)
+ self.raise_tcl_unknown_error(unknown)
+
+ @abc.abstractmethod
+ def execute(self, args, unnamed_args):
+ """
+ Direct execute of command, this method should be implemented in each descendant.
+ No main catch should be implemented here.
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None, output text or exception
+ """
+
+ raise NotImplementedError("Please Implement this method")
+
+class TclCommandSignaled(TclCommand):
+ """
+ !!! I left it here only for demonstration !!!
+ Go to TclCommandCncjob and into class definition put
+ class TclCommandCncjob(TclCommand.TclCommandSignaled):
+ also change
+ obj.generatecncjob(use_thread = False, **args)
+ to
+ obj.generatecncjob(use_thread = True, **args)
+
+
+ This class is child of TclCommand and is used for commands which create new objects
+ it handles all neccessary stuff about blocking and passing exeptions
+ """
+
+ output = None
+
+ def execute_call(self, args, unnamed_args):
+
+ try:
+ self.output = None
+ self.error=None
+ self.error_info=None
+ self.output = self.execute(args, unnamed_args)
+ except Exception as unknown:
+ self.error_info = sys.exc_info()
+ self.error=unknown
+ finally:
+ self.app.shell_command_finished.emit(self)
+
+ def execute_wrapper(self, *args):
+ """
+ Command which is called by tcl console when current commands aliases are hit.
+ Main catch(except) is implemented here.
+ This method should be reimplemented only when initial checking sequence differs
+
+ :param args: arguments passed from tcl command console
+ :return: None, output text or exception
+ """
+
+ @contextmanager
+ def wait_signal(signal, timeout=300000):
+ """Block loop until signal emitted, or timeout (ms) elapses."""
+ loop = QtCore.QEventLoop()
+
+ # Normal termination
+ signal.connect(loop.quit)
+
+ # Termination by exception in thread
+ self.app.thread_exception.connect(loop.quit)
+
+ status = {'timed_out': False}
+
+ def report_quit():
+ status['timed_out'] = True
+ loop.quit()
+
+ yield
+
+ # Temporarily change how exceptions are managed.
+ oeh = sys.excepthook
+ ex = []
+
+ def except_hook(type_, value, traceback_):
+ ex.append(value)
+ oeh(type_, value, traceback_)
+ sys.excepthook = except_hook
+
+ # Terminate on timeout
+ if timeout is not None:
+ QtCore.QTimer.singleShot(timeout, report_quit)
+
+ # Block
+ loop.exec_()
+
+ # Restore exception management
+ sys.excepthook = oeh
+ if ex:
+ raise ex[0]
+
+ if status['timed_out']:
+ self.app.raise_tcl_unknown_error("Operation timed outed! Consider increasing option '-timeout ' for command or 'set_sys background_timeout '.")
+
+ try:
+ self.log.debug("TCL command '%s' executed." % str(self.__class__))
+ self.original_args=args
+ args, unnamed_args = self.check_args(args)
+ if 'timeout' in args:
+ passed_timeout=args['timeout']
+ del args['timeout']
+ else:
+ passed_timeout= self.app.defaults['background_timeout']
+
+ # set detail for processing, it will be there until next open or close
+ self.app.shell.open_proccessing(self.get_current_command())
+
+ def handle_finished(obj):
+ self.app.shell_command_finished.disconnect(handle_finished)
+ if self.error is not None:
+ self.raise_tcl_unknown_error(self.error)
+
+ self.app.shell_command_finished.connect(handle_finished)
+
+ with wait_signal(self.app.shell_command_finished, passed_timeout):
+ # every TclCommandNewObject ancestor support timeout as parameter,
+ # but it does not mean anything for child itself
+ # when operation will be really long is good to set it higher then defqault 30s
+ self.app.worker_task.emit({'fcn': self.execute_call, 'params': [args, unnamed_args]})
+
+ return self.output
+
+ except Exception as unknown:
+ error_info=sys.exc_info()
+ self.log.error("TCL command '%s' failed." % str(self))
+ self.app.display_tcl_error(unknown, error_info)
+ self.raise_tcl_unknown_error(unknown)
\ No newline at end of file
diff --git a/tclCommands/TclCommandAddPolygon.py b/tclCommands/TclCommandAddPolygon.py
new file mode 100644
index 00000000..c9e35078
--- /dev/null
+++ b/tclCommands/TclCommandAddPolygon.py
@@ -0,0 +1,61 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandAddPolygon(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to create a polygon in the given Geometry object
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['add_polygon', 'add_poly']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict()
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Creates a polygon in the given Geometry object.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the Geometry object to which to append the polygon.'),
+ ('xi, yi', 'Coordinates of points in the polygon.')
+ ]),
+ 'examples': [
+ 'add_polygon [x3 y3 [...]]'
+ ]
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+ if len(unnamed_args) % 2 != 0:
+ self.raise_tcl_error("Incomplete coordinates.")
+
+ points = [[float(unnamed_args[2*i]), float(unnamed_args[2*i+1])] for i in range(len(unnamed_args)/2)]
+
+ obj.add_polygon(points)
+ obj.plot()
diff --git a/tclCommands/TclCommandAddPolyline.py b/tclCommands/TclCommandAddPolyline.py
new file mode 100644
index 00000000..3c994760
--- /dev/null
+++ b/tclCommands/TclCommandAddPolyline.py
@@ -0,0 +1,61 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandAddPolyline(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to create a polyline in the given Geometry object
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['add_polyline']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict()
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Creates a polyline in the given Geometry object.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the Geometry object to which to append the polyline.'),
+ ('xi, yi', 'Coordinates of points in the polyline.')
+ ]),
+ 'examples': [
+ 'add_polyline [x3 y3 [...]]'
+ ]
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+ if len(unnamed_args) % 2 != 0:
+ self.raise_tcl_error("Incomplete coordinates.")
+
+ points = [[float(unnamed_args[2*i]), float(unnamed_args[2*i+1])] for i in range(len(unnamed_args)/2)]
+
+ obj.add_polyline(points)
+ obj.plot()
diff --git a/tclCommands/TclCommandCncjob.py b/tclCommands/TclCommandCncjob.py
new file mode 100644
index 00000000..e6d84de3
--- /dev/null
+++ b/tclCommands/TclCommandCncjob.py
@@ -0,0 +1,80 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandCncjob(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to Generates a CNC Job from a Geometry Object.
+
+ example:
+ set_sys units MM
+ new
+ open_gerber tests/gerber_files/simple1.gbr -outname margin
+ isolate margin -dia 3
+ cncjob margin_iso
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['cncjob']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('z_cut',float),
+ ('z_move',float),
+ ('feedrate',float),
+ ('tooldia',float),
+ ('spindlespeed',int),
+ ('multidepth',bool),
+ ('depthperpass',float),
+ ('outname',str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Generates a CNC Job from a Geometry Object.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source object.'),
+ ('z_cut', 'Z-axis cutting position.'),
+ ('z_move', 'Z-axis moving position.'),
+ ('feedrate', 'Moving speed when cutting.'),
+ ('tooldia', 'Tool diameter to show on screen.'),
+ ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
+ ('multidepth', 'Use or not multidepth cnccut.'),
+ ('depthperpass', 'Height of one layer for multidepth.'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ if 'outname' not in args:
+ args['outname'] = name + "_cnc"
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, FlatCAMGeometry):
+ self.raise_tcl_error('Expected FlatCAMGeometry, got %s %s.' % (name, type(obj)))
+
+ del args['name']
+ obj.generatecncjob(use_thread = False, **args)
\ No newline at end of file
diff --git a/tclCommands/TclCommandDrillcncjob.py b/tclCommands/TclCommandDrillcncjob.py
new file mode 100644
index 00000000..783b6599
--- /dev/null
+++ b/tclCommands/TclCommandDrillcncjob.py
@@ -0,0 +1,81 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandDrillcncjob(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to Generates a Drill CNC Job from a Excellon Object.
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['drillcncjob']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('tools',str),
+ ('drillz',float),
+ ('travelz',float),
+ ('feedrate',float),
+ ('spindlespeed',int),
+ ('toolchange',bool),
+ ('outname',str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Generates a Drill CNC Job from a Excellon Object.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source object.'),
+ ('tools', 'Comma separated indexes of tools (example: 1,3 or 2) or select all if not specified.'),
+ ('drillz', 'Drill depth into material (example: -2.0).'),
+ ('travelz', 'Travel distance above material (example: 2.0).'),
+ ('feedrate', 'Drilling feed rate.'),
+ ('spindlespeed', 'Speed of the spindle in rpm (example: 4000).'),
+ ('toolchange', 'Enable tool changes (example: True).'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ if 'outname' not in args:
+ args['outname'] = name + "_cnc"
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, FlatCAMExcellon):
+ self.raise_tcl_error('Expected FlatCAMExcellon, got %s %s.' % (name, type(obj)))
+
+ def job_init(job_obj, app):
+ job_obj.z_cut = args["drillz"]
+ job_obj.z_move = args["travelz"]
+ job_obj.feedrate = args["feedrate"]
+ job_obj.spindlespeed = args["spindlespeed"] if "spindlespeed" in args else None
+ toolchange = True if "toolchange" in args and args["toolchange"] == 1 else False
+ tools = args["tools"] if "tools" in args else 'all'
+ job_obj.generate_from_excellon_by_tool(obj, tools, toolchange)
+ job_obj.gcode_parse()
+ job_obj.create_geometry()
+
+ self.app.new_object("cncjob", args['outname'], job_init)
diff --git a/tclCommands/TclCommandExportGcode.py b/tclCommands/TclCommandExportGcode.py
new file mode 100644
index 00000000..feecd870
--- /dev/null
+++ b/tclCommands/TclCommandExportGcode.py
@@ -0,0 +1,79 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandExportGcode(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to export gcode as tcl output for "set X [export_gcode ...]"
+
+ Requires name to be available. It might still be in the
+ making at the time this function is called, so check for
+ promises and send to background if there are promises.
+
+
+ this export may be catched by tcl and past as preable to another export_gcode or write_gcode
+ this can be used to join GCODES
+
+ example:
+ set_sys units MM
+ new
+ open_gerber tests/gerber_files/simple1.gbr -outname margin
+ isolate margin -dia 3
+ cncjob margin_iso
+ cncjob margin_iso
+ set EXPORT [export_gcode margin_iso_cnc]
+ write_gcode margin_iso_cnc_1 /tmp/file.gcode ${EXPORT}
+
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['export_gcode']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str),
+ ('preamble', str),
+ ('postamble', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict()
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Export gcode into console output.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source Geometry object.'),
+ ('preamble', 'Prepend GCODE.'),
+ ('postamble', 'Append GCODE.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, CNCjob):
+ self.raise_tcl_error('Expected CNCjob, got %s %s.' % (name, type(obj)))
+
+ if self.app.collection.has_promises():
+ self.raise_tcl_error('!!!Promises exists, but should not here!!!')
+
+ del args['name']
+ return obj.get_gcode(**args)
diff --git a/tclCommands/TclCommandExteriors.py b/tclCommands/TclCommandExteriors.py
new file mode 100644
index 00000000..ac69e7cb
--- /dev/null
+++ b/tclCommands/TclCommandExteriors.py
@@ -0,0 +1,64 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandExteriors(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to get exteriors of polygons
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['exteriors', 'ext']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('outname', str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Get exteriors of polygons.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source Geometry object.'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ if 'outname' in args:
+ outname = args['outname']
+ else:
+ outname = name + "_exteriors"
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+ def geo_init(geo_obj, app_obj):
+ geo_obj.solid_geometry = obj_exteriors
+
+ obj_exteriors = obj.get_exteriors()
+ self.app.new_object('geometry', outname, geo_init)
diff --git a/tclCommands/TclCommandInteriors.py b/tclCommands/TclCommandInteriors.py
new file mode 100644
index 00000000..61bfe9f0
--- /dev/null
+++ b/tclCommands/TclCommandInteriors.py
@@ -0,0 +1,64 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandInteriors(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to get interiors of polygons
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['interiors']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('outname', str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Get interiors of polygons.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source Geometry object.'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ if 'outname' in args:
+ outname = args['outname']
+ else:
+ outname = name + "_interiors"
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, Geometry):
+ self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+
+ def geo_init(geo_obj, app_obj):
+ geo_obj.solid_geometry = obj_exteriors
+
+ obj_exteriors = obj.get_interiors()
+ self.app.new_object('geometry', outname, geo_init)
diff --git a/tclCommands/TclCommandIsolate.py b/tclCommands/TclCommandIsolate.py
new file mode 100644
index 00000000..8c51f21e
--- /dev/null
+++ b/tclCommands/TclCommandIsolate.py
@@ -0,0 +1,79 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandIsolate(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to Creates isolation routing geometry for the given Gerber.
+
+ example:
+ set_sys units MM
+ new
+ open_gerber tests/gerber_files/simple1.gbr -outname margin
+ isolate margin -dia 3
+ cncjob margin_iso
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['isolate']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('name', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('dia',float),
+ ('passes',int),
+ ('overlap',float),
+ ('combine',int),
+ ('outname',str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['name']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Creates isolation routing geometry for the given Gerber.",
+ 'args': collections.OrderedDict([
+ ('name', 'Name of the source object.'),
+ ('dia', 'Tool diameter.'),
+ ('passes', 'Passes of tool width.'),
+ ('overlap', 'Fraction of tool diameter to overlap passes.'),
+ ('combine', 'Combine all passes into one geometry.'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ name = args['name']
+
+ if 'outname' not in args:
+ args['outname'] = name + "_iso"
+
+ if 'timeout' in args:
+ timeout = args['timeout']
+ else:
+ timeout = 10000
+
+ obj = self.app.collection.get_by_name(name)
+ if obj is None:
+ self.raise_tcl_error("Object not found: %s" % name)
+
+ if not isinstance(obj, FlatCAMGerber):
+ self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (name, type(obj)))
+
+ del args['name']
+ obj.isolate(**args)
diff --git a/tclCommands/TclCommandNew.py b/tclCommands/TclCommandNew.py
new file mode 100644
index 00000000..db3fe576
--- /dev/null
+++ b/tclCommands/TclCommandNew.py
@@ -0,0 +1,40 @@
+from ObjectCollection import *
+from PyQt4 import QtCore
+import TclCommand
+
+
+class TclCommandNew(TclCommand.TclCommand):
+ """
+ Tcl shell command to starts a new project. Clears objects from memory
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['new']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict()
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict()
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = []
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Starts a new project. Clears objects from memory.",
+ 'args': collections.OrderedDict(),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ self.app.on_file_new()
diff --git a/tclCommands/TclCommandOpenGerber.py b/tclCommands/TclCommandOpenGerber.py
new file mode 100644
index 00000000..a951d8f3
--- /dev/null
+++ b/tclCommands/TclCommandOpenGerber.py
@@ -0,0 +1,95 @@
+from ObjectCollection import *
+import TclCommand
+
+
+class TclCommandOpenGerber(TclCommand.TclCommandSignaled):
+ """
+ Tcl shell command to opens a Gerber file
+ """
+
+ # array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+ aliases = ['open_gerber']
+
+ # dictionary of types from Tcl command, needs to be ordered
+ arg_names = collections.OrderedDict([
+ ('filename', str)
+ ])
+
+ # dictionary of types from Tcl command, needs to be ordered , this is for options like -optionname value
+ option_types = collections.OrderedDict([
+ ('follow', str),
+ ('outname', str)
+ ])
+
+ # array of mandatory options for current Tcl command: required = {'name','outname'}
+ required = ['filename']
+
+ # structured help for current command, args needs to be ordered
+ help = {
+ 'main': "Opens a Gerber file.",
+ 'args': collections.OrderedDict([
+ ('filename', 'Path to file to open.'),
+ ('follow', 'N If 1, does not create polygons, just follows the gerber path.'),
+ ('outname', 'Name of the resulting Geometry object.')
+ ]),
+ 'examples': []
+ }
+
+ def execute(self, args, unnamed_args):
+ """
+ execute current TCL shell command
+
+ :param args: array of known named arguments and options
+ :param unnamed_args: array of other values which were passed into command
+ without -somename and we do not have them in known arg_names
+ :return: None or exception
+ """
+
+ # How the object should be initialized
+ def obj_init(gerber_obj, app_obj):
+
+ if not isinstance(gerber_obj, Geometry):
+ self.raise_tcl_error('Expected FlatCAMGerber, got %s %s.' % (outname, type(gerber_obj)))
+
+ # Opening the file happens here
+ self.app.progress.emit(30)
+ try:
+ gerber_obj.parse_file(filename, follow=follow)
+
+ except IOError:
+ app_obj.inform.emit("[error] Failed to open file: %s " % filename)
+ app_obj.progress.emit(0)
+ self.raise_tcl_error('Failed to open file: %s' % filename)
+
+ except ParseError, e:
+ app_obj.inform.emit("[error] Failed to parse file: %s, %s " % (filename, str(e)))
+ app_obj.progress.emit(0)
+ self.log.error(str(e))
+ raise
+
+ # Further parsing
+ app_obj.progress.emit(70)
+
+ filename = args['filename']
+
+ if 'outname' in args:
+ outname = args['outname']
+ else:
+ outname = filename.split('/')[-1].split('\\')[-1]
+
+ follow = None
+ if 'follow' in args:
+ follow = args['follow']
+
+ with self.app.proc_container.new("Opening Gerber"):
+
+ # Object creation
+ self.app.new_object("gerber", outname, obj_init)
+
+ # Register recent file
+ self.app.file_opened.emit("gerber", filename)
+
+ self.app.progress.emit(100)
+
+ # GUI feedback
+ self.app.inform.emit("Opened: " + filename)
diff --git a/tclCommands/__init__.py b/tclCommands/__init__.py
new file mode 100644
index 00000000..2f733017
--- /dev/null
+++ b/tclCommands/__init__.py
@@ -0,0 +1,52 @@
+import pkgutil
+import sys
+
+# allowed command modules (please append them alphabetically ordered)
+import tclCommands.TclCommandAddPolygon
+import tclCommands.TclCommandAddPolyline
+import tclCommands.TclCommandCncjob
+import tclCommands.TclCommandDrillcncjob
+import tclCommands.TclCommandExportGcode
+import tclCommands.TclCommandExteriors
+import tclCommands.TclCommandInteriors
+import tclCommands.TclCommandIsolate
+import tclCommands.TclCommandNew
+import tclCommands.TclCommandOpenGerber
+
+
+__all__ = []
+
+for loader, name, is_pkg in pkgutil.walk_packages(__path__):
+ module = loader.find_module(name).load_module(name)
+ __all__.append(name)
+
+def register_all_commands(app, commands):
+ """
+ Static method which register all known commands.
+
+ Command should be for now in directory tclCommands and module should start with TCLCommand
+ Class have to follow same name as module.
+
+ we need import all modules in top section:
+ import tclCommands.TclCommandExteriors
+ at this stage we can include only wanted commands with this, auto loading may be implemented in future
+ I have no enough knowledge about python's anatomy. Would be nice to include all classes which are descendant etc.
+
+ :param app: FlatCAMApp
+ :param commands: array of commands which should be modified
+ :return: None
+ """
+
+ tcl_modules = {k: v for k, v in sys.modules.items() if k.startswith('tclCommands.TclCommand')}
+
+ for key, mod in tcl_modules.items():
+ if key != 'tclCommands.TclCommand':
+ class_name = key.split('.')[1]
+ class_type = getattr(mod, class_name)
+ command_instance = class_type(app)
+
+ for alias in command_instance.aliases:
+ commands[alias] = {
+ 'fcn': command_instance.execute_wrapper,
+ 'help': command_instance.get_decorated_help()
+ }
diff --git a/termwidget.py b/termwidget.py
index b2e4fdba..538cc161 100644
--- a/termwidget.py
+++ b/termwidget.py
@@ -4,8 +4,7 @@ Shows intput and output text. Allows to enter commands. Supports history.
"""
import cgi
-
-from PyQt4.QtCore import pyqtSignal
+from PyQt4.QtCore import pyqtSignal, Qt
from PyQt4.QtGui import QColor, QKeySequence, QLineEdit, QPalette, \
QSizePolicy, QTextCursor, QTextEdit, \
QVBoxLayout, QWidget
@@ -83,7 +82,6 @@ class _ExpandableTextEdit(QTextEdit):
# Paste only plain text.
self.insertPlainText(mime_data.text())
-
class TermWidget(QWidget):
"""
Widget wich represents terminal. It only displays text and allows to enter text.
@@ -118,6 +116,34 @@ class TermWidget(QWidget):
self._edit.setFocus()
+ def open_proccessing(self, detail=None):
+ """
+ Open processing and disable using shell commands again until all commands are finished
+
+ :param detail: text detail about what is currently called from TCL to python
+ :return: None
+ """
+
+ self._edit.setTextColor(Qt.white)
+ self._edit.setTextBackgroundColor(Qt.darkGreen)
+ if detail is None:
+ self._edit.setPlainText("...proccessing...")
+ else:
+ self._edit.setPlainText("...proccessing... [%s]" % detail)
+
+ self._edit.setDisabled(True)
+
+ def close_proccessing(self):
+ """
+ Close processing and enable using shell commands again
+ :return:
+ """
+
+ self._edit.setTextColor(Qt.black)
+ self._edit.setTextBackgroundColor(Qt.white)
+ self._edit.setPlainText('')
+ self._edit.setDisabled(False)
+
def _append_to_browser(self, style, text):
"""
Convert text to HTML for inserting it to browser
@@ -225,4 +251,3 @@ class TermWidget(QWidget):
self._historyIndex -= 1
self._edit.setPlainText(self._history[self._historyIndex])
self._edit.moveCursor(QTextCursor.End)
-
diff --git a/tests/gerber_files/detector_contour.gbr b/tests/gerber_files/detector_contour.gbr
new file mode 100644
index 00000000..93adef01
--- /dev/null
+++ b/tests/gerber_files/detector_contour.gbr
@@ -0,0 +1,26 @@
+G04 MADE WITH FRITZING*
+G04 WWW.FRITZING.ORG*
+G04 DOUBLE SIDED*
+G04 HOLES PLATED*
+G04 CONTOUR ON CENTER OF CONTOUR VECTOR*
+%ASAXBY*%
+%FSLAX23Y23*%
+%MOIN*%
+%OFA0B0*%
+%SFA1.0B1.0*%
+%ADD10R,1.771650X1.181100*%
+%ADD11C,0.008000*%
+%ADD10C,0.008*%
+%LNCONTOUR*%
+G90*
+G70*
+G54D10*
+G54D11*
+X4Y1177D02*
+X1768Y1177D01*
+X1768Y4D01*
+X4Y4D01*
+X4Y1177D01*
+D02*
+G04 End of contour*
+M02*
\ No newline at end of file
diff --git a/tests/gerber_files/detector_copper_bottom.gbr b/tests/gerber_files/detector_copper_bottom.gbr
new file mode 100644
index 00000000..d3bca481
--- /dev/null
+++ b/tests/gerber_files/detector_copper_bottom.gbr
@@ -0,0 +1,2146 @@
+G04 MADE WITH FRITZING*
+G04 WWW.FRITZING.ORG*
+G04 DOUBLE SIDED*
+G04 HOLES PLATED*
+G04 CONTOUR ON CENTER OF CONTOUR VECTOR*
+%ASAXBY*%
+%FSLAX23Y23*%
+%MOIN*%
+%OFA0B0*%
+%SFA1.0B1.0*%
+%ADD10C,0.075000*%
+%ADD11C,0.099055*%
+%ADD12C,0.078740*%
+%ADD13R,0.075000X0.075000*%
+%ADD14C,0.048000*%
+%ADD15C,0.020000*%
+%ADD16R,0.001000X0.001000*%
+%LNCOPPER0*%
+G90*
+G70*
+G54D10*
+X1149Y872D03*
+X1349Y872D03*
+X749Y722D03*
+X749Y522D03*
+X1149Y522D03*
+X1449Y522D03*
+X1149Y422D03*
+X1449Y422D03*
+X1149Y322D03*
+X1449Y322D03*
+X1149Y222D03*
+X1449Y222D03*
+X949Y472D03*
+X949Y72D03*
+G54D11*
+X749Y972D03*
+X599Y972D03*
+X349Y322D03*
+X349Y472D03*
+X349Y672D03*
+X349Y822D03*
+G54D10*
+X699Y122D03*
+X699Y322D03*
+G54D12*
+X699Y222D03*
+X949Y972D03*
+X749Y622D03*
+X1049Y222D03*
+X1249Y872D03*
+G54D13*
+X1149Y872D03*
+X1149Y522D03*
+G54D14*
+X949Y373D02*
+X949Y433D01*
+D02*
+X999Y323D02*
+X949Y373D01*
+D02*
+X1109Y322D02*
+X999Y323D01*
+D02*
+X499Y873D02*
+X1109Y872D01*
+D02*
+X1299Y73D02*
+X989Y72D01*
+D02*
+X1399Y322D02*
+X1349Y272D01*
+D02*
+X1349Y272D02*
+X1349Y122D01*
+D02*
+X1349Y122D02*
+X1299Y73D01*
+D02*
+X1409Y322D02*
+X1399Y322D01*
+D02*
+X909Y72D02*
+X749Y73D01*
+D02*
+X749Y73D02*
+X727Y94D01*
+D02*
+X649Y522D02*
+X709Y522D01*
+D02*
+X599Y473D02*
+X649Y522D01*
+D02*
+X401Y472D02*
+X599Y473D01*
+D02*
+X789Y522D02*
+X899Y522D01*
+D02*
+X709Y722D02*
+X599Y722D01*
+D02*
+X599Y722D02*
+X549Y673D01*
+D02*
+X549Y673D02*
+X401Y672D01*
+D02*
+X1149Y562D02*
+X1149Y833D01*
+D02*
+X499Y972D02*
+X499Y873D01*
+D02*
+X547Y972D02*
+X499Y972D01*
+D02*
+X699Y283D02*
+X699Y260D01*
+D02*
+X749Y562D02*
+X749Y584D01*
+D02*
+X499Y873D02*
+X499Y972D01*
+D02*
+X499Y972D02*
+X547Y972D01*
+D02*
+X401Y823D02*
+X449Y823D01*
+D02*
+X899Y522D02*
+X921Y500D01*
+D02*
+X1309Y872D02*
+X1287Y872D01*
+D02*
+X449Y823D02*
+X499Y873D01*
+D02*
+X1349Y422D02*
+X1349Y833D01*
+D02*
+X1189Y422D02*
+X1349Y422D01*
+D02*
+X1399Y322D02*
+X1409Y322D01*
+D02*
+X1349Y372D02*
+X1399Y322D01*
+D02*
+X1349Y422D02*
+X1349Y372D01*
+D02*
+X1189Y422D02*
+X1349Y422D01*
+D02*
+X801Y972D02*
+X911Y972D01*
+D02*
+X1109Y222D02*
+X1087Y222D01*
+D02*
+X401Y322D02*
+X659Y322D01*
+D02*
+X1399Y972D02*
+X987Y972D01*
+D02*
+X1449Y923D02*
+X1399Y972D01*
+D02*
+X1449Y562D02*
+X1449Y923D01*
+G54D15*
+X776Y695D02*
+X721Y695D01*
+X721Y750D01*
+X776Y750D01*
+X776Y695D01*
+D02*
+X671Y150D02*
+X726Y150D01*
+X726Y95D01*
+X671Y95D01*
+X671Y150D01*
+D02*
+G54D16*
+X766Y1112D02*
+X769Y1112D01*
+X764Y1111D02*
+X771Y1111D01*
+X763Y1110D02*
+X772Y1110D01*
+X762Y1109D02*
+X772Y1109D01*
+X762Y1108D02*
+X773Y1108D01*
+X762Y1107D02*
+X773Y1107D01*
+X762Y1106D02*
+X773Y1106D01*
+X762Y1105D02*
+X773Y1105D01*
+X762Y1104D02*
+X773Y1104D01*
+X762Y1103D02*
+X773Y1103D01*
+X762Y1102D02*
+X773Y1102D01*
+X762Y1101D02*
+X773Y1101D01*
+X762Y1100D02*
+X773Y1100D01*
+X762Y1099D02*
+X773Y1099D01*
+X762Y1098D02*
+X773Y1098D01*
+X762Y1097D02*
+X773Y1097D01*
+X762Y1096D02*
+X773Y1096D01*
+X762Y1095D02*
+X773Y1095D01*
+X762Y1094D02*
+X773Y1094D01*
+X762Y1093D02*
+X773Y1093D01*
+X762Y1092D02*
+X773Y1092D01*
+X762Y1091D02*
+X773Y1091D01*
+X762Y1090D02*
+X773Y1090D01*
+X762Y1089D02*
+X773Y1089D01*
+X566Y1088D02*
+X618Y1088D01*
+X741Y1088D02*
+X793Y1088D01*
+X565Y1087D02*
+X620Y1087D01*
+X740Y1087D02*
+X795Y1087D01*
+X564Y1086D02*
+X621Y1086D01*
+X739Y1086D02*
+X796Y1086D01*
+X563Y1085D02*
+X621Y1085D01*
+X738Y1085D02*
+X796Y1085D01*
+X563Y1084D02*
+X622Y1084D01*
+X738Y1084D02*
+X796Y1084D01*
+X563Y1083D02*
+X622Y1083D01*
+X738Y1083D02*
+X796Y1083D01*
+X563Y1082D02*
+X622Y1082D01*
+X738Y1082D02*
+X796Y1082D01*
+X563Y1081D02*
+X622Y1081D01*
+X738Y1081D02*
+X796Y1081D01*
+X563Y1080D02*
+X622Y1080D01*
+X738Y1080D02*
+X796Y1080D01*
+X563Y1079D02*
+X622Y1079D01*
+X739Y1079D02*
+X795Y1079D01*
+X563Y1078D02*
+X622Y1078D01*
+X739Y1078D02*
+X795Y1078D01*
+X563Y1077D02*
+X622Y1077D01*
+X741Y1077D02*
+X794Y1077D01*
+X563Y1076D02*
+X622Y1076D01*
+X762Y1076D02*
+X773Y1076D01*
+X563Y1075D02*
+X621Y1075D01*
+X762Y1075D02*
+X773Y1075D01*
+X563Y1074D02*
+X621Y1074D01*
+X762Y1074D02*
+X773Y1074D01*
+X564Y1073D02*
+X620Y1073D01*
+X762Y1073D02*
+X773Y1073D01*
+X565Y1072D02*
+X619Y1072D01*
+X762Y1072D02*
+X773Y1072D01*
+X569Y1071D02*
+X615Y1071D01*
+X762Y1071D02*
+X773Y1071D01*
+X762Y1070D02*
+X773Y1070D01*
+X762Y1069D02*
+X773Y1069D01*
+X762Y1068D02*
+X773Y1068D01*
+X762Y1067D02*
+X773Y1067D01*
+X762Y1066D02*
+X773Y1066D01*
+X762Y1065D02*
+X773Y1065D01*
+X762Y1064D02*
+X773Y1064D01*
+X762Y1063D02*
+X773Y1063D01*
+X762Y1062D02*
+X773Y1062D01*
+X762Y1061D02*
+X773Y1061D01*
+X762Y1060D02*
+X773Y1060D01*
+X762Y1059D02*
+X773Y1059D01*
+X762Y1058D02*
+X773Y1058D01*
+X762Y1057D02*
+X773Y1057D01*
+X762Y1056D02*
+X773Y1056D01*
+X763Y1055D02*
+X772Y1055D01*
+X763Y1054D02*
+X771Y1054D01*
+X765Y1053D02*
+X770Y1053D01*
+X1661Y878D02*
+X1697Y878D01*
+X1658Y877D02*
+X1698Y877D01*
+X1656Y876D02*
+X1700Y876D01*
+X1653Y875D02*
+X1701Y875D01*
+X1651Y874D02*
+X1701Y874D01*
+X1648Y873D02*
+X1702Y873D01*
+X1645Y872D02*
+X1702Y872D01*
+X1643Y871D02*
+X1702Y871D01*
+X1640Y870D02*
+X1702Y870D01*
+X1638Y869D02*
+X1703Y869D01*
+X1635Y868D02*
+X1702Y868D01*
+X1633Y867D02*
+X1702Y867D01*
+X1630Y866D02*
+X1702Y866D01*
+X1627Y865D02*
+X1701Y865D01*
+X1625Y864D02*
+X1701Y864D01*
+X1622Y863D02*
+X1700Y863D01*
+X1620Y862D02*
+X1699Y862D01*
+X1617Y861D02*
+X1697Y861D01*
+X1615Y860D02*
+X1664Y860D01*
+X1612Y859D02*
+X1661Y859D01*
+X1609Y858D02*
+X1659Y858D01*
+X1607Y857D02*
+X1656Y857D01*
+X1604Y856D02*
+X1653Y856D01*
+X1602Y855D02*
+X1651Y855D01*
+X1599Y854D02*
+X1648Y854D01*
+X1597Y853D02*
+X1646Y853D01*
+X1594Y852D02*
+X1643Y852D01*
+X1592Y851D02*
+X1641Y851D01*
+X1589Y850D02*
+X1638Y850D01*
+X1586Y849D02*
+X1635Y849D01*
+X1584Y848D02*
+X1633Y848D01*
+X1581Y847D02*
+X1630Y847D01*
+X1579Y846D02*
+X1628Y846D01*
+X1576Y845D02*
+X1625Y845D01*
+X1574Y844D02*
+X1623Y844D01*
+X1571Y843D02*
+X1620Y843D01*
+X1569Y842D02*
+X1618Y842D01*
+X1567Y841D02*
+X1615Y841D01*
+X1566Y840D02*
+X1612Y840D01*
+X1565Y839D02*
+X1610Y839D01*
+X1564Y838D02*
+X1607Y838D01*
+X1564Y837D02*
+X1605Y837D01*
+X1563Y836D02*
+X1602Y836D01*
+X1563Y835D02*
+X1600Y835D01*
+X1563Y834D02*
+X1597Y834D01*
+X1563Y833D02*
+X1599Y833D01*
+X1563Y832D02*
+X1601Y832D01*
+X1564Y831D02*
+X1604Y831D01*
+X1564Y830D02*
+X1606Y830D01*
+X1564Y829D02*
+X1609Y829D01*
+X1565Y828D02*
+X1611Y828D01*
+X1566Y827D02*
+X1614Y827D01*
+X1567Y826D02*
+X1616Y826D01*
+X1569Y825D02*
+X1619Y825D01*
+X1572Y824D02*
+X1622Y824D01*
+X1574Y823D02*
+X1624Y823D01*
+X1577Y822D02*
+X1627Y822D01*
+X1580Y821D02*
+X1629Y821D01*
+X1582Y820D02*
+X1632Y820D01*
+X1585Y819D02*
+X1634Y819D01*
+X1587Y818D02*
+X1637Y818D01*
+X1590Y817D02*
+X1639Y817D01*
+X1592Y816D02*
+X1642Y816D01*
+X1595Y815D02*
+X1645Y815D01*
+X1598Y814D02*
+X1647Y814D01*
+X1600Y813D02*
+X1650Y813D01*
+X1603Y812D02*
+X1652Y812D01*
+X1605Y811D02*
+X1655Y811D01*
+X1608Y810D02*
+X1657Y810D01*
+X1610Y809D02*
+X1660Y809D01*
+X1613Y808D02*
+X1662Y808D01*
+X1616Y807D02*
+X1695Y807D01*
+X1618Y806D02*
+X1698Y806D01*
+X1621Y805D02*
+X1699Y805D01*
+X1623Y804D02*
+X1700Y804D01*
+X1626Y803D02*
+X1701Y803D01*
+X1628Y802D02*
+X1702Y802D01*
+X1631Y801D02*
+X1702Y801D01*
+X1634Y800D02*
+X1702Y800D01*
+X1636Y799D02*
+X1702Y799D01*
+X1639Y798D02*
+X1703Y798D01*
+X1641Y797D02*
+X1702Y797D01*
+X1644Y796D02*
+X1702Y796D01*
+X1646Y795D02*
+X1702Y795D01*
+X1649Y794D02*
+X1702Y794D01*
+X1652Y793D02*
+X1701Y793D01*
+X1654Y792D02*
+X1700Y792D01*
+X1657Y791D02*
+X1699Y791D01*
+X1659Y790D02*
+X1698Y790D01*
+X1662Y789D02*
+X1694Y789D01*
+X191Y786D02*
+X194Y786D01*
+X106Y785D02*
+X117Y785D01*
+X189Y785D02*
+X198Y785D01*
+X104Y784D02*
+X119Y784D01*
+X187Y784D02*
+X200Y784D01*
+X102Y783D02*
+X121Y783D01*
+X186Y783D02*
+X202Y783D01*
+X101Y782D02*
+X122Y782D01*
+X186Y782D02*
+X204Y782D01*
+X100Y781D02*
+X123Y781D01*
+X185Y781D02*
+X205Y781D01*
+X99Y780D02*
+X125Y780D01*
+X185Y780D02*
+X206Y780D01*
+X98Y779D02*
+X126Y779D01*
+X185Y779D02*
+X207Y779D01*
+X97Y778D02*
+X127Y778D01*
+X185Y778D02*
+X208Y778D01*
+X97Y777D02*
+X128Y777D01*
+X185Y777D02*
+X208Y777D01*
+X96Y776D02*
+X130Y776D01*
+X185Y776D02*
+X209Y776D01*
+X96Y775D02*
+X131Y775D01*
+X186Y775D02*
+X210Y775D01*
+X96Y774D02*
+X132Y774D01*
+X186Y774D02*
+X210Y774D01*
+X95Y773D02*
+X134Y773D01*
+X187Y773D02*
+X211Y773D01*
+X95Y772D02*
+X135Y772D01*
+X188Y772D02*
+X211Y772D01*
+X95Y771D02*
+X136Y771D01*
+X191Y771D02*
+X211Y771D01*
+X95Y770D02*
+X109Y770D01*
+X113Y770D02*
+X137Y770D01*
+X195Y770D02*
+X211Y770D01*
+X95Y769D02*
+X109Y769D01*
+X114Y769D02*
+X139Y769D01*
+X196Y769D02*
+X212Y769D01*
+X95Y768D02*
+X109Y768D01*
+X116Y768D02*
+X140Y768D01*
+X197Y768D02*
+X212Y768D01*
+X95Y767D02*
+X109Y767D01*
+X117Y767D02*
+X141Y767D01*
+X197Y767D02*
+X212Y767D01*
+X95Y766D02*
+X109Y766D01*
+X118Y766D02*
+X143Y766D01*
+X198Y766D02*
+X212Y766D01*
+X95Y765D02*
+X109Y765D01*
+X120Y765D02*
+X144Y765D01*
+X198Y765D02*
+X212Y765D01*
+X95Y764D02*
+X109Y764D01*
+X121Y764D02*
+X145Y764D01*
+X198Y764D02*
+X212Y764D01*
+X95Y763D02*
+X109Y763D01*
+X122Y763D02*
+X146Y763D01*
+X198Y763D02*
+X212Y763D01*
+X95Y762D02*
+X109Y762D01*
+X123Y762D02*
+X148Y762D01*
+X198Y762D02*
+X212Y762D01*
+X95Y761D02*
+X109Y761D01*
+X125Y761D02*
+X149Y761D01*
+X198Y761D02*
+X212Y761D01*
+X95Y760D02*
+X109Y760D01*
+X126Y760D02*
+X150Y760D01*
+X198Y760D02*
+X212Y760D01*
+X95Y759D02*
+X109Y759D01*
+X127Y759D02*
+X152Y759D01*
+X198Y759D02*
+X212Y759D01*
+X95Y758D02*
+X109Y758D01*
+X129Y758D02*
+X153Y758D01*
+X198Y758D02*
+X212Y758D01*
+X95Y757D02*
+X109Y757D01*
+X130Y757D02*
+X154Y757D01*
+X198Y757D02*
+X212Y757D01*
+X95Y756D02*
+X109Y756D01*
+X131Y756D02*
+X155Y756D01*
+X198Y756D02*
+X212Y756D01*
+X95Y755D02*
+X109Y755D01*
+X132Y755D02*
+X157Y755D01*
+X198Y755D02*
+X212Y755D01*
+X95Y754D02*
+X109Y754D01*
+X134Y754D02*
+X158Y754D01*
+X198Y754D02*
+X212Y754D01*
+X95Y753D02*
+X109Y753D01*
+X135Y753D02*
+X159Y753D01*
+X198Y753D02*
+X212Y753D01*
+X95Y752D02*
+X109Y752D01*
+X136Y752D02*
+X161Y752D01*
+X198Y752D02*
+X212Y752D01*
+X95Y751D02*
+X109Y751D01*
+X138Y751D02*
+X162Y751D01*
+X198Y751D02*
+X212Y751D01*
+X95Y750D02*
+X109Y750D01*
+X139Y750D02*
+X163Y750D01*
+X198Y750D02*
+X212Y750D01*
+X95Y749D02*
+X109Y749D01*
+X140Y749D02*
+X164Y749D01*
+X198Y749D02*
+X212Y749D01*
+X95Y748D02*
+X109Y748D01*
+X141Y748D02*
+X166Y748D01*
+X198Y748D02*
+X212Y748D01*
+X1569Y748D02*
+X1620Y748D01*
+X95Y747D02*
+X109Y747D01*
+X143Y747D02*
+X167Y747D01*
+X198Y747D02*
+X212Y747D01*
+X1567Y747D02*
+X1622Y747D01*
+X95Y746D02*
+X109Y746D01*
+X144Y746D02*
+X168Y746D01*
+X198Y746D02*
+X212Y746D01*
+X1566Y746D02*
+X1623Y746D01*
+X95Y745D02*
+X109Y745D01*
+X145Y745D02*
+X170Y745D01*
+X198Y745D02*
+X212Y745D01*
+X1565Y745D02*
+X1624Y745D01*
+X95Y744D02*
+X109Y744D01*
+X147Y744D02*
+X171Y744D01*
+X198Y744D02*
+X212Y744D01*
+X1565Y744D02*
+X1625Y744D01*
+X95Y743D02*
+X109Y743D01*
+X148Y743D02*
+X172Y743D01*
+X198Y743D02*
+X212Y743D01*
+X1564Y743D02*
+X1626Y743D01*
+X95Y742D02*
+X109Y742D01*
+X149Y742D02*
+X173Y742D01*
+X198Y742D02*
+X212Y742D01*
+X1564Y742D02*
+X1626Y742D01*
+X95Y741D02*
+X109Y741D01*
+X151Y741D02*
+X175Y741D01*
+X198Y741D02*
+X212Y741D01*
+X1563Y741D02*
+X1626Y741D01*
+X95Y740D02*
+X109Y740D01*
+X152Y740D02*
+X176Y740D01*
+X198Y740D02*
+X212Y740D01*
+X1563Y740D02*
+X1626Y740D01*
+X95Y739D02*
+X109Y739D01*
+X153Y739D02*
+X177Y739D01*
+X198Y739D02*
+X212Y739D01*
+X1563Y739D02*
+X1626Y739D01*
+X95Y738D02*
+X109Y738D01*
+X154Y738D02*
+X179Y738D01*
+X198Y738D02*
+X212Y738D01*
+X1563Y738D02*
+X1626Y738D01*
+X95Y737D02*
+X109Y737D01*
+X156Y737D02*
+X180Y737D01*
+X198Y737D02*
+X212Y737D01*
+X1563Y737D02*
+X1626Y737D01*
+X95Y736D02*
+X109Y736D01*
+X157Y736D02*
+X181Y736D01*
+X198Y736D02*
+X212Y736D01*
+X1563Y736D02*
+X1626Y736D01*
+X95Y735D02*
+X109Y735D01*
+X158Y735D02*
+X182Y735D01*
+X198Y735D02*
+X212Y735D01*
+X1563Y735D02*
+X1626Y735D01*
+X95Y734D02*
+X109Y734D01*
+X160Y734D02*
+X184Y734D01*
+X198Y734D02*
+X212Y734D01*
+X1563Y734D02*
+X1626Y734D01*
+X95Y733D02*
+X109Y733D01*
+X161Y733D02*
+X185Y733D01*
+X198Y733D02*
+X212Y733D01*
+X1563Y733D02*
+X1626Y733D01*
+X95Y732D02*
+X109Y732D01*
+X162Y732D02*
+X186Y732D01*
+X198Y732D02*
+X212Y732D01*
+X1563Y732D02*
+X1626Y732D01*
+X95Y731D02*
+X109Y731D01*
+X163Y731D02*
+X188Y731D01*
+X198Y731D02*
+X212Y731D01*
+X1563Y731D02*
+X1626Y731D01*
+X95Y730D02*
+X109Y730D01*
+X165Y730D02*
+X189Y730D01*
+X198Y730D02*
+X212Y730D01*
+X1563Y730D02*
+X1581Y730D01*
+X1609Y730D02*
+X1626Y730D01*
+X95Y729D02*
+X110Y729D01*
+X166Y729D02*
+X190Y729D01*
+X198Y729D02*
+X212Y729D01*
+X1563Y729D02*
+X1580Y729D01*
+X1609Y729D02*
+X1626Y729D01*
+X95Y728D02*
+X110Y728D01*
+X167Y728D02*
+X191Y728D01*
+X198Y728D02*
+X212Y728D01*
+X1563Y728D02*
+X1580Y728D01*
+X1609Y728D02*
+X1626Y728D01*
+X95Y727D02*
+X111Y727D01*
+X169Y727D02*
+X193Y727D01*
+X198Y727D02*
+X212Y727D01*
+X1563Y727D02*
+X1580Y727D01*
+X1609Y727D02*
+X1626Y727D01*
+X96Y726D02*
+X114Y726D01*
+X170Y726D02*
+X194Y726D01*
+X196Y726D02*
+X212Y726D01*
+X1563Y726D02*
+X1580Y726D01*
+X1609Y726D02*
+X1626Y726D01*
+X96Y725D02*
+X118Y725D01*
+X171Y725D02*
+X212Y725D01*
+X1563Y725D02*
+X1580Y725D01*
+X1609Y725D02*
+X1626Y725D01*
+X96Y724D02*
+X119Y724D01*
+X172Y724D02*
+X212Y724D01*
+X1563Y724D02*
+X1580Y724D01*
+X1609Y724D02*
+X1626Y724D01*
+X97Y723D02*
+X120Y723D01*
+X174Y723D02*
+X211Y723D01*
+X1563Y723D02*
+X1580Y723D01*
+X1609Y723D02*
+X1626Y723D01*
+X97Y722D02*
+X121Y722D01*
+X175Y722D02*
+X211Y722D01*
+X1563Y722D02*
+X1580Y722D01*
+X1609Y722D02*
+X1626Y722D01*
+X98Y721D02*
+X122Y721D01*
+X176Y721D02*
+X211Y721D01*
+X1563Y721D02*
+X1580Y721D01*
+X1609Y721D02*
+X1626Y721D01*
+X98Y720D02*
+X122Y720D01*
+X178Y720D02*
+X210Y720D01*
+X1563Y720D02*
+X1580Y720D01*
+X1609Y720D02*
+X1626Y720D01*
+X99Y719D02*
+X122Y719D01*
+X179Y719D02*
+X210Y719D01*
+X1563Y719D02*
+X1580Y719D01*
+X1609Y719D02*
+X1626Y719D01*
+X100Y718D02*
+X122Y718D01*
+X180Y718D02*
+X209Y718D01*
+X1563Y718D02*
+X1580Y718D01*
+X1609Y718D02*
+X1626Y718D01*
+X101Y717D02*
+X122Y717D01*
+X181Y717D02*
+X208Y717D01*
+X1563Y717D02*
+X1580Y717D01*
+X1609Y717D02*
+X1626Y717D01*
+X102Y716D02*
+X122Y716D01*
+X183Y716D02*
+X207Y716D01*
+X1563Y716D02*
+X1580Y716D01*
+X1609Y716D02*
+X1626Y716D01*
+X103Y715D02*
+X121Y715D01*
+X184Y715D02*
+X206Y715D01*
+X1563Y715D02*
+X1580Y715D01*
+X1609Y715D02*
+X1626Y715D01*
+X104Y714D02*
+X121Y714D01*
+X185Y714D02*
+X205Y714D01*
+X1563Y714D02*
+X1580Y714D01*
+X1609Y714D02*
+X1626Y714D01*
+X106Y713D02*
+X120Y713D01*
+X187Y713D02*
+X204Y713D01*
+X1563Y713D02*
+X1580Y713D01*
+X1609Y713D02*
+X1626Y713D01*
+X108Y712D02*
+X119Y712D01*
+X189Y712D02*
+X202Y712D01*
+X1563Y712D02*
+X1580Y712D01*
+X1609Y712D02*
+X1626Y712D01*
+X112Y711D02*
+X117Y711D01*
+X192Y711D02*
+X198Y711D01*
+X1563Y711D02*
+X1580Y711D01*
+X1609Y711D02*
+X1626Y711D01*
+X1563Y710D02*
+X1580Y710D01*
+X1609Y710D02*
+X1626Y710D01*
+X1563Y709D02*
+X1580Y709D01*
+X1609Y709D02*
+X1626Y709D01*
+X1563Y708D02*
+X1580Y708D01*
+X1609Y708D02*
+X1626Y708D01*
+X1563Y707D02*
+X1580Y707D01*
+X1609Y707D02*
+X1626Y707D01*
+X1563Y706D02*
+X1580Y706D01*
+X1609Y706D02*
+X1626Y706D01*
+X1563Y705D02*
+X1580Y705D01*
+X1609Y705D02*
+X1626Y705D01*
+X1563Y704D02*
+X1580Y704D01*
+X1609Y704D02*
+X1626Y704D01*
+X1563Y703D02*
+X1580Y703D01*
+X1609Y703D02*
+X1626Y703D01*
+X1563Y702D02*
+X1580Y702D01*
+X1609Y702D02*
+X1626Y702D01*
+X1563Y701D02*
+X1580Y701D01*
+X1609Y701D02*
+X1626Y701D01*
+X1563Y700D02*
+X1580Y700D01*
+X1609Y700D02*
+X1626Y700D01*
+X1563Y699D02*
+X1580Y699D01*
+X1609Y699D02*
+X1626Y699D01*
+X1563Y698D02*
+X1580Y698D01*
+X1609Y698D02*
+X1626Y698D01*
+X1563Y697D02*
+X1580Y697D01*
+X1609Y697D02*
+X1626Y697D01*
+X1563Y696D02*
+X1580Y696D01*
+X1609Y696D02*
+X1626Y696D01*
+X1563Y695D02*
+X1580Y695D01*
+X1609Y695D02*
+X1626Y695D01*
+X1563Y694D02*
+X1580Y694D01*
+X1609Y694D02*
+X1626Y694D01*
+X1563Y693D02*
+X1580Y693D01*
+X1609Y693D02*
+X1626Y693D01*
+X1563Y692D02*
+X1580Y692D01*
+X1609Y692D02*
+X1626Y692D01*
+X1563Y691D02*
+X1580Y691D01*
+X1609Y691D02*
+X1626Y691D01*
+X1563Y690D02*
+X1580Y690D01*
+X1609Y690D02*
+X1626Y690D01*
+X1563Y689D02*
+X1580Y689D01*
+X1609Y689D02*
+X1626Y689D01*
+X1563Y688D02*
+X1580Y688D01*
+X1609Y688D02*
+X1626Y688D01*
+X1563Y687D02*
+X1580Y687D01*
+X1609Y687D02*
+X1626Y687D01*
+X1563Y686D02*
+X1580Y686D01*
+X1609Y686D02*
+X1626Y686D01*
+X1563Y685D02*
+X1580Y685D01*
+X1609Y685D02*
+X1626Y685D01*
+X1690Y685D02*
+X1698Y685D01*
+X1563Y684D02*
+X1580Y684D01*
+X1609Y684D02*
+X1626Y684D01*
+X1689Y684D02*
+X1699Y684D01*
+X1563Y683D02*
+X1580Y683D01*
+X1609Y683D02*
+X1626Y683D01*
+X1688Y683D02*
+X1700Y683D01*
+X1563Y682D02*
+X1580Y682D01*
+X1609Y682D02*
+X1626Y682D01*
+X1687Y682D02*
+X1701Y682D01*
+X1563Y681D02*
+X1580Y681D01*
+X1609Y681D02*
+X1626Y681D01*
+X1686Y681D02*
+X1702Y681D01*
+X1563Y680D02*
+X1580Y680D01*
+X1609Y680D02*
+X1626Y680D01*
+X1686Y680D02*
+X1702Y680D01*
+X1563Y679D02*
+X1580Y679D01*
+X1609Y679D02*
+X1626Y679D01*
+X1686Y679D02*
+X1702Y679D01*
+X1563Y678D02*
+X1580Y678D01*
+X1609Y678D02*
+X1626Y678D01*
+X1685Y678D02*
+X1702Y678D01*
+X1563Y677D02*
+X1581Y677D01*
+X1609Y677D02*
+X1627Y677D01*
+X1685Y677D02*
+X1703Y677D01*
+X1563Y676D02*
+X1703Y676D01*
+X1563Y675D02*
+X1703Y675D01*
+X1563Y674D02*
+X1703Y674D01*
+X1563Y673D02*
+X1703Y673D01*
+X1563Y672D02*
+X1703Y672D01*
+X1563Y671D02*
+X1703Y671D01*
+X1563Y670D02*
+X1703Y670D01*
+X1563Y669D02*
+X1703Y669D01*
+X1563Y668D02*
+X1703Y668D01*
+X1563Y667D02*
+X1702Y667D01*
+X1563Y666D02*
+X1702Y666D01*
+X1564Y665D02*
+X1702Y665D01*
+X1564Y664D02*
+X1702Y664D01*
+X1565Y663D02*
+X1701Y663D01*
+X1566Y662D02*
+X1700Y662D01*
+X1567Y661D02*
+X1699Y661D01*
+X1568Y660D02*
+X1698Y660D01*
+X1572Y659D02*
+X1694Y659D01*
+X1623Y618D02*
+X1635Y618D01*
+X1621Y617D02*
+X1637Y617D01*
+X1620Y616D02*
+X1639Y616D01*
+X1619Y615D02*
+X1640Y615D01*
+X1618Y614D02*
+X1640Y614D01*
+X1617Y613D02*
+X1641Y613D01*
+X1617Y612D02*
+X1641Y612D01*
+X1617Y611D02*
+X1641Y611D01*
+X1617Y610D02*
+X1642Y610D01*
+X1617Y609D02*
+X1642Y609D01*
+X1617Y608D02*
+X1642Y608D01*
+X1617Y607D02*
+X1642Y607D01*
+X1617Y606D02*
+X1642Y606D01*
+X1617Y605D02*
+X1642Y605D01*
+X1617Y604D02*
+X1642Y604D01*
+X1617Y603D02*
+X1642Y603D01*
+X1617Y602D02*
+X1642Y602D01*
+X1617Y601D02*
+X1642Y601D01*
+X1617Y600D02*
+X1642Y600D01*
+X1617Y599D02*
+X1642Y599D01*
+X1617Y598D02*
+X1642Y598D01*
+X1617Y597D02*
+X1642Y597D01*
+X1617Y596D02*
+X1642Y596D01*
+X1617Y595D02*
+X1642Y595D01*
+X1617Y594D02*
+X1642Y594D01*
+X1617Y593D02*
+X1642Y593D01*
+X1617Y592D02*
+X1642Y592D01*
+X1617Y591D02*
+X1642Y591D01*
+X1617Y590D02*
+X1642Y590D01*
+X1617Y589D02*
+X1642Y589D01*
+X1617Y588D02*
+X1642Y588D01*
+X1617Y587D02*
+X1642Y587D01*
+X1617Y586D02*
+X1642Y586D01*
+X1617Y585D02*
+X1642Y585D01*
+X1617Y584D02*
+X1642Y584D01*
+X1617Y583D02*
+X1642Y583D01*
+X1617Y582D02*
+X1642Y582D01*
+X1617Y581D02*
+X1642Y581D01*
+X1617Y580D02*
+X1642Y580D01*
+X1617Y579D02*
+X1642Y579D01*
+X1617Y578D02*
+X1642Y578D01*
+X1617Y577D02*
+X1642Y577D01*
+X1617Y576D02*
+X1642Y576D01*
+X1617Y575D02*
+X1642Y575D01*
+X1617Y574D02*
+X1642Y574D01*
+X1617Y573D02*
+X1642Y573D01*
+X1617Y572D02*
+X1642Y572D01*
+X1617Y571D02*
+X1642Y571D01*
+X1617Y570D02*
+X1642Y570D01*
+X1617Y569D02*
+X1642Y569D01*
+X1617Y568D02*
+X1642Y568D01*
+X1617Y567D02*
+X1642Y567D01*
+X1617Y566D02*
+X1642Y566D01*
+X1617Y565D02*
+X1642Y565D01*
+X1617Y564D02*
+X1642Y564D01*
+X1617Y563D02*
+X1642Y563D01*
+X1617Y562D02*
+X1642Y562D01*
+X1617Y561D02*
+X1642Y561D01*
+X1617Y560D02*
+X1642Y560D01*
+X1617Y559D02*
+X1642Y559D01*
+X1617Y558D02*
+X1642Y558D01*
+X1617Y557D02*
+X1642Y557D01*
+X1617Y556D02*
+X1642Y556D01*
+X1617Y555D02*
+X1642Y555D01*
+X1617Y554D02*
+X1642Y554D01*
+X1617Y553D02*
+X1642Y553D01*
+X1617Y552D02*
+X1642Y552D01*
+X1617Y551D02*
+X1642Y551D01*
+X1617Y550D02*
+X1642Y550D01*
+X1617Y549D02*
+X1642Y549D01*
+X1617Y548D02*
+X1642Y548D01*
+X1617Y547D02*
+X1642Y547D01*
+X1617Y546D02*
+X1642Y546D01*
+X1617Y545D02*
+X1642Y545D01*
+X1617Y544D02*
+X1642Y544D01*
+X1617Y543D02*
+X1642Y543D01*
+X1617Y542D02*
+X1642Y542D01*
+X1617Y541D02*
+X1642Y541D01*
+X1617Y540D02*
+X1642Y540D01*
+X1617Y539D02*
+X1642Y539D01*
+X1617Y538D02*
+X1642Y538D01*
+X1617Y537D02*
+X1642Y537D01*
+X1617Y536D02*
+X1641Y536D01*
+X1617Y535D02*
+X1641Y535D01*
+X1618Y534D02*
+X1641Y534D01*
+X1618Y533D02*
+X1640Y533D01*
+X1619Y532D02*
+X1639Y532D01*
+X1620Y531D02*
+X1638Y531D01*
+X1621Y530D02*
+X1637Y530D01*
+X1625Y529D02*
+X1633Y529D01*
+X1627Y488D02*
+X1638Y488D01*
+X1623Y487D02*
+X1643Y487D01*
+X1620Y486D02*
+X1646Y486D01*
+X1617Y485D02*
+X1649Y485D01*
+X1615Y484D02*
+X1651Y484D01*
+X1613Y483D02*
+X1653Y483D01*
+X1611Y482D02*
+X1655Y482D01*
+X1609Y481D02*
+X1657Y481D01*
+X1607Y480D02*
+X1659Y480D01*
+X1605Y479D02*
+X1661Y479D01*
+X1603Y478D02*
+X1663Y478D01*
+X1601Y477D02*
+X1665Y477D01*
+X1599Y476D02*
+X1667Y476D01*
+X1597Y475D02*
+X1669Y475D01*
+X1595Y474D02*
+X1671Y474D01*
+X1593Y473D02*
+X1673Y473D01*
+X1591Y472D02*
+X1675Y472D01*
+X1589Y471D02*
+X1677Y471D01*
+X1587Y470D02*
+X1629Y470D01*
+X1637Y470D02*
+X1679Y470D01*
+X1585Y469D02*
+X1625Y469D01*
+X1641Y469D02*
+X1681Y469D01*
+X1583Y468D02*
+X1622Y468D01*
+X1643Y468D02*
+X1683Y468D01*
+X1581Y467D02*
+X1620Y467D01*
+X1645Y467D02*
+X1685Y467D01*
+X1579Y466D02*
+X1618Y466D01*
+X1647Y466D02*
+X1687Y466D01*
+X1577Y465D02*
+X1616Y465D01*
+X1649Y465D02*
+X1689Y465D01*
+X1575Y464D02*
+X1614Y464D01*
+X1651Y464D02*
+X1690Y464D01*
+X1573Y463D02*
+X1612Y463D01*
+X1653Y463D02*
+X1692Y463D01*
+X1572Y462D02*
+X1611Y462D01*
+X1655Y462D02*
+X1693Y462D01*
+X1571Y461D02*
+X1609Y461D01*
+X1657Y461D02*
+X1694Y461D01*
+X1570Y460D02*
+X1607Y460D01*
+X1659Y460D02*
+X1695Y460D01*
+X1569Y459D02*
+X1605Y459D01*
+X1661Y459D02*
+X1696Y459D01*
+X1569Y458D02*
+X1603Y458D01*
+X1663Y458D02*
+X1697Y458D01*
+X1568Y457D02*
+X1601Y457D01*
+X1665Y457D02*
+X1697Y457D01*
+X1567Y456D02*
+X1599Y456D01*
+X1667Y456D02*
+X1698Y456D01*
+X1567Y455D02*
+X1597Y455D01*
+X1669Y455D02*
+X1699Y455D01*
+X1566Y454D02*
+X1595Y454D01*
+X1671Y454D02*
+X1699Y454D01*
+X1566Y453D02*
+X1593Y453D01*
+X1673Y453D02*
+X1700Y453D01*
+X1565Y452D02*
+X1591Y452D01*
+X1675Y452D02*
+X1700Y452D01*
+X1565Y451D02*
+X1589Y451D01*
+X1677Y451D02*
+X1701Y451D01*
+X1565Y450D02*
+X1587Y450D01*
+X1679Y450D02*
+X1701Y450D01*
+X1564Y449D02*
+X1585Y449D01*
+X1681Y449D02*
+X1701Y449D01*
+X1564Y448D02*
+X1583Y448D01*
+X1682Y448D02*
+X1702Y448D01*
+X1564Y447D02*
+X1582Y447D01*
+X1683Y447D02*
+X1702Y447D01*
+X1564Y446D02*
+X1582Y446D01*
+X1684Y446D02*
+X1702Y446D01*
+X1563Y445D02*
+X1581Y445D01*
+X1685Y445D02*
+X1702Y445D01*
+X1563Y444D02*
+X1581Y444D01*
+X1685Y444D02*
+X1702Y444D01*
+X1563Y443D02*
+X1581Y443D01*
+X1685Y443D02*
+X1702Y443D01*
+X1563Y442D02*
+X1580Y442D01*
+X1685Y442D02*
+X1703Y442D01*
+X1563Y441D02*
+X1580Y441D01*
+X1685Y441D02*
+X1703Y441D01*
+X1563Y440D02*
+X1580Y440D01*
+X1685Y440D02*
+X1703Y440D01*
+X1563Y439D02*
+X1580Y439D01*
+X1685Y439D02*
+X1703Y439D01*
+X1563Y438D02*
+X1580Y438D01*
+X1685Y438D02*
+X1703Y438D01*
+X1563Y437D02*
+X1580Y437D01*
+X1685Y437D02*
+X1703Y437D01*
+X1563Y436D02*
+X1580Y436D01*
+X1685Y436D02*
+X1703Y436D01*
+X1563Y435D02*
+X1581Y435D01*
+X1685Y435D02*
+X1703Y435D01*
+X1563Y434D02*
+X1703Y434D01*
+X99Y433D02*
+X105Y433D01*
+X202Y433D02*
+X208Y433D01*
+X1563Y433D02*
+X1703Y433D01*
+X98Y432D02*
+X106Y432D01*
+X200Y432D02*
+X209Y432D01*
+X1563Y432D02*
+X1703Y432D01*
+X97Y431D02*
+X107Y431D01*
+X199Y431D02*
+X210Y431D01*
+X1563Y431D02*
+X1703Y431D01*
+X96Y430D02*
+X108Y430D01*
+X199Y430D02*
+X211Y430D01*
+X1563Y430D02*
+X1703Y430D01*
+X95Y429D02*
+X109Y429D01*
+X198Y429D02*
+X211Y429D01*
+X1563Y429D02*
+X1703Y429D01*
+X95Y428D02*
+X109Y428D01*
+X198Y428D02*
+X212Y428D01*
+X1563Y428D02*
+X1703Y428D01*
+X95Y427D02*
+X109Y427D01*
+X198Y427D02*
+X212Y427D01*
+X1563Y427D02*
+X1703Y427D01*
+X95Y426D02*
+X109Y426D01*
+X198Y426D02*
+X212Y426D01*
+X1563Y426D02*
+X1703Y426D01*
+X95Y425D02*
+X109Y425D01*
+X198Y425D02*
+X212Y425D01*
+X1563Y425D02*
+X1703Y425D01*
+X95Y424D02*
+X109Y424D01*
+X198Y424D02*
+X212Y424D01*
+X1563Y424D02*
+X1703Y424D01*
+X95Y423D02*
+X109Y423D01*
+X198Y423D02*
+X212Y423D01*
+X1563Y423D02*
+X1703Y423D01*
+X95Y422D02*
+X109Y422D01*
+X198Y422D02*
+X212Y422D01*
+X1563Y422D02*
+X1703Y422D01*
+X95Y421D02*
+X109Y421D01*
+X198Y421D02*
+X212Y421D01*
+X1563Y421D02*
+X1703Y421D01*
+X95Y420D02*
+X109Y420D01*
+X198Y420D02*
+X212Y420D01*
+X1563Y420D02*
+X1703Y420D01*
+X95Y419D02*
+X109Y419D01*
+X198Y419D02*
+X212Y419D01*
+X1563Y419D02*
+X1703Y419D01*
+X95Y418D02*
+X109Y418D01*
+X198Y418D02*
+X212Y418D01*
+X1563Y418D02*
+X1703Y418D01*
+X95Y417D02*
+X109Y417D01*
+X198Y417D02*
+X212Y417D01*
+X1563Y417D02*
+X1703Y417D01*
+X95Y416D02*
+X109Y416D01*
+X198Y416D02*
+X212Y416D01*
+X1563Y416D02*
+X1580Y416D01*
+X1685Y416D02*
+X1703Y416D01*
+X95Y415D02*
+X109Y415D01*
+X198Y415D02*
+X212Y415D01*
+X1563Y415D02*
+X1580Y415D01*
+X1685Y415D02*
+X1703Y415D01*
+X95Y414D02*
+X109Y414D01*
+X198Y414D02*
+X212Y414D01*
+X1563Y414D02*
+X1580Y414D01*
+X1685Y414D02*
+X1703Y414D01*
+X95Y413D02*
+X109Y413D01*
+X198Y413D02*
+X212Y413D01*
+X1563Y413D02*
+X1580Y413D01*
+X1685Y413D02*
+X1703Y413D01*
+X95Y412D02*
+X109Y412D01*
+X198Y412D02*
+X212Y412D01*
+X1563Y412D02*
+X1580Y412D01*
+X1685Y412D02*
+X1703Y412D01*
+X95Y411D02*
+X109Y411D01*
+X198Y411D02*
+X212Y411D01*
+X1563Y411D02*
+X1580Y411D01*
+X1685Y411D02*
+X1703Y411D01*
+X95Y410D02*
+X109Y410D01*
+X198Y410D02*
+X212Y410D01*
+X1563Y410D02*
+X1580Y410D01*
+X1685Y410D02*
+X1703Y410D01*
+X95Y409D02*
+X109Y409D01*
+X198Y409D02*
+X212Y409D01*
+X1563Y409D02*
+X1580Y409D01*
+X1685Y409D02*
+X1703Y409D01*
+X95Y408D02*
+X109Y408D01*
+X198Y408D02*
+X212Y408D01*
+X1563Y408D02*
+X1580Y408D01*
+X1685Y408D02*
+X1703Y408D01*
+X95Y407D02*
+X109Y407D01*
+X198Y407D02*
+X212Y407D01*
+X1563Y407D02*
+X1580Y407D01*
+X1685Y407D02*
+X1702Y407D01*
+X95Y406D02*
+X109Y406D01*
+X198Y406D02*
+X212Y406D01*
+X1563Y406D02*
+X1580Y406D01*
+X1686Y406D02*
+X1702Y406D01*
+X95Y405D02*
+X109Y405D01*
+X198Y405D02*
+X212Y405D01*
+X1564Y405D02*
+X1580Y405D01*
+X1686Y405D02*
+X1702Y405D01*
+X95Y404D02*
+X109Y404D01*
+X198Y404D02*
+X212Y404D01*
+X1564Y404D02*
+X1580Y404D01*
+X1686Y404D02*
+X1702Y404D01*
+X95Y403D02*
+X109Y403D01*
+X198Y403D02*
+X212Y403D01*
+X1565Y403D02*
+X1579Y403D01*
+X1687Y403D02*
+X1701Y403D01*
+X95Y402D02*
+X109Y402D01*
+X198Y402D02*
+X212Y402D01*
+X1565Y402D02*
+X1578Y402D01*
+X1688Y402D02*
+X1700Y402D01*
+X95Y401D02*
+X109Y401D01*
+X198Y401D02*
+X212Y401D01*
+X1567Y401D02*
+X1577Y401D01*
+X1689Y401D02*
+X1699Y401D01*
+X95Y400D02*
+X109Y400D01*
+X198Y400D02*
+X212Y400D01*
+X1568Y400D02*
+X1576Y400D01*
+X1690Y400D02*
+X1698Y400D01*
+X95Y399D02*
+X109Y399D01*
+X198Y399D02*
+X212Y399D01*
+X1571Y399D02*
+X1573Y399D01*
+X1693Y399D02*
+X1695Y399D01*
+X95Y398D02*
+X109Y398D01*
+X198Y398D02*
+X212Y398D01*
+X95Y397D02*
+X109Y397D01*
+X198Y397D02*
+X212Y397D01*
+X95Y396D02*
+X109Y396D01*
+X197Y396D02*
+X212Y396D01*
+X95Y395D02*
+X110Y395D01*
+X197Y395D02*
+X212Y395D01*
+X95Y394D02*
+X110Y394D01*
+X197Y394D02*
+X212Y394D01*
+X95Y393D02*
+X111Y393D01*
+X196Y393D02*
+X211Y393D01*
+X96Y392D02*
+X112Y392D01*
+X195Y392D02*
+X211Y392D01*
+X96Y391D02*
+X114Y391D01*
+X193Y391D02*
+X211Y391D01*
+X96Y390D02*
+X116Y390D01*
+X191Y390D02*
+X211Y390D01*
+X97Y389D02*
+X118Y389D01*
+X189Y389D02*
+X210Y389D01*
+X97Y388D02*
+X120Y388D01*
+X187Y388D02*
+X210Y388D01*
+X98Y387D02*
+X122Y387D01*
+X185Y387D02*
+X209Y387D01*
+X98Y386D02*
+X124Y386D01*
+X183Y386D02*
+X209Y386D01*
+X99Y385D02*
+X126Y385D01*
+X181Y385D02*
+X208Y385D01*
+X100Y384D02*
+X128Y384D01*
+X179Y384D02*
+X207Y384D01*
+X101Y383D02*
+X130Y383D01*
+X177Y383D02*
+X207Y383D01*
+X101Y382D02*
+X132Y382D01*
+X175Y382D02*
+X206Y382D01*
+X102Y381D02*
+X134Y381D01*
+X173Y381D02*
+X205Y381D01*
+X104Y380D02*
+X136Y380D01*
+X171Y380D02*
+X204Y380D01*
+X105Y379D02*
+X138Y379D01*
+X169Y379D02*
+X202Y379D01*
+X107Y378D02*
+X140Y378D01*
+X167Y378D02*
+X201Y378D01*
+X108Y377D02*
+X141Y377D01*
+X165Y377D02*
+X199Y377D01*
+X110Y376D02*
+X143Y376D01*
+X163Y376D02*
+X197Y376D01*
+X112Y375D02*
+X146Y375D01*
+X161Y375D02*
+X195Y375D01*
+X114Y374D02*
+X149Y374D01*
+X157Y374D02*
+X193Y374D01*
+X116Y373D02*
+X191Y373D01*
+X118Y372D02*
+X189Y372D01*
+X120Y371D02*
+X187Y371D01*
+X122Y370D02*
+X185Y370D01*
+X124Y369D02*
+X183Y369D01*
+X126Y368D02*
+X181Y368D01*
+X128Y367D02*
+X179Y367D01*
+X130Y366D02*
+X177Y366D01*
+X132Y365D02*
+X174Y365D01*
+X134Y364D02*
+X172Y364D01*
+X136Y363D02*
+X170Y363D01*
+X138Y362D02*
+X168Y362D01*
+X141Y361D02*
+X166Y361D01*
+X144Y360D02*
+X163Y360D01*
+X148Y359D02*
+X159Y359D01*
+X1569Y358D02*
+X1702Y358D01*
+X1567Y357D02*
+X1703Y357D01*
+X1566Y356D02*
+X1703Y356D01*
+X1565Y355D02*
+X1703Y355D01*
+X1565Y354D02*
+X1703Y354D01*
+X1564Y353D02*
+X1703Y353D01*
+X1564Y352D02*
+X1703Y352D01*
+X1563Y351D02*
+X1703Y351D01*
+X1563Y350D02*
+X1703Y350D01*
+X1563Y349D02*
+X1703Y349D01*
+X1563Y348D02*
+X1703Y348D01*
+X1564Y347D02*
+X1703Y347D01*
+X1564Y346D02*
+X1703Y346D01*
+X1564Y345D02*
+X1703Y345D01*
+X1565Y344D02*
+X1703Y344D01*
+X1566Y343D02*
+X1703Y343D01*
+X1567Y342D02*
+X1703Y342D01*
+X1569Y341D02*
+X1703Y341D01*
+X1678Y340D02*
+X1703Y340D01*
+X1677Y339D02*
+X1703Y339D01*
+X1675Y338D02*
+X1703Y338D01*
+X1674Y337D02*
+X1703Y337D01*
+X1672Y336D02*
+X1703Y336D01*
+X1671Y335D02*
+X1702Y335D01*
+X1670Y334D02*
+X1700Y334D01*
+X1668Y333D02*
+X1699Y333D01*
+X1667Y332D02*
+X1697Y332D01*
+X1665Y331D02*
+X1696Y331D01*
+X1664Y330D02*
+X1694Y330D01*
+X1662Y329D02*
+X1693Y329D01*
+X1661Y328D02*
+X1692Y328D01*
+X1660Y327D02*
+X1690Y327D01*
+X1658Y326D02*
+X1689Y326D01*
+X1657Y325D02*
+X1687Y325D01*
+X1655Y324D02*
+X1686Y324D01*
+X1654Y323D02*
+X1684Y323D01*
+X1645Y322D02*
+X1683Y322D01*
+X1643Y321D02*
+X1682Y321D01*
+X1642Y320D02*
+X1680Y320D01*
+X1641Y319D02*
+X1679Y319D01*
+X1641Y318D02*
+X1677Y318D01*
+X1640Y317D02*
+X1676Y317D01*
+X1640Y316D02*
+X1674Y316D01*
+X1640Y315D02*
+X1673Y315D01*
+X1640Y314D02*
+X1672Y314D01*
+X1640Y313D02*
+X1672Y313D01*
+X1640Y312D02*
+X1673Y312D01*
+X1640Y311D02*
+X1675Y311D01*
+X1640Y310D02*
+X1676Y310D01*
+X1641Y309D02*
+X1678Y309D01*
+X1641Y308D02*
+X1679Y308D01*
+X1642Y307D02*
+X1681Y307D01*
+X1644Y306D02*
+X1682Y306D01*
+X1646Y305D02*
+X1683Y305D01*
+X1654Y304D02*
+X1685Y304D01*
+X1655Y303D02*
+X1686Y303D01*
+X1657Y302D02*
+X1688Y302D01*
+X1658Y301D02*
+X1689Y301D01*
+X1660Y300D02*
+X1691Y300D01*
+X1661Y299D02*
+X1692Y299D01*
+X1663Y298D02*
+X1693Y298D01*
+X1664Y297D02*
+X1695Y297D01*
+X1665Y296D02*
+X1696Y296D01*
+X1667Y295D02*
+X1698Y295D01*
+X1668Y294D02*
+X1699Y294D01*
+X1670Y293D02*
+X1700Y293D01*
+X1671Y292D02*
+X1702Y292D01*
+X1673Y291D02*
+X1703Y291D01*
+X1674Y290D02*
+X1703Y290D01*
+X1675Y289D02*
+X1703Y289D01*
+X1677Y288D02*
+X1703Y288D01*
+X1571Y287D02*
+X1703Y287D01*
+X1568Y286D02*
+X1703Y286D01*
+X1567Y285D02*
+X1703Y285D01*
+X1566Y284D02*
+X1703Y284D01*
+X1565Y283D02*
+X1703Y283D01*
+X1564Y282D02*
+X1703Y282D01*
+X1564Y281D02*
+X1703Y281D01*
+X1563Y280D02*
+X1703Y280D01*
+X1563Y279D02*
+X1703Y279D01*
+X1563Y278D02*
+X1703Y278D01*
+X1563Y277D02*
+X1703Y277D01*
+X1563Y276D02*
+X1703Y276D01*
+X1564Y275D02*
+X1703Y275D01*
+X1564Y274D02*
+X1703Y274D01*
+X1565Y273D02*
+X1703Y273D01*
+X1565Y272D02*
+X1703Y272D01*
+X1567Y271D02*
+X1703Y271D01*
+X1568Y270D02*
+X1703Y270D01*
+X1571Y269D02*
+X1702Y269D01*
+D02*
+G04 End of Copper0*
+M02*
\ No newline at end of file
diff --git a/tests/gerber_files/detector_copper_top.gbr b/tests/gerber_files/detector_copper_top.gbr
new file mode 100644
index 00000000..52b2e2ae
--- /dev/null
+++ b/tests/gerber_files/detector_copper_top.gbr
@@ -0,0 +1,71 @@
+G04 MADE WITH FRITZING*
+G04 WWW.FRITZING.ORG*
+G04 DOUBLE SIDED*
+G04 HOLES PLATED*
+G04 CONTOUR ON CENTER OF CONTOUR VECTOR*
+%ASAXBY*%
+%FSLAX23Y23*%
+%MOIN*%
+%OFA0B0*%
+%SFA1.0B1.0*%
+%ADD10C,0.075000*%
+%ADD11C,0.099055*%
+%ADD12C,0.078740*%
+%ADD13R,0.075000X0.075000*%
+%ADD14C,0.024000*%
+%ADD15C,0.020000*%
+%LNCOPPER1*%
+G90*
+G70*
+G54D10*
+X1149Y872D03*
+X1349Y872D03*
+X749Y722D03*
+X749Y522D03*
+X1149Y522D03*
+X1449Y522D03*
+X1149Y422D03*
+X1449Y422D03*
+X1149Y322D03*
+X1449Y322D03*
+X1149Y222D03*
+X1449Y222D03*
+X949Y472D03*
+X949Y72D03*
+G54D11*
+X749Y972D03*
+X599Y972D03*
+X349Y322D03*
+X349Y472D03*
+X349Y672D03*
+X349Y822D03*
+G54D10*
+X699Y122D03*
+X699Y322D03*
+G54D12*
+X699Y222D03*
+X949Y972D03*
+X749Y622D03*
+X1049Y222D03*
+X1249Y872D03*
+G54D13*
+X1149Y872D03*
+X1149Y522D03*
+G54D14*
+X952Y946D02*
+X1045Y249D01*
+G54D15*
+X776Y695D02*
+X721Y695D01*
+X721Y750D01*
+X776Y750D01*
+X776Y695D01*
+D02*
+X671Y150D02*
+X726Y150D01*
+X726Y95D01*
+X671Y95D01*
+X671Y150D01*
+D02*
+G04 End of Copper1*
+M02*
\ No newline at end of file
diff --git a/tests/gerber_files/detector_drill.txt b/tests/gerber_files/detector_drill.txt
new file mode 100644
index 00000000..c4945b84
--- /dev/null
+++ b/tests/gerber_files/detector_drill.txt
@@ -0,0 +1,46 @@
+; NON-PLATED HOLES START AT T1
+; THROUGH (PLATED) HOLES START AT T100
+M48
+INCH
+T1C0.125984
+T100C0.031496
+T101C0.035000
+T102C0.059055
+%
+T1
+X001488Y010223
+X001488Y001223
+X016488Y001223
+X016488Y010223
+T100
+X009488Y009723
+X007488Y006223
+X012488Y008723
+X010488Y002223
+X006988Y002223
+T101
+X014488Y004223
+X006988Y003223
+X013488Y008723
+X011488Y008723
+X007488Y005223
+X014488Y003223
+X014488Y002223
+X011488Y005223
+X009488Y000723
+X011488Y004223
+X006988Y001223
+X009488Y004723
+X007488Y007223
+X011488Y003223
+X014488Y005223
+X011488Y002223
+T102
+X003488Y008223
+X003488Y004723
+X007488Y009723
+X003488Y006723
+X005988Y009723
+X003488Y003223
+T00
+M30
diff --git a/tests/test_tcl_shell.py b/tests/test_tcl_shell.py
new file mode 100644
index 00000000..d36f30ed
--- /dev/null
+++ b/tests/test_tcl_shell.py
@@ -0,0 +1,180 @@
+import sys
+import unittest
+from PyQt4 import QtGui
+from PyQt4.QtCore import QThread
+
+from FlatCAMApp import App
+from FlatCAMObj import FlatCAMGerber, FlatCAMGeometry, FlatCAMCNCjob, FlatCAMExcellon
+from ObjectUI import GerberObjectUI, GeometryObjectUI
+from time import sleep
+import os
+import tempfile
+
+class TclShellTest(unittest.TestCase):
+
+ gerber_files = 'tests/gerber_files'
+ copper_bottom_filename = 'detector_copper_bottom.gbr'
+ copper_top_filename = 'detector_copper_top.gbr'
+ cutout_filename = 'detector_contour.gbr'
+ excellon_filename = 'detector_drill.txt'
+ excellon_name = "excellon"
+ gerber_top_name = "top"
+ gerber_bottom_name = "bottom"
+ gerber_cutout_name = "cutout"
+ engraver_diameter = 0.3
+ cutout_diameter = 3
+ drill_diameter = 0.8
+
+ @classmethod
+ def setUpClass(self):
+
+ self.setup=True
+ self.app = QtGui.QApplication(sys.argv)
+ # Create App, keep app defaults (do not load
+ # user-defined defaults).
+ self.fc = App(user_defaults=False)
+ self.fc.ui.shell_dock.show()
+
+ @classmethod
+ def tearDownClass(self):
+ self.fc.tcl=None
+ self.app.closeAllWindows()
+ del self.fc
+ del self.app
+ pass
+
+ def test_set_get_units(self):
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+
+ self.fc.exec_command_test('set_sys units IN')
+ self.fc.exec_command_test('new')
+ units=self.fc.exec_command_test('get_sys units')
+ self.assertEquals(units, "IN")
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+ units=self.fc.exec_command_test('get_sys units')
+ self.assertEquals(units, "MM")
+
+
+ def test_gerber_flow(self):
+
+ # open gerber files top, bottom and cutout
+
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+
+ self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_top_filename, self.gerber_top_name))
+ gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name)
+ self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber),
+ "Expected FlatCAMGerber, instead, %s is %s" %
+ (self.gerber_top_name, type(gerber_top_obj)))
+
+ self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_bottom_filename, self.gerber_bottom_name))
+ gerber_bottom_obj = self.fc.collection.get_by_name(self.gerber_bottom_name)
+ self.assertTrue(isinstance(gerber_bottom_obj, FlatCAMGerber),
+ "Expected FlatCAMGerber, instead, %s is %s" %
+ (self.gerber_bottom_name, type(gerber_bottom_obj)))
+
+ self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.cutout_filename, self.gerber_cutout_name))
+ gerber_cutout_obj = self.fc.collection.get_by_name(self.gerber_cutout_name)
+ self.assertTrue(isinstance(gerber_cutout_obj, FlatCAMGerber),
+ "Expected FlatCAMGerber, instead, %s is %s" %
+ (self.gerber_cutout_name, type(gerber_cutout_obj)))
+
+ # exteriors delete and join geometries for top layer
+ self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_cutout_name, self.engraver_diameter))
+ self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_iso', self.gerber_cutout_name + '_iso_exterior'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso'))
+ obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_iso_exterior')
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (self.gerber_cutout_name + '_iso_exterior', type(obj)))
+
+ # mirror bottom gerbers
+ self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_bottom_name, self.gerber_cutout_name))
+ self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.gerber_cutout_name, self.gerber_cutout_name))
+
+ # exteriors delete and join geometries for bottom layer
+ self.fc.exec_command_test('isolate %s -dia %f -outname %s' % (self.gerber_cutout_name, self.engraver_diameter, self.gerber_cutout_name + '_bottom_iso'))
+ self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso'))
+ obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior')
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (self.gerber_cutout_name + '_bottom_iso_exterior', type(obj)))
+
+ # at this stage we should have 5 objects
+ names = self.fc.collection.get_names()
+ self.assertEqual(len(names), 5,
+ "Expected 5 objects, found %d" % len(names))
+
+ # isolate traces
+ self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_top_name, self.engraver_diameter))
+ self.fc.exec_command_test('isolate %s -dia %f' % (self.gerber_bottom_name, self.engraver_diameter))
+
+ # join isolated geometries for top and bottom
+ self.fc.exec_command_test('join_geometries %s %s %s' % (self.gerber_top_name + '_join_iso', self.gerber_top_name + '_iso', self.gerber_cutout_name + '_iso_exterior'))
+ self.fc.exec_command_test('join_geometries %s %s %s' % (self.gerber_bottom_name + '_join_iso', self.gerber_bottom_name + '_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
+
+ # at this stage we should have 9 objects
+ names = self.fc.collection.get_names()
+ self.assertEqual(len(names), 9,
+ "Expected 9 objects, found %d" % len(names))
+
+ # clean unused isolations
+ self.fc.exec_command_test('delete %s' % (self.gerber_bottom_name + '_iso'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_top_name + '_iso'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_iso_exterior'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso_exterior'))
+
+ # at this stage we should have 5 objects again
+ names = self.fc.collection.get_names()
+ self.assertEqual(len(names), 5,
+ "Expected 5 objects, found %d" % len(names))
+
+ # geocutout bottom test (it cuts to same object)
+ self.fc.exec_command_test('isolate %s -dia %f -outname %s' % (self.gerber_cutout_name, self.cutout_diameter, self.gerber_cutout_name + '_bottom_iso'))
+ self.fc.exec_command_test('exteriors %s -outname %s' % (self.gerber_cutout_name + '_bottom_iso', self.gerber_cutout_name + '_bottom_iso_exterior'))
+ self.fc.exec_command_test('delete %s' % (self.gerber_cutout_name + '_bottom_iso'))
+ obj = self.fc.collection.get_by_name(self.gerber_cutout_name + '_bottom_iso_exterior')
+ self.assertTrue(isinstance(obj, FlatCAMGeometry),
+ "Expected FlatCAMGeometry, instead, %s is %s" %
+ (self.gerber_cutout_name + '_bottom_iso_exterior', type(obj)))
+ self.fc.exec_command_test('geocutout %s -dia %f -gapsize 0.3 -gaps 4' % (self.gerber_cutout_name + '_bottom_iso_exterior', self.cutout_diameter))
+
+ # at this stage we should have 6 objects
+ names = self.fc.collection.get_names()
+ self.assertEqual(len(names), 6,
+ "Expected 6 objects, found %d" % len(names))
+
+ # TODO: tests for tcl
+
+ def test_open_gerber(self):
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+
+ self.fc.exec_command_test('open_gerber %s/%s -outname %s' % (self.gerber_files, self.copper_top_filename, self.gerber_top_name))
+ gerber_top_obj = self.fc.collection.get_by_name(self.gerber_top_name)
+ self.assertTrue(isinstance(gerber_top_obj, FlatCAMGerber),
+ "Expected FlatCAMGerber, instead, %s is %s" %
+ (self.gerber_top_name, type(gerber_top_obj)))
+
+ def test_excellon_flow(self):
+
+ self.fc.exec_command_test('set_sys units MM')
+ self.fc.exec_command_test('new')
+ self.fc.exec_command_test('open_excellon %s/%s -outname %s' % (self.gerber_files, self.excellon_filename, self.excellon_name))
+ excellon_obj = self.fc.collection.get_by_name(self.excellon_name)
+ self.assertTrue(isinstance(excellon_obj, FlatCAMExcellon),
+ "Expected FlatCAMExcellon, instead, %s is %s" %
+ (self.excellon_name, type(excellon_obj)))
+
+ # mirror bottom excellon
+ self.fc.exec_command_test('mirror %s -box %s -axis X' % (self.excellon_name, self.gerber_cutout_name))
+
+ # TODO: tests for tcl
\ No newline at end of file