Support for LPD and LPC in Gerber. Major changes in Gerber parser.
This commit is contained in:
246
FlatCAM.py
246
FlatCAM.py
@@ -16,6 +16,7 @@ from gi.repository import GdkPixbuf
|
|||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
|
import traceback
|
||||||
|
|
||||||
import matplotlib
|
import matplotlib
|
||||||
from matplotlib.figure import Figure
|
from matplotlib.figure import Figure
|
||||||
@@ -29,12 +30,15 @@ import urllib
|
|||||||
import copy
|
import copy
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from shapely import speedups
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
## Imports part of FlatCAM ##
|
## Imports part of FlatCAM ##
|
||||||
########################################
|
########################################
|
||||||
from camlib import *
|
from camlib import *
|
||||||
from FlatCAMObj import *
|
from FlatCAMObj import *
|
||||||
from FlatCAMWorker import Worker
|
from FlatCAMWorker import Worker
|
||||||
|
from FlatCAMException import *
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
@@ -55,6 +59,9 @@ class App:
|
|||||||
:rtype: App
|
:rtype: App
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if speedups.available:
|
||||||
|
speedups.enable()
|
||||||
|
|
||||||
# Needed to interact with the GUI from other threads.
|
# Needed to interact with the GUI from other threads.
|
||||||
GObject.threads_init()
|
GObject.threads_init()
|
||||||
|
|
||||||
@@ -185,13 +192,10 @@ class App:
|
|||||||
def somethreadfunc(app_obj):
|
def somethreadfunc(app_obj):
|
||||||
print "Hello World!"
|
print "Hello World!"
|
||||||
|
|
||||||
self.message_dialog("Starting", "The best program is starting")
|
|
||||||
|
|
||||||
t = threading.Thread(target=somethreadfunc, args=(self,))
|
t = threading.Thread(target=somethreadfunc, args=(self,))
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
## START ##
|
## START ##
|
||||||
########################################
|
########################################
|
||||||
@@ -203,14 +207,18 @@ class App:
|
|||||||
self.window.set_default_size(900, 600)
|
self.window.set_default_size(900, 600)
|
||||||
self.window.show_all()
|
self.window.show_all()
|
||||||
|
|
||||||
def message_dialog(self, title, message, type="info"):
|
def message_dialog(self, title, message, kind="info"):
|
||||||
types = {"info": Gtk.MessageType.INFO,
|
types = {"info": Gtk.MessageType.INFO,
|
||||||
"warn": Gtk.MessageType.WARNING,
|
"warn": Gtk.MessageType.WARNING,
|
||||||
"error": Gtk.MessageType.ERROR}
|
"error": Gtk.MessageType.ERROR}
|
||||||
dlg = Gtk.MessageDialog(self.window, 0, types[type], Gtk.ButtonsType.OK, title)
|
dlg = Gtk.MessageDialog(self.window, 0, types[kind], Gtk.ButtonsType.OK, title)
|
||||||
dlg.format_secondary_text(message)
|
dlg.format_secondary_text(message)
|
||||||
dlg.run()
|
|
||||||
dlg.destroy()
|
def lifecycle():
|
||||||
|
dlg.run()
|
||||||
|
dlg.destroy()
|
||||||
|
|
||||||
|
GLib.idle_add(lifecycle)
|
||||||
|
|
||||||
def question_dialog(self, title, message):
|
def question_dialog(self, title, message):
|
||||||
label = Gtk.Label(message)
|
label = Gtk.Label(message)
|
||||||
@@ -879,6 +887,158 @@ class App:
|
|||||||
|
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
|
def open_gerber(self, filename):
|
||||||
|
"""
|
||||||
|
Opens a Gerber file, parses it and creates a new object for
|
||||||
|
it in the program. Thread-safe.
|
||||||
|
|
||||||
|
:param filename: Gerber file filename
|
||||||
|
:type filename: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Gerber ..."))
|
||||||
|
|
||||||
|
# How the object should be initialized
|
||||||
|
def obj_init(gerber_obj, app_obj):
|
||||||
|
assert isinstance(gerber_obj, FlatCAMGerber)
|
||||||
|
|
||||||
|
# Opening the file happens here
|
||||||
|
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
|
||||||
|
gerber_obj.parse_file(filename)
|
||||||
|
|
||||||
|
# Further parsing
|
||||||
|
GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
|
||||||
|
#gerber_obj.create_geometry()
|
||||||
|
gerber_obj.solid_geometry = gerber_obj.otf_geometry
|
||||||
|
GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
|
||||||
|
|
||||||
|
# Object name
|
||||||
|
name = filename.split('/')[-1].split('\\')[-1]
|
||||||
|
|
||||||
|
# New object creation and file processing
|
||||||
|
try:
|
||||||
|
self.new_object("gerber", name, obj_init)
|
||||||
|
except:
|
||||||
|
e = sys.exc_info()
|
||||||
|
print "ERROR:", e[0]
|
||||||
|
traceback.print_exc()
|
||||||
|
self.message_dialog("Failed to create Gerber Object",
|
||||||
|
"Attempting to create a FlatCAM Gerber Object from " +
|
||||||
|
"Gerber file failed during processing:\n" +
|
||||||
|
str(e[0]) + " " + str(e[1]), kind="error")
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
|
||||||
|
self.collection.delete_active()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Register recent file
|
||||||
|
self.register_recent("gerber", filename)
|
||||||
|
|
||||||
|
# GUI feedback
|
||||||
|
self.info("Opened: " + filename)
|
||||||
|
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
|
||||||
|
|
||||||
|
def open_excellon(self, filename):
|
||||||
|
"""
|
||||||
|
Opens an Excellon file, parses it and creates a new object for
|
||||||
|
it in the program. Thread-safe.
|
||||||
|
|
||||||
|
:param filename: Excellon file filename
|
||||||
|
:type filename: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Excellon ..."))
|
||||||
|
|
||||||
|
# How the object should be initialized
|
||||||
|
def obj_init(excellon_obj, app_obj):
|
||||||
|
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
|
||||||
|
excellon_obj.parse_file(filename)
|
||||||
|
excellon_obj.create_geometry()
|
||||||
|
GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
|
||||||
|
|
||||||
|
# Object name
|
||||||
|
name = filename.split('/')[-1].split('\\')[-1]
|
||||||
|
|
||||||
|
# New object creation and file processing
|
||||||
|
try:
|
||||||
|
self.new_object("excellon", name, obj_init)
|
||||||
|
except:
|
||||||
|
e = sys.exc_info()
|
||||||
|
print "ERROR:", e[0]
|
||||||
|
self.message_dialog("Failed to create Excellon Object",
|
||||||
|
"Attempting to create a FlatCAM Excellon Object from " +
|
||||||
|
"Excellon file failed during processing:\n" +
|
||||||
|
str(e[0]) + " " + str(e[1]), kind="error")
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
|
||||||
|
self.collection.delete_active()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Register recent file
|
||||||
|
self.register_recent("excellon", filename)
|
||||||
|
|
||||||
|
# GUI feedback
|
||||||
|
self.info("Opened: " + filename)
|
||||||
|
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
|
||||||
|
|
||||||
|
def open_gcode(self, filename):
|
||||||
|
"""
|
||||||
|
Opens a G-gcode file, parses it and creates a new object for
|
||||||
|
it in the program. Thread-safe.
|
||||||
|
|
||||||
|
:param filename: G-code file filename
|
||||||
|
:type filename: str
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
# How the object should be initialized
|
||||||
|
def obj_init(job_obj, app_obj_):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type app_obj_: App
|
||||||
|
"""
|
||||||
|
assert isinstance(app_obj_, App)
|
||||||
|
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.1, "Opening G-Code ..."))
|
||||||
|
|
||||||
|
f = open(filename)
|
||||||
|
gcode = f.read()
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
job_obj.gcode = gcode
|
||||||
|
|
||||||
|
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.2, "Parsing ..."))
|
||||||
|
job_obj.gcode_parse()
|
||||||
|
|
||||||
|
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Creating geometry ..."))
|
||||||
|
job_obj.create_geometry()
|
||||||
|
|
||||||
|
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Plotting ..."))
|
||||||
|
|
||||||
|
# Object name
|
||||||
|
name = filename.split('/')[-1].split('\\')[-1]
|
||||||
|
|
||||||
|
# New object creation and file processing
|
||||||
|
try:
|
||||||
|
self.new_object("cncjob", name, obj_init)
|
||||||
|
except:
|
||||||
|
e = sys.exc_info()
|
||||||
|
print "ERROR:", e[0]
|
||||||
|
self.message_dialog("Failed to create CNCJob Object",
|
||||||
|
"Attempting to create a FlatCAM CNCJob Object from " +
|
||||||
|
"G-Code file failed during processing:\n" +
|
||||||
|
str(e[0]) + " " + str(e[1]), kind="error")
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
|
||||||
|
self.collection.delete_active()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Register recent file
|
||||||
|
self.register_recent("cncjob", filename)
|
||||||
|
|
||||||
|
# GUI feedback
|
||||||
|
self.info("Opened: " + filename)
|
||||||
|
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
||||||
|
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
## EVENT HANDLERS ##
|
## EVENT HANDLERS ##
|
||||||
########################################
|
########################################
|
||||||
@@ -1980,29 +2140,6 @@ class App:
|
|||||||
self.info("Save cancelled.") # print("Cancel clicked")
|
self.info("Save cancelled.") # print("Cancel clicked")
|
||||||
dialog.destroy()
|
dialog.destroy()
|
||||||
|
|
||||||
def open_gerber(self, filename):
|
|
||||||
GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Gerber ..."))
|
|
||||||
|
|
||||||
def obj_init(gerber_obj, app_obj):
|
|
||||||
assert isinstance(gerber_obj, FlatCAMGerber)
|
|
||||||
|
|
||||||
# Opening the file happens here
|
|
||||||
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
|
|
||||||
gerber_obj.parse_file(filename)
|
|
||||||
|
|
||||||
# Further parsing
|
|
||||||
GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Creating Geometry ..."))
|
|
||||||
gerber_obj.create_geometry()
|
|
||||||
GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
|
|
||||||
|
|
||||||
name = filename.split('/')[-1].split('\\')[-1]
|
|
||||||
self.new_object("gerber", name, obj_init)
|
|
||||||
self.register_recent("gerber", filename)
|
|
||||||
|
|
||||||
self.info("Opened: " + filename)
|
|
||||||
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
|
||||||
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, "Idle"))
|
|
||||||
|
|
||||||
def on_fileopengerber(self, param):
|
def on_fileopengerber(self, param):
|
||||||
"""
|
"""
|
||||||
Callback for menu item File->Open Gerber. Defines a function that is then passed
|
Callback for menu item File->Open Gerber. Defines a function that is then passed
|
||||||
@@ -2015,23 +2152,6 @@ class App:
|
|||||||
|
|
||||||
self.file_chooser_action(lambda ao, filename: self.open_gerber(filename))
|
self.file_chooser_action(lambda ao, filename: self.open_gerber(filename))
|
||||||
|
|
||||||
def open_excellon(self, filename):
|
|
||||||
GLib.idle_add(lambda: self.set_progress_bar(0.1, "Opening Excellon ..."))
|
|
||||||
|
|
||||||
def obj_init(excellon_obj, app_obj):
|
|
||||||
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Parsing ..."))
|
|
||||||
excellon_obj.parse_file(filename)
|
|
||||||
excellon_obj.create_geometry()
|
|
||||||
GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Plotting ..."))
|
|
||||||
|
|
||||||
name = filename.split('/')[-1].split('\\')[-1]
|
|
||||||
self.new_object("excellon", name, obj_init)
|
|
||||||
self.register_recent("excellon", filename)
|
|
||||||
|
|
||||||
self.info("Opened: " + filename)
|
|
||||||
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
|
||||||
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
|
|
||||||
|
|
||||||
def on_fileopenexcellon(self, param):
|
def on_fileopenexcellon(self, param):
|
||||||
"""
|
"""
|
||||||
Callback for menu item File->Open Excellon. Defines a function that is then passed
|
Callback for menu item File->Open Excellon. Defines a function that is then passed
|
||||||
@@ -2044,38 +2164,6 @@ class App:
|
|||||||
|
|
||||||
self.file_chooser_action(lambda ao, filename: self.open_excellon(filename))
|
self.file_chooser_action(lambda ao, filename: self.open_excellon(filename))
|
||||||
|
|
||||||
def open_gcode(self, filename):
|
|
||||||
|
|
||||||
def obj_init(job_obj, app_obj_):
|
|
||||||
"""
|
|
||||||
|
|
||||||
:type app_obj_: App
|
|
||||||
"""
|
|
||||||
assert isinstance(app_obj_, App)
|
|
||||||
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.1, "Opening G-Code ..."))
|
|
||||||
|
|
||||||
f = open(filename)
|
|
||||||
gcode = f.read()
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
job_obj.gcode = gcode
|
|
||||||
|
|
||||||
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.2, "Parsing ..."))
|
|
||||||
job_obj.gcode_parse()
|
|
||||||
|
|
||||||
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Creating geometry ..."))
|
|
||||||
job_obj.create_geometry()
|
|
||||||
|
|
||||||
GLib.idle_add(lambda: app_obj_.set_progress_bar(0.6, "Plotting ..."))
|
|
||||||
|
|
||||||
name = filename.split('/')[-1].split('\\')[-1]
|
|
||||||
self.new_object("cncjob", name, obj_init)
|
|
||||||
self.register_recent("cncjob", filename)
|
|
||||||
|
|
||||||
self.info("Opened: " + filename)
|
|
||||||
GLib.idle_add(lambda: self.set_progress_bar(1.0, "Done!"))
|
|
||||||
GLib.timeout_add_seconds(1, lambda: self.set_progress_bar(0.0, ""))
|
|
||||||
|
|
||||||
def on_fileopengcode(self, param):
|
def on_fileopengcode(self, param):
|
||||||
"""
|
"""
|
||||||
Callback for menu item File->Open G-Code. Defines a function that is then passed
|
Callback for menu item File->Open G-Code. Defines a function that is then passed
|
||||||
|
|||||||
71
FlatCAM.ui
71
FlatCAM.ui
@@ -137,77 +137,6 @@ THE SOFTWARE.</property>
|
|||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="stock">gtk-open</property>
|
<property name="stock">gtk-open</property>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkDialog" id="question_dialog">
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="border_width">5</property>
|
|
||||||
<property name="type_hint">dialog</property>
|
|
||||||
<child internal-child="vbox">
|
|
||||||
<object class="GtkBox" id="question_box">
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<property name="spacing">2</property>
|
|
||||||
<child internal-child="action_area">
|
|
||||||
<object class="GtkButtonBox" id="dialog-action_area1">
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="layout_style">end</property>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="pack_type">end</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox" id="box40">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkImage" id="image22">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="valign">start</property>
|
|
||||||
<property name="pixbuf">share/warning.png</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="label_warning">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="margin_left">12</property>
|
|
||||||
<property name="margin_right">12</property>
|
|
||||||
<property name="margin_top">12</property>
|
|
||||||
<property name="margin_bottom">12</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="label" translatable="yes">label</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
|
<object class="GtkOffscreenWindow" id="offscreenwindow_dblsided">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
@@ -304,6 +304,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
|
|||||||
[poly['polygon'] for poly in self.regions] + \
|
[poly['polygon'] for poly in self.regions] + \
|
||||||
self.flash_geometry
|
self.flash_geometry
|
||||||
|
|
||||||
|
# Make sure geometry is iterable.
|
||||||
|
try:
|
||||||
|
_ = iter(geometry)
|
||||||
|
except TypeError:
|
||||||
|
geometry = [geometry]
|
||||||
|
|
||||||
if self.options["multicolored"]:
|
if self.options["multicolored"]:
|
||||||
linespec = '-'
|
linespec = '-'
|
||||||
else:
|
else:
|
||||||
|
|||||||
212
camlib.py
212
camlib.py
@@ -651,6 +651,9 @@ class Gerber (Geometry):
|
|||||||
# Geometry from flashes
|
# Geometry from flashes
|
||||||
self.flash_geometry = []
|
self.flash_geometry = []
|
||||||
|
|
||||||
|
# On-the-fly geometry. Initialized to an empty polygon
|
||||||
|
self.otf_geometry = Polygon()
|
||||||
|
|
||||||
# Aperture Macros
|
# Aperture Macros
|
||||||
# TODO: Make sure these can be serialized
|
# TODO: Make sure these can be serialized
|
||||||
self.aperture_macros = {}
|
self.aperture_macros = {}
|
||||||
@@ -686,16 +689,20 @@ class Gerber (Geometry):
|
|||||||
# May begin with G54 but that is deprecated
|
# May begin with G54 but that is deprecated
|
||||||
self.tool_re = re.compile(r'^(?:G54)?D(\d\d+)\*$')
|
self.tool_re = re.compile(r'^(?:G54)?D(\d\d+)\*$')
|
||||||
|
|
||||||
# G01 - Linear interpolation plus flashes
|
# G01... - Linear interpolation plus flashes with coordinates
|
||||||
# Operation code (D0x) missing is deprecated... oh well I will support it.
|
# Operation code (D0x) missing is deprecated... oh well I will support it.
|
||||||
self.lin_re = re.compile(r'^(?:G0?(1))?(?:X(-?\d+))?(?:Y(-?\d+))?(?:D0?([123]))?\*$')
|
self.lin_re = re.compile(r'^(?:G0?(1))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))?[XY][^DIJ]*(?:D0?([123]))?\*$')
|
||||||
|
|
||||||
self.setlin_re = re.compile(r'^(?:G0?1)\*')
|
#
|
||||||
|
self.opcode_re = re.compile(r'^D0?([123])\*$')
|
||||||
|
|
||||||
# G02/3 - Circular interpolation
|
# G02/3... - Circular interpolation with coordinates
|
||||||
# 2-clockwise, 3-counterclockwise
|
# 2-clockwise, 3-counterclockwise
|
||||||
self.circ_re = re.compile(r'^(?:G0?([23]))?(?:X(-?\d+))?(?:Y(-?\d+))' +
|
# Operation code (D0x) missing is deprecated... oh well I will support it.
|
||||||
'?(?:I(-?\d+))?(?:J(-?\d+))?D0([12])\*$')
|
# Optional start with G02 or G03, optional end with D01 or D02 with
|
||||||
|
# optional coordinates but at least one in any order.
|
||||||
|
self.circ_re = re.compile(r'^(?:G0?([23]))?(?=.*X(-?\d+))?(?=.*Y(-?\d+))' +
|
||||||
|
'?(?=.*I(-?\d+))?(?=.*J(-?\d+))?[XYIJ][^D]*(?:D0([12]))?\*$')
|
||||||
|
|
||||||
# G01/2/3 Occurring without coordinates
|
# G01/2/3 Occurring without coordinates
|
||||||
self.interp_re = re.compile(r'^(?:G0?([123]))\*')
|
self.interp_re = re.compile(r'^(?:G0?([123]))\*')
|
||||||
@@ -1038,6 +1045,7 @@ class Gerber (Geometry):
|
|||||||
current_y = None
|
current_y = None
|
||||||
|
|
||||||
# Absolute or Relative/Incremental coordinates
|
# Absolute or Relative/Incremental coordinates
|
||||||
|
# Not implemented
|
||||||
absolute = True
|
absolute = True
|
||||||
|
|
||||||
# How to interpret circular interpolation: SINGLE or MULTI
|
# How to interpret circular interpolation: SINGLE or MULTI
|
||||||
@@ -1046,6 +1054,12 @@ class Gerber (Geometry):
|
|||||||
# Indicates we are parsing an aperture macro
|
# Indicates we are parsing an aperture macro
|
||||||
current_macro = None
|
current_macro = None
|
||||||
|
|
||||||
|
# Indicates the current polarity: D-Dark, C-Clear
|
||||||
|
current_polarity = 'D'
|
||||||
|
|
||||||
|
# If a region is being defined
|
||||||
|
making_region = False
|
||||||
|
|
||||||
#### Parsing starts here ####
|
#### Parsing starts here ####
|
||||||
line_num = 0
|
line_num = 0
|
||||||
for gline in glines:
|
for gline in glines:
|
||||||
@@ -1107,19 +1121,39 @@ class Gerber (Geometry):
|
|||||||
path.append([current_x, current_y])
|
path.append([current_x, current_y])
|
||||||
last_path_aperture = current_aperture
|
last_path_aperture = current_aperture
|
||||||
|
|
||||||
# Pen up: finish path
|
|
||||||
elif current_operation_code == 2:
|
elif current_operation_code == 2:
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
if last_path_aperture is None:
|
|
||||||
print "Warning: No aperture defined for curent path. (%d)" % line_num
|
# self.paths.append({"linestring": LineString(path),
|
||||||
self.paths.append({"linestring": LineString(path),
|
# "aperture": last_path_aperture})
|
||||||
"aperture": last_path_aperture})
|
|
||||||
|
# --- OTF ---
|
||||||
|
if making_region:
|
||||||
|
geo = Polygon(path)
|
||||||
|
else:
|
||||||
|
if last_path_aperture is None:
|
||||||
|
print "Warning: No aperture defined for curent path. (%d)" % line_num
|
||||||
|
width = self.apertures[last_path_aperture]["size"]
|
||||||
|
geo = LineString(path).buffer(width/2)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(geo)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(geo)
|
||||||
|
|
||||||
path = [[current_x, current_y]] # Start new path
|
path = [[current_x, current_y]] # Start new path
|
||||||
|
|
||||||
# Flash
|
# Flash
|
||||||
elif current_operation_code == 3:
|
elif current_operation_code == 3:
|
||||||
self.flashes.append({"loc": Point([current_x, current_y]),
|
# self.flashes.append({"loc": Point([current_x, current_y]),
|
||||||
"aperture": current_aperture})
|
# "aperture": current_aperture})
|
||||||
|
|
||||||
|
# --- OTF ---
|
||||||
|
flash = Gerber.create_flash_geometry(Point([current_x, current_y]),
|
||||||
|
self.apertures[current_aperture])
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(flash)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(flash)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1168,8 +1202,17 @@ class Gerber (Geometry):
|
|||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
if last_path_aperture is None:
|
if last_path_aperture is None:
|
||||||
print "Warning: No aperture defined for curent path. (%d)" % line_num
|
print "Warning: No aperture defined for curent path. (%d)" % line_num
|
||||||
self.paths.append({"linestring": LineString(path),
|
# self.paths.append({"linestring": LineString(path),
|
||||||
"aperture": last_path_aperture})
|
# "aperture": last_path_aperture})
|
||||||
|
|
||||||
|
# --- OTF ---
|
||||||
|
width = self.apertures[last_path_aperture]["size"]
|
||||||
|
buffered = LineString(path).buffer(width/2)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(buffered)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(buffered)
|
||||||
|
|
||||||
current_x = x
|
current_x = x
|
||||||
current_y = y
|
current_y = y
|
||||||
path = [[current_x, current_y]] # Start new path
|
path = [[current_x, current_y]] # Start new path
|
||||||
@@ -1204,6 +1247,19 @@ class Gerber (Geometry):
|
|||||||
if quadrant_mode == 'SINGLE':
|
if quadrant_mode == 'SINGLE':
|
||||||
print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num
|
print "Warning: Single quadrant arc are not implemented yet. (%d)" % line_num
|
||||||
|
|
||||||
|
### Operation code alone
|
||||||
|
match = self.opcode_re.search(gline)
|
||||||
|
if match:
|
||||||
|
current_operation_code = int(match.group(1))
|
||||||
|
if current_operation_code == 3:
|
||||||
|
flash = Gerber.create_flash_geometry(Point(path[-1]),
|
||||||
|
self.apertures[current_aperture])
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(flash)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(flash)
|
||||||
|
continue
|
||||||
|
|
||||||
### G74/75* - Single or multiple quadrant arcs
|
### G74/75* - Single or multiple quadrant arcs
|
||||||
match = self.quad_re.search(gline)
|
match = self.quad_re.search(gline)
|
||||||
if match:
|
if match:
|
||||||
@@ -1213,20 +1269,49 @@ class Gerber (Geometry):
|
|||||||
quadrant_mode = 'MULTI'
|
quadrant_mode = 'MULTI'
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
### G36* - Begin region
|
||||||
|
if self.regionon_re.search(gline):
|
||||||
|
if len(path) > 1:
|
||||||
|
# Take care of what is left in the path
|
||||||
|
width = self.apertures[last_path_aperture]["size"]
|
||||||
|
geo = LineString(path).buffer(width/2)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(geo)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(geo)
|
||||||
|
path = [path[-1]]
|
||||||
|
|
||||||
|
making_region = True
|
||||||
|
continue
|
||||||
|
|
||||||
### G37* - End region
|
### G37* - End region
|
||||||
if self.regionoff_re.search(gline):
|
if self.regionoff_re.search(gline):
|
||||||
|
making_region = False
|
||||||
|
|
||||||
# Only one path defines region?
|
# Only one path defines region?
|
||||||
|
# This can happen if D02 happened before G37 and
|
||||||
|
# is not and error.
|
||||||
if len(path) < 3:
|
if len(path) < 3:
|
||||||
print "ERROR: Path contains less than 3 points:"
|
# print "ERROR: Path contains less than 3 points:"
|
||||||
print path
|
# print path
|
||||||
print "Line (%d): " % line_num, gline
|
# print "Line (%d): " % line_num, gline
|
||||||
path = []
|
# path = []
|
||||||
|
#path = [[current_x, current_y]]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# For regions we may ignore an aperture that is None
|
# For regions we may ignore an aperture that is None
|
||||||
self.regions.append({"polygon": Polygon(path),
|
# self.regions.append({"polygon": Polygon(path),
|
||||||
"aperture": last_path_aperture})
|
# "aperture": last_path_aperture})
|
||||||
#path = []
|
|
||||||
|
# --- OTF ---
|
||||||
|
region = Polygon(path)
|
||||||
|
if not region.is_valid:
|
||||||
|
region = region.buffer(0)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(region)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(region)
|
||||||
|
|
||||||
path = [[current_x, current_y]] # Start new path
|
path = [[current_x, current_y]] # Start new path
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -1252,6 +1337,22 @@ class Gerber (Geometry):
|
|||||||
current_aperture = match.group(1)
|
current_aperture = match.group(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
### Polarity change
|
||||||
|
# Example: %LPD*% or %LPC*%
|
||||||
|
match = self.lpol_re.search(gline)
|
||||||
|
if match:
|
||||||
|
if len(path) > 1 and current_polarity != match.group(1):
|
||||||
|
|
||||||
|
width = self.apertures[last_path_aperture]["size"]
|
||||||
|
geo = LineString(path).buffer(width/2)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(geo)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(geo)
|
||||||
|
path = [path[-1]]
|
||||||
|
current_polarity = match.group(1)
|
||||||
|
continue
|
||||||
|
|
||||||
### Number format
|
### Number format
|
||||||
# Example: %FSLAX24Y24*%
|
# Example: %FSLAX24Y24*%
|
||||||
# TODO: This is ignoring most of the format. Implement the rest.
|
# TODO: This is ignoring most of the format. Implement the rest.
|
||||||
@@ -1297,8 +1398,71 @@ class Gerber (Geometry):
|
|||||||
|
|
||||||
if len(path) > 1:
|
if len(path) > 1:
|
||||||
# EOF, create shapely LineString if something still in path
|
# EOF, create shapely LineString if something still in path
|
||||||
self.paths.append({"linestring": LineString(path),
|
# self.paths.append({"linestring": LineString(path),
|
||||||
"aperture": last_path_aperture})
|
# "aperture": last_path_aperture})
|
||||||
|
|
||||||
|
width = self.apertures[last_path_aperture]["size"]
|
||||||
|
geo = LineString(path).buffer(width/2)
|
||||||
|
if current_polarity == 'D':
|
||||||
|
self.otf_geometry = self.otf_geometry.union(geo)
|
||||||
|
else:
|
||||||
|
self.otf_geometry = self.otf_geometry.difference(geo)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_flash_geometry(location, aperture):
|
||||||
|
|
||||||
|
if type(location) == list:
|
||||||
|
location = Point(location)
|
||||||
|
|
||||||
|
if aperture['type'] == 'C': # Circles
|
||||||
|
return location.buffer(aperture['size']/2)
|
||||||
|
|
||||||
|
if aperture['type'] == 'R': # Rectangles
|
||||||
|
loc = location.coords[0]
|
||||||
|
width = aperture['width']
|
||||||
|
height = aperture['height']
|
||||||
|
minx = loc[0] - width/2
|
||||||
|
maxx = loc[0] + width/2
|
||||||
|
miny = loc[1] - height/2
|
||||||
|
maxy = loc[1] + height/2
|
||||||
|
return shply_box(minx, miny, maxx, maxy)
|
||||||
|
|
||||||
|
if aperture['type'] == 'O': # Obround
|
||||||
|
loc = location.coords[0]
|
||||||
|
width = aperture['width']
|
||||||
|
height = aperture['height']
|
||||||
|
if width > height:
|
||||||
|
p1 = Point(loc[0] + 0.5*(width-height), loc[1])
|
||||||
|
p2 = Point(loc[0] - 0.5*(width-height), loc[1])
|
||||||
|
c1 = p1.buffer(height*0.5)
|
||||||
|
c2 = p2.buffer(height*0.5)
|
||||||
|
else:
|
||||||
|
p1 = Point(loc[0], loc[1] + 0.5*(height-width))
|
||||||
|
p2 = Point(loc[0], loc[1] - 0.5*(height-width))
|
||||||
|
c1 = p1.buffer(width*0.5)
|
||||||
|
c2 = p2.buffer(width*0.5)
|
||||||
|
return cascaded_union([c1, c2]).convex_hull
|
||||||
|
|
||||||
|
if aperture['type'] == 'P': # Regular polygon
|
||||||
|
loc = location.coords[0]
|
||||||
|
diam = aperture['diam']
|
||||||
|
n_vertices = aperture['nVertices']
|
||||||
|
points = []
|
||||||
|
for i in range(0, n_vertices):
|
||||||
|
x = loc[0] + diam * (cos(2 * pi * i / n_vertices))
|
||||||
|
y = loc[1] + diam * (sin(2 * pi * i / n_vertices))
|
||||||
|
points.append((x, y))
|
||||||
|
ply = Polygon(points)
|
||||||
|
if 'rotation' in aperture:
|
||||||
|
ply = affinity.rotate(ply, aperture['rotation'])
|
||||||
|
return ply
|
||||||
|
|
||||||
|
if aperture['type'] == 'AM': # Aperture Macro
|
||||||
|
loc = location.coords[0]
|
||||||
|
flash_geo = aperture['macro'].make_geometry(aperture['modifiers'])
|
||||||
|
return affinity.translate(flash_geo, xoff=loc[0], yoff=loc[1])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def do_flashes(self):
|
def do_flashes(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
[{"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\TFTadapter.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_3.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5_2.fcproj"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\RTWO_fc5.fcproj"}, {"kind": "cncjob", "filename": "Z:\\CNC\\testpcb\\2\\noname-F_Cu_ISOLATION_GCODE3.ngc"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\RTWO1_CNC\\iso_bottom1.gcode"}]
|
[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane_modified.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\WindMills - Bottom Copper 2.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\CC_LOAD_7000164-00_REV_A_copper_top.gbr"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\Example1_copper_bottom_Gndplane.gbr"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\TFTadapter.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles-F_Cu.gtl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\PlacaReles.drl"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\BLDC2003Through.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\PLLs\\RTWO\\Project Outputs for RTWO1\\PCB1.GTL"}]
|
||||||
Reference in New Issue
Block a user