|
@@ -44,10 +44,13 @@ class App(QtCore.QObject):
|
|
|
log.addHandler(handler)
|
|
log.addHandler(handler)
|
|
|
|
|
|
|
|
## URL for update checks
|
|
## URL for update checks
|
|
|
- version_url = "http://caram.cl/flatcam/VERSION"
|
|
|
|
|
|
|
+ version_url = "http://flatcam.org/FlatCAM/apptalk/version"
|
|
|
|
|
|
|
|
## App URL
|
|
## App URL
|
|
|
- app_url = "http://caram.cl/software/flatcam"
|
|
|
|
|
|
|
+ app_url = "http://flatcam.org"
|
|
|
|
|
+
|
|
|
|
|
+ ## Manual URL
|
|
|
|
|
+ manual_url = "http://flatcam.org/manual/static/index.html"
|
|
|
|
|
|
|
|
## Signals
|
|
## Signals
|
|
|
inform = QtCore.pyqtSignal(str) # Message
|
|
inform = QtCore.pyqtSignal(str) # Message
|
|
@@ -132,6 +135,8 @@ class App(QtCore.QObject):
|
|
|
self.defaults = LoudDict()
|
|
self.defaults = LoudDict()
|
|
|
self.defaults.set_change_callback(lambda key: self.defaults_write_form()) # When the dictionary changes.
|
|
self.defaults.set_change_callback(lambda key: self.defaults_write_form()) # When the dictionary changes.
|
|
|
self.defaults.update({
|
|
self.defaults.update({
|
|
|
|
|
+ "serial": 0,
|
|
|
|
|
+ "stats": {},
|
|
|
"units": "IN",
|
|
"units": "IN",
|
|
|
"gerber_plot": True,
|
|
"gerber_plot": True,
|
|
|
"gerber_solid": True,
|
|
"gerber_solid": True,
|
|
@@ -166,6 +171,11 @@ class App(QtCore.QObject):
|
|
|
})
|
|
})
|
|
|
self.load_defaults()
|
|
self.load_defaults()
|
|
|
|
|
|
|
|
|
|
+ chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
|
|
|
+ if self.defaults['serial'] == 0:
|
|
|
|
|
+ self.defaults['serial'] = ''.join([random.choice(chars) for i in range(20)])
|
|
|
|
|
+ self.save_defaults()
|
|
|
|
|
+
|
|
|
self.options_form = GlobalOptionsUI()
|
|
self.options_form = GlobalOptionsUI()
|
|
|
self.options_form_fields = {
|
|
self.options_form_fields = {
|
|
|
"units": self.options_form.units_radio,
|
|
"units": self.options_form.units_radio,
|
|
@@ -298,7 +308,8 @@ class App(QtCore.QObject):
|
|
|
self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
|
|
self.ui.menuviewenable.triggered.connect(self.enable_all_plots)
|
|
|
self.ui.menutoolshell.triggered.connect(lambda: self.shell.show())
|
|
self.ui.menutoolshell.triggered.connect(lambda: self.shell.show())
|
|
|
self.ui.menuhelp_about.triggered.connect(self.on_about)
|
|
self.ui.menuhelp_about.triggered.connect(self.on_about)
|
|
|
- self.ui.menuhelp_manual.triggered.connect(lambda: webbrowser.open(self.app_url))
|
|
|
|
|
|
|
+ self.ui.menuhelp_home.triggered.connect(lambda: webbrowser.open(self.app_url))
|
|
|
|
|
+ self.ui.menuhelp_manual.triggered.connect(lambda: webbrowser.open(self.manual_url))
|
|
|
# Toolbar
|
|
# Toolbar
|
|
|
self.ui.zoom_fit_btn.triggered.connect(self.on_zoom_fit)
|
|
self.ui.zoom_fit_btn.triggered.connect(self.on_zoom_fit)
|
|
|
self.ui.zoom_in_btn.triggered.connect(lambda: self.plotcanvas.zoom(1.5))
|
|
self.ui.zoom_in_btn.triggered.connect(lambda: self.plotcanvas.zoom(1.5))
|
|
@@ -354,7 +365,9 @@ class App(QtCore.QObject):
|
|
|
try:
|
|
try:
|
|
|
self.defaults_form_fields[option].set_value(self.defaults[option])
|
|
self.defaults_form_fields[option].set_value(self.defaults[option])
|
|
|
except KeyError:
|
|
except KeyError:
|
|
|
- self.log.error("defaults_write_form(): No field for: %s" % option)
|
|
|
|
|
|
|
+ #self.log.debug("defaults_write_form(): No field for: %s" % option)
|
|
|
|
|
+ # TODO: Rethink this?
|
|
|
|
|
+ pass
|
|
|
|
|
|
|
|
def disable_plots(self, except_current=False):
|
|
def disable_plots(self, except_current=False):
|
|
|
"""
|
|
"""
|
|
@@ -387,9 +400,9 @@ class App(QtCore.QObject):
|
|
|
# Send to worker
|
|
# Send to worker
|
|
|
self.worker_task.emit({'fcn': worker_task, 'params': [self]})
|
|
self.worker_task.emit({'fcn': worker_task, 'params': [self]})
|
|
|
|
|
|
|
|
- def execCommand(self, text):
|
|
|
|
|
|
|
+ def exec_command(self, text):
|
|
|
"""
|
|
"""
|
|
|
- Hadles input from the shell.
|
|
|
|
|
|
|
+ Hadles input from the shell. See FlatCAMApp.setup_shell for shell commands.
|
|
|
|
|
|
|
|
:param text: Input command
|
|
:param text: Input command
|
|
|
:return: None
|
|
:return: None
|
|
@@ -404,60 +417,9 @@ class App(QtCore.QObject):
|
|
|
raise
|
|
raise
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- def shhelp(p=None):
|
|
|
|
|
- if not p:
|
|
|
|
|
- return "Available commands:\n" + '\n'.join([' ' + cmd for cmd in commands])
|
|
|
|
|
-
|
|
|
|
|
- if p not in commands:
|
|
|
|
|
- return "Unknown command: %s" % p
|
|
|
|
|
-
|
|
|
|
|
- return commands[p]["help"]
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- commands = {
|
|
|
|
|
- "open_gerber": {
|
|
|
|
|
- "fcn": self.open_gerber,
|
|
|
|
|
- "params": 1,
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: None,
|
|
|
|
|
- "help": "Opens a Gerber file.\n> open_gerber <filename>\n filename: Path to file to open."
|
|
|
|
|
- },
|
|
|
|
|
- "open_excellon": {
|
|
|
|
|
- "fcn": self.open_excellon,
|
|
|
|
|
- "params": 1,
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: None,
|
|
|
|
|
- "help": "Opens an Excellon file.\n> open_excellon <filename>\n filename: Path to file to open."
|
|
|
|
|
- },
|
|
|
|
|
- "open_gcode": {
|
|
|
|
|
- "fcn": self.open_gcode,
|
|
|
|
|
- "params": 1,
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: None,
|
|
|
|
|
- "help": "Opens an G-Code file.\n> open_gcode <filename>\n filename: Path to file to open."
|
|
|
|
|
- },
|
|
|
|
|
- "open_project": {
|
|
|
|
|
- "fcn": self.open_project,
|
|
|
|
|
- "params": 1,
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: None,
|
|
|
|
|
- "help": "Opens a FlatCAM project.\n> open_project <filename>\n filename: Path to file to open."
|
|
|
|
|
- },
|
|
|
|
|
- "save_project": {
|
|
|
|
|
- "fcn": self.save_project,
|
|
|
|
|
- "params": 1,
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: None,
|
|
|
|
|
- "help": "Saves the FlatCAM project to file.\n> save_project <filename>\n filename: Path to file to save."
|
|
|
|
|
- },
|
|
|
|
|
- "help": {
|
|
|
|
|
- "fcn": shhelp,
|
|
|
|
|
- "params": [0, 1],
|
|
|
|
|
- "converters": [lambda x: x],
|
|
|
|
|
- "retfcn": lambda x: x,
|
|
|
|
|
- "help": "Shows list of commands."
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ """
|
|
|
|
|
+ Code below is unsused. Saved for later.
|
|
|
|
|
+ """
|
|
|
|
|
|
|
|
parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
|
|
parts = re.findall(r'([\w\\:\.]+|".*?")+', text)
|
|
|
parts = [p.replace('\n', '').replace('"', '') for p in parts]
|
|
parts = [p.replace('\n', '').replace('"', '') for p in parts]
|
|
@@ -690,6 +652,9 @@ class App(QtCore.QObject):
|
|
|
:return: None
|
|
:return: None
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+ self.save_defaults()
|
|
|
|
|
+
|
|
|
|
|
+ def save_defaults(self):
|
|
|
# Read options from file
|
|
# Read options from file
|
|
|
try:
|
|
try:
|
|
|
f = open("defaults.json")
|
|
f = open("defaults.json")
|
|
@@ -1257,13 +1222,16 @@ class App(QtCore.QObject):
|
|
|
else:
|
|
else:
|
|
|
self.inform.emit("Project copy saved to: " + self.project_filename)
|
|
self.inform.emit("Project copy saved to: " + self.project_filename)
|
|
|
|
|
|
|
|
- def open_gerber(self, filename):
|
|
|
|
|
|
|
+ def open_gerber(self, filename, follow=False, outname=None):
|
|
|
"""
|
|
"""
|
|
|
Opens a Gerber file, parses it and creates a new object for
|
|
Opens a Gerber file, parses it and creates a new object for
|
|
|
it in the program. Thread-safe.
|
|
it in the program. Thread-safe.
|
|
|
|
|
|
|
|
:param filename: Gerber file filename
|
|
:param filename: Gerber file filename
|
|
|
:type filename: str
|
|
:type filename: str
|
|
|
|
|
+ :param follow: If true, the parser will not create polygons, just lines
|
|
|
|
|
+ following the gerber path.
|
|
|
|
|
+ :type follow: bool
|
|
|
:return: None
|
|
:return: None
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
@@ -1277,13 +1245,13 @@ class App(QtCore.QObject):
|
|
|
|
|
|
|
|
# Opening the file happens here
|
|
# Opening the file happens here
|
|
|
self.progress.emit(30)
|
|
self.progress.emit(30)
|
|
|
- gerber_obj.parse_file(filename)
|
|
|
|
|
|
|
+ gerber_obj.parse_file(filename, follow=follow)
|
|
|
|
|
|
|
|
# Further parsing
|
|
# Further parsing
|
|
|
self.progress.emit(70)
|
|
self.progress.emit(70)
|
|
|
|
|
|
|
|
# Object name
|
|
# Object name
|
|
|
- name = filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
+ name = outname or filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
|
self.new_object("gerber", name, obj_init)
|
|
self.new_object("gerber", name, obj_init)
|
|
|
|
|
|
|
@@ -1310,7 +1278,7 @@ class App(QtCore.QObject):
|
|
|
# GUI feedback
|
|
# GUI feedback
|
|
|
self.inform.emit("Opened: " + filename)
|
|
self.inform.emit("Opened: " + filename)
|
|
|
|
|
|
|
|
- def open_excellon(self, filename):
|
|
|
|
|
|
|
+ def open_excellon(self, filename, outname=None):
|
|
|
"""
|
|
"""
|
|
|
Opens an Excellon file, parses it and creates a new object for
|
|
Opens an Excellon file, parses it and creates a new object for
|
|
|
it in the program. Thread-safe.
|
|
it in the program. Thread-safe.
|
|
@@ -1332,7 +1300,7 @@ class App(QtCore.QObject):
|
|
|
self.progress.emit(70)
|
|
self.progress.emit(70)
|
|
|
|
|
|
|
|
# Object name
|
|
# Object name
|
|
|
- name = filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
+ name = outname or filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
|
self.new_object("excellon", name, obj_init)
|
|
self.new_object("excellon", name, obj_init)
|
|
|
# New object creation and file processing
|
|
# New object creation and file processing
|
|
@@ -1356,7 +1324,7 @@ class App(QtCore.QObject):
|
|
|
self.inform.emit("Opened: " + filename)
|
|
self.inform.emit("Opened: " + filename)
|
|
|
self.progress.emit(100)
|
|
self.progress.emit(100)
|
|
|
|
|
|
|
|
- def open_gcode(self, filename):
|
|
|
|
|
|
|
+ def open_gcode(self, filename, outname=None):
|
|
|
"""
|
|
"""
|
|
|
Opens a G-gcode file, parses it and creates a new object for
|
|
Opens a G-gcode file, parses it and creates a new object for
|
|
|
it in the program. Thread-safe.
|
|
it in the program. Thread-safe.
|
|
@@ -1389,7 +1357,7 @@ class App(QtCore.QObject):
|
|
|
job_obj.create_geometry()
|
|
job_obj.create_geometry()
|
|
|
|
|
|
|
|
# Object name
|
|
# Object name
|
|
|
- name = filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
+ name = outname or filename.split('/')[-1].split('\\')[-1]
|
|
|
|
|
|
|
|
# New object creation and file processing
|
|
# New object creation and file processing
|
|
|
try:
|
|
try:
|
|
@@ -1503,12 +1471,18 @@ class App(QtCore.QObject):
|
|
|
self.ui.progress_bar.setValue(int(percentage))
|
|
self.ui.progress_bar.setValue(int(percentage))
|
|
|
|
|
|
|
|
def setup_shell(self):
|
|
def setup_shell(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Creates shell functions.
|
|
|
|
|
+
|
|
|
|
|
+ :return: None
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
self.log.debug("setup_shell()")
|
|
self.log.debug("setup_shell()")
|
|
|
|
|
|
|
|
def shelp(p=None):
|
|
def shelp(p=None):
|
|
|
if not p:
|
|
if not p:
|
|
|
return "Available commands:\n" + '\n'.join([' ' + cmd for cmd in commands]) + \
|
|
return "Available commands:\n" + '\n'.join([' ' + cmd for cmd in commands]) + \
|
|
|
- "\n\nType help <command_name> for usage.\n Example: help open_gerber"
|
|
|
|
|
|
|
+ "\n\nType help <command_name> for usage.\n Example: help open_gerber"
|
|
|
|
|
|
|
|
if p not in commands:
|
|
if p not in commands:
|
|
|
return "Unknown command: %s" % p
|
|
return "Unknown command: %s" % p
|
|
@@ -1519,11 +1493,252 @@ class App(QtCore.QObject):
|
|
|
ops = self.collection.get_by_name(str(name)).options
|
|
ops = self.collection.get_by_name(str(name)).options
|
|
|
return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops])
|
|
return '\n'.join(["%s: %s" % (o, ops[o]) for o in ops])
|
|
|
|
|
|
|
|
- def isolate(name, dia=None, passes=None, overlap=None):
|
|
|
|
|
- dia = float(dia) if dia is not None else None
|
|
|
|
|
- passes = int(passes) if passes is not None else None
|
|
|
|
|
- overlap = float(overlap) if overlap is not None else None
|
|
|
|
|
- self.collection.get_by_name(str(name)).isolate(dia, passes, overlap)
|
|
|
|
|
|
|
+ def h(*args):
|
|
|
|
|
+ """
|
|
|
|
|
+ Pre-processes arguments to detect '-keyword value' pairs into dictionary
|
|
|
|
|
+ and standalone parameters into list.
|
|
|
|
|
+ """
|
|
|
|
|
+
|
|
|
|
|
+ kwa = {}
|
|
|
|
|
+ a = []
|
|
|
|
|
+ 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:
|
|
|
|
|
+ a.append(args[i])
|
|
|
|
|
+ else:
|
|
|
|
|
+ kwa[name] = args[i]
|
|
|
|
|
+ name = None
|
|
|
|
|
+
|
|
|
|
|
+ return a, kwa
|
|
|
|
|
+
|
|
|
|
|
+ def open_gerber(filename, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+ types = {'follow': bool,
|
|
|
|
|
+ 'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ self.open_gerber(str(filename), **kwa)
|
|
|
|
|
+
|
|
|
|
|
+ def open_excellon(filename, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+ types = {'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ self.open_excellon(str(filename), **kwa)
|
|
|
|
|
+
|
|
|
|
|
+ def open_gcode(filename, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+ types = {'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ self.open_gcode(str(filename), **kwa)
|
|
|
|
|
+
|
|
|
|
|
+ def isolate(name, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+ types = {'dia': float,
|
|
|
|
|
+ 'passes': int,
|
|
|
|
|
+ 'overlap': float,
|
|
|
|
|
+ 'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % name
|
|
|
|
|
+
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % name
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj.isolate(**kwa)
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ return "Operation failed: %s" % str(e)
|
|
|
|
|
+
|
|
|
|
|
+ return 'Ok'
|
|
|
|
|
+
|
|
|
|
|
+ def cncjob(obj_name, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+
|
|
|
|
|
+ types = {'z_cut': float,
|
|
|
|
|
+ 'z_move': float,
|
|
|
|
|
+ 'feedrate': float,
|
|
|
|
|
+ 'tooldia': float,
|
|
|
|
|
+ 'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj.generatecncjob(**kwa)
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ return "Operation failed: %s" % str(e)
|
|
|
|
|
+
|
|
|
|
|
+ return 'Ok'
|
|
|
|
|
+
|
|
|
|
|
+ def write_gcode(obj_name, filename, preamble='', postamble=''):
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+ obj.export_gcode(str(filename), str(preamble), str(postamble))
|
|
|
|
|
+
|
|
|
|
|
+ def paint_poly(obj_name, inside_pt_x, inside_pt_y, tooldia, overlap):
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+ obj.paint_poly([float(inside_pt_x), float(inside_pt_y)], float(tooldia), float(overlap))
|
|
|
|
|
+
|
|
|
|
|
+ def add_poly(obj_name, *args):
|
|
|
|
|
+ if len(args) % 2 != 0:
|
|
|
|
|
+ return "Incomplete coordinate."
|
|
|
|
|
+
|
|
|
|
|
+ points = [[float(args[2*i]), float(args[2*i+1])] for i in range(len(args)/2)]
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+
|
|
|
|
|
+ obj.add_polygon(points)
|
|
|
|
|
+
|
|
|
|
|
+ def add_rectangle(obj_name, botleft_x, botleft_y, topright_x, topright_y):
|
|
|
|
|
+ return add_poly(obj_name, botleft_x, botleft_y, botleft_x, topright_y,
|
|
|
|
|
+ topright_x, topright_y, topright_x, botleft_y)
|
|
|
|
|
+
|
|
|
|
|
+ def add_circle(obj_name, center_x, center_y, radius):
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+
|
|
|
|
|
+ obj.add_circle([float(center_x), float(center_y)], float(radius))
|
|
|
|
|
+
|
|
|
|
|
+ def set_active(obj_name):
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.collection.set_active(str(obj_name))
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ return "Command failed: %s" % str(e)
|
|
|
|
|
+
|
|
|
|
|
+ def delete(obj_name):
|
|
|
|
|
+ try:
|
|
|
|
|
+ self.collection.set_active(str(obj_name))
|
|
|
|
|
+ self.on_delete()
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ return "Command failed: %s" % str(e)
|
|
|
|
|
+
|
|
|
|
|
+ def geo_union(obj_name):
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+
|
|
|
|
|
+ obj.union()
|
|
|
|
|
+
|
|
|
|
|
+ def make_docs():
|
|
|
|
|
+ output = ''
|
|
|
|
|
+ import collections
|
|
|
|
|
+ od = collections.OrderedDict(sorted(commands.items()))
|
|
|
|
|
+ for cmd, val in od.iteritems():
|
|
|
|
|
+ #print cmd, '\n', ''.join(['~']*len(cmd))
|
|
|
|
|
+ output += cmd + ' \n' + ''.join(['~']*len(cmd)) + '\n'
|
|
|
|
|
+
|
|
|
|
|
+ t = val['help']
|
|
|
|
|
+ usage_i = t.find('>')
|
|
|
|
|
+ if usage_i < 0:
|
|
|
|
|
+ expl = t
|
|
|
|
|
+ #print expl + '\n'
|
|
|
|
|
+ output += expl + '\n\n'
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ expl = t[:usage_i-1]
|
|
|
|
|
+ #print expl + '\n'
|
|
|
|
|
+ output += expl + '\n\n'
|
|
|
|
|
+
|
|
|
|
|
+ end_usage_i = t[usage_i:].find('\n')
|
|
|
|
|
+
|
|
|
|
|
+ if end_usage_i < 0:
|
|
|
|
|
+ end_usage_i = len(t[usage_i:])
|
|
|
|
|
+ #print ' ' + t[usage_i:]
|
|
|
|
|
+ #print ' No parameters.\n'
|
|
|
|
|
+ output += ' ' + t[usage_i:] + '\n No parameters.\n'
|
|
|
|
|
+ else:
|
|
|
|
|
+ extras = t[usage_i+end_usage_i+1:]
|
|
|
|
|
+ parts = [s.strip() for s in extras.split('\n')]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ #print ' ' + t[usage_i:usage_i+end_usage_i]
|
|
|
|
|
+ output += ' ' + t[usage_i:usage_i+end_usage_i] + '\n'
|
|
|
|
|
+ for p in parts:
|
|
|
|
|
+ #print ' ' + p + '\n'
|
|
|
|
|
+ output += ' ' + p + '\n\n'
|
|
|
|
|
+
|
|
|
|
|
+ return output
|
|
|
|
|
+
|
|
|
|
|
+ def follow(obj_name, *args):
|
|
|
|
|
+ a, kwa = h(*args)
|
|
|
|
|
+
|
|
|
|
|
+ types = {'outname': str}
|
|
|
|
|
+
|
|
|
|
|
+ for key in kwa:
|
|
|
|
|
+ if key not in types:
|
|
|
|
|
+ return 'Unknown parameter: %s' % key
|
|
|
|
|
+ kwa[key] = types[key](kwa[key])
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj = self.collection.get_by_name(str(obj_name))
|
|
|
|
|
+ except:
|
|
|
|
|
+ return "Could not retrieve object: %s" % obj_name
|
|
|
|
|
+ if obj is None:
|
|
|
|
|
+ return "Object not found: %s" % obj_name
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ obj.follow(**kwa)
|
|
|
|
|
+ except Exception, e:
|
|
|
|
|
+ return "ERROR: %s" % str(e)
|
|
|
|
|
|
|
|
commands = {
|
|
commands = {
|
|
|
'help': {
|
|
'help': {
|
|
@@ -1531,16 +1746,26 @@ class App(QtCore.QObject):
|
|
|
'help': "Shows list of commands."
|
|
'help': "Shows list of commands."
|
|
|
},
|
|
},
|
|
|
'open_gerber': {
|
|
'open_gerber': {
|
|
|
- 'fcn': self.open_gerber,
|
|
|
|
|
- 'help': "Opens a Gerber file.\n> open_gerber <filename>\n filename: Path to file to open."
|
|
|
|
|
|
|
+ 'fcn': open_gerber,
|
|
|
|
|
+ 'help': "Opens a Gerber file.\n' +"
|
|
|
|
|
+ "> open_gerber <filename> [-follow <0|1>] [-outname <o>]\n' +"
|
|
|
|
|
+ " filename: Path to file to open.\n" +
|
|
|
|
|
+ " follow: If 1, does not create polygons, just follows the gerber path.\n" +
|
|
|
|
|
+ " outname: Name of the created gerber object."
|
|
|
},
|
|
},
|
|
|
'open_excellon': {
|
|
'open_excellon': {
|
|
|
- 'fcn': self.open_excellon,
|
|
|
|
|
- 'help': "Opens an Excellon file.\n> open_excellon <filename>\n filename: Path to file to open."
|
|
|
|
|
|
|
+ 'fcn': open_excellon,
|
|
|
|
|
+ 'help': "Opens an Excellon file.\n" +
|
|
|
|
|
+ "> open_excellon <filename> [-outname <o>]\n" +
|
|
|
|
|
+ " filename: Path to file to open.\n" +
|
|
|
|
|
+ " outname: Name of the created excellon object."
|
|
|
},
|
|
},
|
|
|
'open_gcode': {
|
|
'open_gcode': {
|
|
|
- 'fcn': self.open_gcode,
|
|
|
|
|
- 'help': "Opens an G-Code file.\n> open_gcode <filename>\n filename: Path to file to open."
|
|
|
|
|
|
|
+ 'fcn': open_gcode,
|
|
|
|
|
+ 'help': "Opens an G-Code file.\n" +
|
|
|
|
|
+ "> open_gcode <filename> [-outname <o>]\n" +
|
|
|
|
|
+ " filename: Path to file to open.\n" +
|
|
|
|
|
+ " outname: Name of the created CNC Job object."
|
|
|
},
|
|
},
|
|
|
'open_project': {
|
|
'open_project': {
|
|
|
'fcn': self.open_project,
|
|
'fcn': self.open_project,
|
|
@@ -1551,7 +1776,7 @@ class App(QtCore.QObject):
|
|
|
'help': "Saves the FlatCAM project to file.\n> save_project <filename>\n filename: Path to file to save."
|
|
'help': "Saves the FlatCAM project to file.\n> save_project <filename>\n filename: Path to file to save."
|
|
|
},
|
|
},
|
|
|
'set_active': {
|
|
'set_active': {
|
|
|
- 'fcn': self.collection.set_active,
|
|
|
|
|
|
|
+ 'fcn': set_active,
|
|
|
'help': "Sets a FlatCAM object as active.\n > set_active <name>\n name: Name of the object."
|
|
'help': "Sets a FlatCAM object as active.\n > set_active <name>\n name: Name of the object."
|
|
|
},
|
|
},
|
|
|
'get_names': {
|
|
'get_names': {
|
|
@@ -1569,8 +1794,8 @@ class App(QtCore.QObject):
|
|
|
'isolate': {
|
|
'isolate': {
|
|
|
'fcn': isolate,
|
|
'fcn': isolate,
|
|
|
'help': "Creates isolation routing geometry for the given Gerber.\n" +
|
|
'help': "Creates isolation routing geometry for the given Gerber.\n" +
|
|
|
- "> isolate <name> [dia [passes [overlap]]]\n" +
|
|
|
|
|
- " name: Name if the object\n"
|
|
|
|
|
|
|
+ "> isolate <name> [-dia <d>] [-passes <p>] [-overlap <o>]\n" +
|
|
|
|
|
+ " name: Name of the object\n"
|
|
|
" dia: Tool diameter\n passes: # of tool width\n" +
|
|
" dia: Tool diameter\n passes: # of tool width\n" +
|
|
|
" overlap: Fraction of tool diameter to overlap passes"
|
|
" overlap: Fraction of tool diameter to overlap passes"
|
|
|
},
|
|
},
|
|
@@ -1591,12 +1816,96 @@ class App(QtCore.QObject):
|
|
|
'plot': {
|
|
'plot': {
|
|
|
'fcn': self.plot_all,
|
|
'fcn': self.plot_all,
|
|
|
'help': 'Updates the plot on the user interface'
|
|
'help': 'Updates the plot on the user interface'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'cncjob': {
|
|
|
|
|
+ 'fcn': cncjob,
|
|
|
|
|
+ 'help': 'Generates a CNC Job from a Geometry Object.\n' +
|
|
|
|
|
+ '> cncjob <name> [-z_cut <c>] [-z_move <m>] [-feedrate <f>] [-tooldia <t>] [-outname <n>]\n' +
|
|
|
|
|
+ ' name: Name of the source object\n' +
|
|
|
|
|
+ ' z_cut: Z-axis cutting position\n' +
|
|
|
|
|
+ ' z_move: Z-axis moving position\n' +
|
|
|
|
|
+ ' feedrate: Moving speed when cutting\n' +
|
|
|
|
|
+ ' tooldia: Tool diameter to show on screen\n' +
|
|
|
|
|
+ ' outname: Name of the output object'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'write_gcode': {
|
|
|
|
|
+ 'fcn': write_gcode,
|
|
|
|
|
+ 'help': 'Saves G-code of a CNC Job object to file.\n' +
|
|
|
|
|
+ '> write_gcode <name> <filename>\n' +
|
|
|
|
|
+ ' name: Source CNC Job object\n' +
|
|
|
|
|
+ ' filename: Output filename'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'paint_poly': {
|
|
|
|
|
+ 'fcn': paint_poly,
|
|
|
|
|
+ 'help': 'Creates a geometry object with toolpath to cover the inside of a polygon.\n' +
|
|
|
|
|
+ '> paint_poly <name> <inside_pt_x> <inside_pt_y> <tooldia> <overlap>\n' +
|
|
|
|
|
+ ' name: Name of the sourge geometry object.\n' +
|
|
|
|
|
+ ' inside_pt_x, inside_pt_y: Coordinates of a point inside the polygon.\n' +
|
|
|
|
|
+ ' tooldia: Diameter of the tool to be used.\n' +
|
|
|
|
|
+ ' overlap: Fraction of the tool diameter to overlap cuts.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'new_geometry': {
|
|
|
|
|
+ 'fcn': lambda name: self.new_object('geometry', str(name), lambda x, y: None),
|
|
|
|
|
+ 'help': 'Creates a new empty geometry object.\n' +
|
|
|
|
|
+ '> new_geometry <name>\n' +
|
|
|
|
|
+ ' name: New object name'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'add_poly': {
|
|
|
|
|
+ 'fcn': add_poly,
|
|
|
|
|
+ 'help': 'Creates a polygon in the given Geometry object.\n' +
|
|
|
|
|
+ '> create_poly <name> <x0> <y0> <x1> <y1> <x2> <y2> [x3 y3 [...]]\n' +
|
|
|
|
|
+ ' name: Name of the geometry object to which to append the polygon.\n' +
|
|
|
|
|
+ ' xi, yi: Coordinates of points in the polygon.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'delete': {
|
|
|
|
|
+ 'fcn': delete,
|
|
|
|
|
+ 'help': 'Deletes the give object.\n' +
|
|
|
|
|
+ '> delete <name>\n' +
|
|
|
|
|
+ ' name: Name of the object to delete.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'geo_union': {
|
|
|
|
|
+ 'fcn': geo_union,
|
|
|
|
|
+ 'help': 'Runs a union operation (addition) on the components ' +
|
|
|
|
|
+ 'of the geometry object. For example, if it contains ' +
|
|
|
|
|
+ '2 intersecting polygons, this opperation adds them into' +
|
|
|
|
|
+ 'a single larger polygon.\n' +
|
|
|
|
|
+ '> geo_union <name>\n' +
|
|
|
|
|
+ ' name: Name of the geometry object.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'add_rect': {
|
|
|
|
|
+ 'fcn': add_rectangle,
|
|
|
|
|
+ 'help': 'Creates a rectange in the given Geometry object.\n' +
|
|
|
|
|
+ '> add_rect <name> <botleft_x> <botleft_y> <topright_x> <topright_y>\n' +
|
|
|
|
|
+ ' name: Name of the geometry object to which to append the rectangle.\n' +
|
|
|
|
|
+ ' botleft_x, botleft_y: Coordinates of the bottom left corner.\n' +
|
|
|
|
|
+ ' topright_x, topright_y Coordinates of the top right corner.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'add_circle': {
|
|
|
|
|
+ 'fcn': add_circle,
|
|
|
|
|
+ 'help': 'Creates a circle in the given Geometry object.\n' +
|
|
|
|
|
+ '> add_circle <name> <center_x> <center_y> <radius>\n' +
|
|
|
|
|
+ ' name: Name of the geometry object to which to append the circle.\n' +
|
|
|
|
|
+ ' center_x, center_y: Coordinates of the center of the circle.\n' +
|
|
|
|
|
+ ' radius: Radius of the circle.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'make_docs': {
|
|
|
|
|
+ 'fcn': make_docs,
|
|
|
|
|
+ 'help': 'Prints command rererence in reStructuredText format.'
|
|
|
|
|
+ },
|
|
|
|
|
+ 'follow': {
|
|
|
|
|
+ 'fcn': follow,
|
|
|
|
|
+ 'help': 'Creates a geometry object following gerber paths.\n' +
|
|
|
|
|
+ '> follow <name> [-outname <oname>]\n' +
|
|
|
|
|
+ ' name: Name of the gerber object.\n' +
|
|
|
|
|
+ ' outname: Name of the output geometry object.'
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ # Add commands to the tcl interpreter
|
|
|
for cmd in commands:
|
|
for cmd in commands:
|
|
|
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
|
|
self.tcl.createcommand(cmd, commands[cmd]['fcn'])
|
|
|
|
|
|
|
|
|
|
+ # Make the tcl puts function return instead of print to stdout
|
|
|
self.tcl.eval('''
|
|
self.tcl.eval('''
|
|
|
rename puts original_puts
|
|
rename puts original_puts
|
|
|
proc puts {args} {
|
|
proc puts {args} {
|
|
@@ -1608,8 +1917,6 @@ class App(QtCore.QObject):
|
|
|
}
|
|
}
|
|
|
''')
|
|
''')
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
def setup_recent_items(self):
|
|
def setup_recent_items(self):
|
|
|
self.log.debug("setup_recent_items()")
|
|
self.log.debug("setup_recent_items()")
|
|
|
|
|
|
|
@@ -1693,9 +2000,11 @@ class App(QtCore.QObject):
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
self.log.debug("version_check()")
|
|
self.log.debug("version_check()")
|
|
|
|
|
+ full_url = App.version_url + "?s=" + str(self.defaults['serial']) + "&v=" + str(self.version)
|
|
|
|
|
+ App.log.debug("Checking for updates @ %s" % full_url)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
- f = urllib.urlopen(App.version_url)
|
|
|
|
|
|
|
+ f = urllib.urlopen(full_url)
|
|
|
except:
|
|
except:
|
|
|
App.log.warning("Failed checking for latest version. Could not connect.")
|
|
App.log.warning("Failed checking for latest version. Could not connect.")
|
|
|
self.inform.emit("Failed checking for latest version. Could not connect.")
|
|
self.inform.emit("Failed checking for latest version. Could not connect.")
|
|
@@ -1703,9 +2012,10 @@ class App(QtCore.QObject):
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
data = json.load(f)
|
|
data = json.load(f)
|
|
|
- except:
|
|
|
|
|
|
|
+ except Exception, e:
|
|
|
App.log.error("Could not parse information about latest version.")
|
|
App.log.error("Could not parse information about latest version.")
|
|
|
self.inform.emit("Could not parse information about latest version.")
|
|
self.inform.emit("Could not parse information about latest version.")
|
|
|
|
|
+ App.log.debug("json.load(): %s" % str(e))
|
|
|
f.close()
|
|
f.close()
|
|
|
return
|
|
return
|
|
|
|
|
|