gethub n02
i
mport math | |
from string import join | |
import time | |
import ExcellonReader | |
import GerberReader3 | |
from wxGerberCanvas import GerberCanvas | |
from GerberReader3 import load_file, GerberLayer, GerberData | |
import pyclipper | |
import wx | |
import wx.grid | |
import wx.lib.scrolledpanel | |
import wx.lib.agw.pycollapsiblepane as PCP | |
__author__ = 'Damien, Ben, Thompson' | |
VERSION = "V5.5" | |
DATE = "02/12/15" | |
def sizedToFill(content, margin=0): | |
""" | |
Utility method to add a sizer that fills the whole parent with the content pane | |
:param content: | |
:return: | |
""" | |
sizer = wx.BoxSizer(wx.VERTICAL) | |
if margin == 0: | |
sizer.Add(content, 0, wx.EXPAND) | |
else: | |
sizer.Add(content, 0, wx.EXPAND | wx.ALL, margin) | |
content.GetParent().SetSizer(sizer) | |
class DeviceProfile(): | |
def __init__(self, name, | |
tooldiameter = None, | |
feedrate = None, | |
spindlespeed = None, | |
toolcode = None, | |
coolant = None, | |
zup = None, | |
zdown = None): | |
self.name = name | |
self.tooldiameter = tooldiameter | |
self.feedrate = feedrate | |
self.spindlespeed = spindlespeed | |
self.toolcode = toolcode | |
self.coolant = coolant | |
self.zup = zup | |
self.zdown = zdown | |
class MyPopupMenu(wx.Menu): | |
def __init__(self, lazorApp, pos): | |
wx.Menu.__init__(self) | |
self.pos = pos | |
self.lazorApp = lazorApp | |
""" :type lazorApp: LazorAppFrame """ | |
item = wx.MenuItem(self, wx.NewId(), "Set measuring point") | |
self.AppendItem(item) | |
self.Bind(wx.EVT_MENU, self.OnItem1, item) | |
item = wx.MenuItem(self, wx.NewId(), "Cancel measurement") | |
self.AppendItem(item) | |
self.Bind(wx.EVT_MENU, self.OnItem2, item) | |
self.AppendSeparator() | |
item = wx.MenuItem(self, wx.NewId(),"Set custom origin") | |
self.AppendItem(item) | |
self.Bind(wx.EVT_MENU, self.OnItem3, item) | |
def OnItem1(self, event): | |
self.lazorApp.canvas.startMeasurement(self.pos) | |
def OnItem2(self, event): | |
self.lazorApp.canvas.stopMeasurement() | |
def OnItem3(self, event): | |
self.lazorApp.setCustomOrigin(self.pos) | |
class LazorAppFrame(wx.Frame): | |
""" Main App frame""" | |
deviceProfiles = [DeviceProfile("g: g code file", "0.008", "100", "1000", "1", False, "0.05", "-0.005"), | |
DeviceProfile("l: LaZoR code file", "0.004", "100", "100", "1", False, None, None)] | |
def __init__(self, parent=None): | |
super(LazorAppFrame, self).__init__(parent, title="LaZoR", | |
size=(800, 600), | |
style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE) | |
# Create status bar | |
self.CreateStatusBar() | |
self.GetStatusBar().SetFieldsCount(4) | |
self.SetStatusWidths([-1, 125, 125, 125]) | |
# Setting up the menu. | |
self.loadMenu() | |
# Set up frame contents | |
self.canvas = canvas = GerberCanvas(self, self.GetStatusBar(), 1) # shows gerber data | |
canvas.Bind(wx.EVT_RIGHT_UP, self.OnRightClick, canvas) | |
toolapanel = self.loadToolsPanel() | |
self.layersPanel = layersPanel = LayersPanel(self) | |
layersPanel.loadLayersPanel(None, self.NotifyDataChange) | |
self.sizerH1 = sizerH1 = wx.BoxSizer(wx.HORIZONTAL) | |
sizerH1.Add(toolapanel, 0, wx.EXPAND) | |
sizerH1.Add(canvas, 1, wx.EXPAND) | |
sizerH1.Add(layersPanel, 0, wx.EXPAND) | |
self.SetSizer(sizerH1) | |
self.SetAutoLayout(True) | |
sizerH1.Fit(self) | |
# Initialise variables | |
self.contours = None | |
self.boundarys = None | |
self.pcb_edges = None | |
self.data = None | |
""" :type data: GerberData """ | |
def loadMenu(self): | |
filemenu = wx.Menu() | |
menuAbout = filemenu.Append(wx.ID_ABOUT, "&About"," Information about this program") | |
filemenu.AppendSeparator() | |
menuExit = filemenu.Append(wx.ID_EXIT,"E&xit"," Terminate the program") | |
filemenu2 = wx.Menu() | |
zoomin = filemenu2.Append(wx.ID_ANY, "Zoom In") | |
zoomout = filemenu2.Append(wx.ID_ANY, "Zoom Out") | |
menuFitToView = filemenu2.Append(wx.ID_ANY, "Fit to View") | |
filemenu2.AppendSeparator() | |
self.menuLayerEditor = filemenu2.AppendCheckItem(wx.ID_ANY, "Show/Hide layer editor") | |
filemenu2.Check(self.menuLayerEditor.GetId(), True) | |
# Create the menubar. | |
menuBar = wx.MenuBar() | |
menuBar.Append(filemenu,"&File") | |
menuBar.Append(filemenu2, "&View") | |
self.SetMenuBar(menuBar) | |
# Set events. | |
self.Bind(wx.EVT_MENU, self.OnAbout, menuAbout) | |
self.Bind(wx.EVT_MENU, self.OnExit, menuExit) | |
self.Bind(wx.EVT_MENU, self.OnShowHideLayerEditor, self.menuLayerEditor) | |
self.Bind(wx.EVT_MENU, self.OnZoomIn, zoomin) | |
self.Bind(wx.EVT_MENU, self.OnZoomOut, zoomout) | |
self.Bind(wx.EVT_MENU, self.OnFitToView, menuFitToView) | |
def loadToolsPanel(self): | |
panel = wx.lib.scrolledpanel.ScrolledPanel(self, size=(300, 0)) | |
sizer = wx.BoxSizer(wx.VERTICAL) | |
sizerH1 = wx.BoxSizer(wx.HORIZONTAL) | |
sizerH1.Add(wx.StaticText(panel, -1, "Units: "), 0, wx.FIXED | wx.TOP | wx.RIGHT, 3) | |
self.unitcombo = wx.ComboBox(panel, -1, "in", choices=["mm","in"], style=wx.CB_READONLY) | |
self.Bind(wx.EVT_COMBOBOX, self.hdl) | |
sizerH1.Add(self.unitcombo, 0, wx.FIXED | wx.ALIGN_BOTTOM) | |
self.nativeLabel = wx.StaticText(panel, -1) | |
sizerH1.AddSpacer(10) | |
sizerH1.Add(self.nativeLabel, 0, wx.FIXED | wx.TOP, 3) | |
sizer.Add(sizerH1, 0, wx.EXPAND | wx.ALL, 10) | |
deviceNames = [] | |
for dp in self.deviceProfiles: deviceNames.append(dp.name) | |
self.devicecombo = wx.ComboBox(panel, -1, deviceNames[0], style=wx.CB_READONLY, choices=deviceNames) | |
fontMono = self.devicecombo.GetFont() | |
fontMono = wx.Font(fontMono.GetPointSize(), wx.TELETYPE, | |
fontMono.GetStyle(), | |
fontMono.GetWeight(), fontMono.GetUnderlined()) | |
self.devicecombo.SetFont(fontMono) | |
self.Bind(wx.EVT_COMBOBOX, self.OnDeviceCombo, self.devicecombo) | |
sizerH2 = wx.BoxSizer(wx.HORIZONTAL) | |
sizerH2.Add(wx.StaticText(panel, -1, "Output device: "), 0, wx.EXPAND | wx.TOP | wx.RIGHT, 4) | |
sizerH2.Add(self.devicecombo, 0, wx.FIXED) | |
sizer.Add(sizerH2, 0, wx.EXPAND | wx.ALL, 10) | |
cp = PCP.PyCollapsiblePane(panel, label="Import Data", agwStyle=wx.CP_NO_TLW_RESIZE) | |
self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, cp) | |
self.loadToolsImportPanel(cp.GetPane()) | |
sizer.Add(cp, 0, wx.EXPAND) | |
cp.Expand() | |
cp = PCP.PyCollapsiblePane(panel, label="Isolation Workbench", agwStyle=wx.CP_NO_TLW_RESIZE) | |
self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, cp) | |
self.loadToolsIsolationPanel(cp.GetPane()) | |
sizer.Add(cp, 0, wx.EXPAND) | |
cp = PCP.PyCollapsiblePane(panel, label="Drill Workbench", agwStyle=wx.CP_NO_TLW_RESIZE) | |
self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, cp) | |
self.loadToolsDrillPanel(cp.GetPane()) | |
sizer.Add(cp, 0, wx.EXPAND) | |
cp = PCP.PyCollapsiblePane(panel, label="Edge Workbench", agwStyle=wx.CP_NO_TLW_RESIZE) | |
self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, cp) | |
self.loadToolsEdgePanel(cp.GetPane()) | |
sizer.Add(cp, 0, wx.EXPAND) | |
self.OnDeviceCombo(0) | |
panel.SetSizer(sizer) | |
panel.SetAutoLayout(True) | |
panel.SetupScrolling(False, True, scrollIntoView=False) | |
return panel | |
def loadToolsImportPanel(self, parent): | |
panel = wx.Panel(parent) | |
sizer = wx.BoxSizer(wx.VERTICAL) | |
button1 = wx.Button(panel, -1, "Load Gerber") | |
sizer.Add(button1, 0, wx.EXPAND | wx.ALL, 5) | |
button2 = wx.Button(panel, -1, "Load Edge") | |
sizer.Add(button2, 0, wx.EXPAND | wx.ALL, 5) | |
button3 = wx.Button(panel, -1, "Load Drill") | |
sizer.Add(button3, 0, wx.EXPAND | wx.ALL, 5) | |
button4 = wx.Button(panel, -1, "Clear Drill Data") | |
sizer.Add(button4, 0, wx.EXPAND | wx.ALL, 5) | |
panel.Bind(wx.EVT_BUTTON, self.LoadGerberFile, button1) | |
panel.Bind(wx.EVT_BUTTON, self.LoadEdgeFile, button2) | |
panel.Bind(wx.EVT_BUTTON, self.LoadDrillFile, button3) | |
panel.Bind(wx.EVT_BUTTON, self.ClearDrillData, button4) | |
panel.SetSizer(sizer) | |
sizedToFill(panel) | |
return panel | |
def loadToolsIsolationPanel(self, parent): | |
panel = wx.Panel(parent) | |
sizer = wx.FlexGridSizer(20, 2, 4, 0) | |
sizer.AddGrowableCol(1, 1) | |
sizer.SetFlexibleDirection(wx.VERTICAL) | |
button1 = wx.Button(panel, -1, "Contour") | |
button2 = wx.Button(panel, -1, "Raster") | |
button3 = wx.Button(panel, -1, "Clear") | |
panel.Bind(wx.EVT_BUTTON, self.addContour, button1) | |
panel.Bind(wx.EVT_BUTTON, self.addToolpath, button2) | |
panel.Bind(wx.EVT_BUTTON, self.clearToolpath, button3) | |
self.showcutwidthcheckbox = wx.CheckBox(panel, -1, "Show cut-width") | |
panel.Bind(wx.EVT_CHECKBOX, self.OnUpdateShowCutWidth, self.showcutwidthcheckbox) | |
self.tooldiainput = wx.TextCtrl(panel, -1, "0.2") | |
self.tooldiainput.SetToolTip(wx.ToolTip("Tool diameter using in units specified at top")) | |
self.ncontourinput = wx.TextCtrl(panel, -1, "1") | |
self.ncontourinput.SetToolTip(wx.ToolTip("Number of contours as positive integer")) | |
self.contourundercutinput = wx.TextCtrl(panel, -1, "0.8") | |
self.contourundercutinput.SetToolTip(wx.ToolTip("Contour overlap as decimal proportion")) | |
self.rasteroverlapinput = wx.TextCtrl(panel, -1, "0.8") | |
self.rasteroverlapinput.SetToolTip(wx.ToolTip("Raster overlap as decimal proportion")) | |
sizer.Add(wx.StaticText(panel, -1, "Tool diameter"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.tooldiainput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "N contour"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.ncontourinput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Contour undercut"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.contourundercutinput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Raster overlap"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.rasteroverlapinput, 0, wx.EXPAND) | |
sizer.AddSpacer(0) | |
sizer.Add(self.showcutwidthcheckbox, 0, wx.EXPAND) | |
sizer.AddSpacer(0) | |
sizer.Add(button1, 0, wx.FIXED) | |
sizer.AddSpacer(0) | |
sizer.Add(button2, 0, wx.FIXED) | |
sizer.AddSpacer(0) | |
sizer.Add(button3, 0, wx.FIXED) | |
sizer.AddSpacer(1) | |
sizer.AddSpacer(1) | |
button1 = wx.Button(panel, -1, "Output File") | |
button2 = wx.Button(panel, -1, "Write Toolpath") | |
panel.Bind(wx.EVT_BUTTON, self.OnChooseOutputFile, button1) | |
panel.Bind(wx.EVT_BUTTON, self.OnWriteToolpath, button2) | |
self.feedrateinput = wx.TextCtrl(panel, -1, "100") | |
self.spindlespeedinput = wx.TextCtrl(panel, -1, "1000") | |
self.toolinput = wx.TextCtrl(panel, -1, "1") | |
self.zupinput = wx.TextCtrl(panel, -1, "0.05") | |
self.zdowninput = wx.TextCtrl(panel, -1, "-0.005") | |
self.fileoutputinput = wx.TextCtrl(panel, -1, "out.g") | |
self.coolantcheckbox = wx.CheckBox(panel, -1, "Coolant") | |
self.origincombo = wx.ComboBox(panel, -1, style=wx.CB_READONLY, | |
choices=["File Origin", "Centre", "Top Left", "Bottom Left", "Top Right", "Bottom Right", "Custom"]) | |
# The last of origin combo choices MUST be custom. | |
self.origincombo.SetSelection(0) | |
self.Bind(wx.EVT_COMBOBOX, self.OnOriginChoose, self.origincombo) | |
sizer.Add(wx.StaticText(panel, -1, "Output origin"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.origincombo, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Feed rate"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.feedrateinput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Spindle speed"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.spindlespeedinput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Tool"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.toolinput, 0, wx.EXPAND) | |
sizer.AddSpacer(0) | |
sizer.Add(self.coolantcheckbox, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Z up"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.zupinput, 0, wx.EXPAND) | |
sizer.Add(wx.StaticText(panel, -1, "Z down"), 0, wx.EXPAND | wx.TOP, 4) | |
sizer.Add(self.zdowninput, 0, wx.EXPAND) | |
sizer.Add(button1, 0, wx.EXPAND | wx.RIGHT, 4) | |
sizer.Add(self.fileoutputinput, 0, wx.EXPAND) | |
sizer.AddSpacer(0) | |
sizer.Add(button2, 0, wx.EXPAND) | |
panel.SetSizer(sizer) | |
panel.SetAutoLayout(True) | |
sizedToFill(panel, 12) | |
return panel | |
def loadToolsDrillPanel(self, parent): | |
panel = wx.Panel(parent) | |
sizer = wx.FlexGridSizer(5, 2, 4, 4) | |
sizer.AddGrowableCol(1, 1) | |
sizer.SetFlexibleDirection(wx.BOTH) | |
# button1 = wx.Button(panel, -1, "Output File") | |
# button2 = wx.Button(panel, -1, "Write Toolpath") | |
# panel.Bind(wx.EVT_BUTTON, self.OnChooseOutputFile, button1) | |
# panel.Bind(wx.EVT_BUTTON, self.OnWriteToolpath, button2) | |
# | |
# self.feedrateinput = wx.TextCtrl(panel, -1, "100") | |
# self.spindlespeedinput = wx.TextCtrl(panel, -1, "1000") | |
# self.toolinput = wx.TextCtrl(panel, -1, "1") | |
# self.zupinput = wx.TextCtrl(panel, -1, "0.05") | |
# self.zdowninput = wx.TextCtrl(panel, -1, "-0.005") | |
# self.fileoutputinput = wx.TextCtrl(panel, -1, "out.g") | |
# | |
# self.coolantcheckbox = wx.CheckBox(panel, -1, "Coolant") | |
# | |
# self.origincombo = wx.ComboBox(panel, -1, style=wx.CB_READONLY, | |
# choices=["File Origin", "Centre", "Top Left", "Bottom Left", "Top Right", "Bottom Right", "Custom"]) | |
# self.origincombo.SetSelection(0) | |
# self.Bind(wx.EVT_COMBOBOX, self.OnOriginChoose, self.origincombo) | |
# | |
# sizer.Add(wx.StaticText(panel, -1, "Output origin"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.origincombo, 0, wx.EXPAND) | |
# sizer.Add(wx.StaticText(panel, -1, "Feed rate"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.feedrateinput, 0, wx.EXPAND) | |
# sizer.Add(wx.StaticText(panel, -1, "Spindle speed"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.spindlespeedinput, 0, wx.EXPAND) | |
# sizer.Add(wx.StaticText(panel, -1, "Tool"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.toolinput, 0, wx.EXPAND) | |
# sizer.AddSpacer(0) | |
# sizer.Add(self.coolantcheckbox, 0, wx.EXPAND) | |
# sizer.Add(wx.StaticText(panel, -1, "Z up"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.zupinput, 0, wx.EXPAND) | |
# sizer.Add(wx.StaticText(panel, -1, "Z down"), 0, wx.EXPAND | wx.TOP, 4) | |
# sizer.Add(self.zdowninput, 0, wx.EXPAND) | |
# sizer.Add(button1, 0, wx.EXPAND) | |
# sizer.Add(self.fileoutputinput, 0, wx.EXPAND) | |
# sizer.AddSpacer(0) | |
# sizer.Add(button2, 0, wx.EXPAND) | |
panel.SetSizer(sizer) | |
panel.SetAutoLayout(True) | |
sizedToFill(panel, 12) | |
return panel | |
def loadToolsEdgePanel(self, parent): | |
panel = wx.Panel(parent) | |
sizer = wx.FlexGridSizer(5, 2, 4, 4) | |
sizer.AddGrowableCol(1, 1) | |
sizer.SetFlexibleDirection(wx.BOTH) | |
panel.SetSizer(sizer) | |
panel.SetAutoLayout(True) | |
sizedToFill(panel, 12) | |
return panel | |
def hdl(self, evt): | |
self.canvas.setUnit(self.unitcombo.GetCurrentSelection()) | |
def OnAbout(self, evt): | |
dlg = wx.MessageDialog( self, join(["LaZoR ", VERSION, "\n", DATE]), "About", wx.OK) | |
dlg.ShowModal() | |
dlg.Destroy() | |
def OnExit(self, evt): | |
self.Close(True) | |
def OnZoomIn(self, evt): | |
self.canvas.zoomrel(1.25) | |
def OnZoomOut(self, evt): | |
self.canvas.zoomrel(1 / 1.25) | |
def OnFitToView(self, evt): | |
if self.data is None: return | |
self.canvas.autoscale_cac() | |
self.canvas.Refresh() | |
def OnShowHideLayerEditor(self, evt): | |
if self.menuLayerEditor.IsChecked(): | |
self.layersPanel.Show() | |
else: | |
self.layersPanel.Hide() | |
self.layersPanel.GetParent().Layout() | |
def OnPaneChanged(self, evt): | |
self.Layout() | |
self.Refresh() | |
def OnChooseOutputFile(self, evt): | |
dlg = wx.FileDialog(self, "Choose g-code output file", "", "", | |
"All Files (*.*)|*", | |
wx.FD_SAVE) | |
if dlg.ShowModal() == wx.ID_CANCEL: return | |
self.fileoutputinput.SetValue(dlg.GetPath()) | |
dlg.Destroy() | |
def OnDeviceCombo(self, evt): | |
dp = self.deviceProfiles[self.devicecombo.GetSelection()] | |
self.setComp(self.tooldiainput, dp.tooldiameter) | |
self.setComp(self.feedrateinput, dp.feedrate) | |
self.setComp(self.spindlespeedinput, dp.spindlespeed) | |
self.setComp(self.toolinput, dp.toolcode) | |
self.setComp(self.zupinput, dp.zup) | |
self.setComp(self.zdowninput, dp.zdown) | |
if dp.coolant is None: | |
self.coolantcheckbox.SetValue(False) | |
self.coolantcheckbox.Disable() | |
else: | |
self.coolantcheckbox.Enable() | |
self.coolantcheckbox.SetValue(dp.coolant) | |
self.contours = [] | |
self.contoolpaths = [] | |
self.canvas.toolpaths = [] | |
self.canvas.updateToolpathData() | |
self.NotifyDataChange() | |
def OnWriteToolpath(self, evt): | |
# TODO: should be more generic than this | |
if self.devicecombo.GetSelection() == 0: | |
self.write_G() | |
else: | |
self.write_l() | |
def OnOriginChoose(self, evt): | |
if self.data is None: return | |
choice = self.origincombo.GetSelection() | |
# "File Origin", "Centre", "Top Left", "Bottom Left", "Top Right", "Bottom Right", "Custom" | |
data = self.data | |
if choice == 0: # file origin | |
data.originx = 0 | |
data.originy = 0 | |
elif choice == 1: # centre | |
data.originx = (data.xmax + data.xmin) / 2.0 | |
data.originy = (data.ymax + data.ymin) / 2.0 | |
elif choice == 2: # top left | |
data.originx = data.xmin | |
data.originy = data.ymax | |
elif choice == 3: # bottom left | |
data.originx = data.xmin | |
data.originy = data.ymin | |
elif choice == 4: # top right | |
data.originx = data.xmax | |
data.originy = data.ymax | |
elif choice == 5: # bottom right | |
data.originx = data.xmax | |
data.originy = data.ymin | |
elif choice == 6: # custom | |
return | |
self.NotifyDataChange() | |
def OnRightClick(self, evt): | |
if self.data is None: return | |
menu = MyPopupMenu(self, evt.GetPosition()) | |
self.canvas.PopupMenu(menu, evt.GetPosition()) | |
menu.Destroy() | |
def setCustomOrigin(self, (mx, my)): | |
x, y = self.canvas.GetViewStart() | |
self.data.originx = self.canvas.MouseToInternalX(mx + x) | |
self.data.originy = self.canvas.MouseToInternalY(my + y) | |
self.origincombo.SetSelection(self.origincombo.GetCount() - 1) # set to custom option | |
self.NotifyDataChange() | |
def LoadGerberFile(self, evt): | |
dlg = wx.FileDialog(self, "Open Gerber file", "", "", | |
"Gerber Files (*.gbl;*.gtl;*.gbr;*.cmp)|*.gbl;*.gtl;*.gbr;*.cmp|All Files (*.*)|*", | |
wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) | |
if dlg.ShowModal() == wx.ID_CANCEL: | |
self.SetStatusText("Load Gerber file cancelled by user", 0) | |
return | |
filename = dlg.GetPath() | |
dlg.Destroy() | |
self.SetStatusText("Loading Gerber: " + filename + "...", 0) | |
[data, tracks] = load_file(filename) | |
self.data = data | |
xmin = 1E99 | |
xmax = -1E99 | |
ymin = 1E99 | |
ymax = -1E99 | |
sum1 = 0 | |
sumReg = 0 | |
sumBound = 0 | |
sumTracks = 0 | |
sumPads = 0 | |
cbounds = pyclipper.Pyclipper() | |
boundarys = [] | |
pcb_edges = [] | |
layers = list(data.layers) | |
for gl in layers: | |
if gl.type == GerberLayer.TYPE_PCBEDGE: | |
data.layers.remove(gl) | |
pcb_edges.extend(gl.points) | |
for segment in gl.points: | |
sum1 += len(segment) | |
for vertex in segment: | |
x = vertex[0] | |
y = vertex[1] | |
if x < xmin: xmin = x | |
if x > xmax: xmax = x | |
if y < ymin: ymin = y | |
if y > ymax: ymax = y | |
continue | |
if gl.type == GerberLayer.TYPE_REGION: | |
sumReg += len(gl.points) | |
# regions.extend(gl.points) | |
continue | |
if gl.type == GerberLayer.TYPE_TRACK: | |
sumTracks += len(gl.points) | |
continue | |
if gl.type == GerberLayer.TYPE_PAD: | |
sumPads += len(gl.points) | |
continue | |
if gl.type == GerberLayer.TYPE_BOUNDARY: | |
# if gl.isDark: | |
# # boundarys.extend(gl.points) | |
# # if len(boundarys) == 0: | |
# boundarys.extend(gl.points) | |
# # else: | |
# # cbounds.AddPaths(boundarys, pyclipper.PT_SUBJECT) | |
# # cbounds.AddPaths(gl.points, pyclipper.PT_SUBJECT) | |
# # boundarys = cbounds.Execute(pyclipper.CT_UNION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) | |
# # cbounds.Clear() | |
# else: | |
# cbounds.AddPaths(boundarys, pyclipper.PT_SUBJECT) | |
# cbounds.AddPaths(gl.points, pyclipper.PT_CLIP) | |
# boundarys = cbounds.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) | |
# cbounds.Clear() | |
if gl.isDark: | |
boundarys.extend(gl.points) | |
else: | |
cbounds.AddPaths(boundarys, pyclipper.PT_SUBJECT) | |
cbounds.AddPaths(gl.points, pyclipper.PT_CLIP) | |
boundarys = cbounds.Execute(pyclipper.CT_DIFFERENCE, pyclipper.PFT_NONZERO, pyclipper.PFT_NONZERO) | |
cbounds.Clear() | |
sumBound += len(gl.points) | |
for segment in gl.points: | |
sum1 += len(segment) | |
for vertex in segment: | |
x = vertex[0] | |
y = vertex[1] | |
if x < xmin: xmin = x | |
if x > xmax: xmax = x | |
if y < ymin: ymin = y | |
if y > ymax: ymax = y | |
continue | |
if gl.type == GerberLayer.TYPE_MERGEDCOPPER: | |
data.layers.remove(gl) | |
continue | |
print " fraction = ",self.data.fraction | |
print " found", sumBound, "polygons,", sum1, "vertices" | |
print " found", sumReg, "pours" | |
print " found", sumTracks, "tracks" | |
print " found", sumPads, "pads" | |
print " found", len(pcb_edges), "edge segments" | |
print " xmin: %0.3g " % xmin, "xmax: %0.3g " % xmax, "dx: %0.3g " % (xmax - xmin) | |
print " ymin: %0.3g " % ymin, "ymax: %0.3g " % ymax, "dy: %0.3g " % (ymax - ymin) | |
data.xmin2 = xmin | |
data.xmax2 = xmax | |
data.ymin2 = ymin | |
data.ymax2 = ymax | |
if len(pcb_edges) == 0: | |
outer_offset = (1 if data.units == 0 else 0.03937) * 10**data.fraction # 1 mm | |
# outer_offset = 0.01 * 10**data.fraction | |
xmin -= outer_offset | |
ymin -= outer_offset | |
xmax += outer_offset | |
ymax += outer_offset | |
pcb_edge = [[xmax, ymax], [xmax, ymin], [xmin, ymin], [xmin, ymax], [xmax, ymax]] | |
pcb_edges.append(pcb_edge) | |
self.pcb_edges = pcb_edges | |
self.boundarys = boundarys = pyclipper.SimplifyPolygons(boundarys, pyclipper.PFT_NONZERO) | |
# boundarys = GerberReader3.replace_holes_with_seams(boundarys) | |
GerberReader3.closeOffPolys(boundarys) | |
data.layers.append(GerberLayer(True, "PCB Edge", pcb_edges, True, False, "blue", GerberLayer.TYPE_PCBEDGE)) | |
data.layers.append(GerberLayer(True, "Merged Copper", boundarys, False, color="brown", type=GerberLayer.TYPE_MERGEDCOPPER)) | |
# PCB bounds | |
data.xmin = xmin | |
data.xmax = xmax | |
data.ymin = ymin | |
data.ymax = ymax | |
# View bounds | |
# Includes the origin | |
if xmin > 0: xmin = 0 | |
if xmax < 0: xmax = 0 | |
if ymin > 0: ymin = 0 | |
if ymax < 0: ymax = 0 | |
# Add margin | |
ww = (xmax - xmin)*0.1 | |
hh = (ymax - ymin)*0.1 | |
xmin -= ww | |
xmax += ww | |
ymin -= hh | |
ymax += hh | |
self.contours = [] | |
self.layersPanel.loadLayersPanel(data, self.NotifyDataChange) | |
self.canvas.loadData2(self.data, xmin, xmax, ymin, ymax) | |
self.SetStatusText("Load Gerber file completed successfully", 0) | |
self.origincombo.SetSelection(0) | |
self.nativeLabel.SetLabelText("(File unit: %s, Dec. places: %0d)" % ("mm" if data.units == 0 else "in", data.fraction)) | |
def LoadEdgeFile(self, evt): | |
if self.data is None: | |
dlg = wx.MessageDialog(self, "You must load a Gerber file first", "Error", wx.OK | wx.ICON_ERROR) | |
dlg.ShowModal() | |
dlg.Destroy() | |
return | |
dlg = wx.FileDialog(self, "Open edge file", "", "", | |
"Gerber Files (*.gml;*.gbl;*.gtl;*.gbr;*.cmp)|*.gml;*.gbl;*.gtl;*.gbr;*.cmp|All Files (*.*)|*", | |
wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) | |
if dlg.ShowModal() == wx.ID_CANCEL: | |
self.SetStatusText("Load edge file cancelled by user", 0) | |
return | |
filename = dlg.GetPath() | |
dlg.Destroy() | |
self.SetStatusText("Loading edge: " + filename + "...", 0) | |
[data, edges] = load_file(filename) | |
brdoutline = [] | |
brdseg = -1 | |
while len(edges) > 0: | |
brdoutline.append([]) | |
brdseg += 1 | |
brdoutline[brdseg].extend(edges[0]) | |
edges.remove(edges[0]) | |
startpnt = brdoutline[brdseg][0] | |
endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] | |
while (abs(startpnt[0] - endpnt[0]) > 10) | (abs(startpnt[1] - endpnt[1]) > 10): | |
found = False | |
for seg in edges: | |
if abs(seg[0][0] - endpnt[0]) < 10: | |
if abs(seg[0][1] - endpnt[1]) < 10: | |
brdoutline[brdseg].extend(seg) | |
edges.remove(seg) | |
endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] | |
found = True | |
break | |
if abs(seg[len(seg) - 1][0] - endpnt[0]) < 10: | |
if abs(seg[len(seg) - 1][1] - endpnt[1]) < 10: | |
edges.remove(seg) | |
seg = seg[::-1] | |
brdoutline[brdseg].extend(seg) | |
endpnt = brdoutline[brdseg][len(brdoutline[brdseg]) - 1] | |
found = True | |
break | |
if not found: | |
dlg = wx.MessageDialog(self, "Edge outline cannot contain any gaps.\n" | |
"No changes were made.", "Load edge file failed", | |
wx.OK | wx.ICON_ERROR) | |
dlg.ShowModal() | |
dlg.Destroy() | |
self.SetStatusText("Load edge failed", 0) | |
return | |
xmin = 1E99 | |
xmax = -1E99 | |
ymin = 1E99 | |
ymax = -1E99 | |
if data.units == self.data.units: | |
for poly in brdoutline: | |
for (x, y) in poly: | |
if x < xmin: xmin = x | |
if x > xmax: xmax = x | |
if y < ymin: ymin = y | |
if y > ymax: ymax = y | |
else: | |
# finx bounds and convert units of data at same time | |
conv = 25.4 if data.units == 1 else 1/25.4 | |
print " Unit conversion of edge file data" | |
for poly in brdoutline: | |
for pt in poly: | |
x = pt[0] = int(pt[0] * conv) | |
y = pt[1] = int(pt[1] * conv) | |
if x < xmin: xmin = x | |
if x > xmax: xmax = x | |
if y < ymin: ymin = y | |
if y > ymax: ymax = y | |
# Check if PCB fits inside edge. We're lazy so we just use box bounds (should really use | |
# polygon bounds checking). | |
eps = 10 | |
if self.data.xmin2 + eps < xmin or self.data.xmax2 - eps > xmax or \ | |
self.data.ymin2 + eps < ymin or self.data.ymax2 - eps > ymax: | |
print self.data.xmin, xmin | |
print self.data.ymin, ymin | |
print self.data.xmax, xmax | |
print self.data.ymax, ymax | |
dlg = wx.MessageDialog(self, "The loaded edge does not fully contain the PCB board.\n" | |
"Do you still wish to proceed using this edge file?", | |
"PCB board extends past edge boundary", wx.YES | wx.NO | wx.ICON_WARNING) | |
ans = dlg.ShowModal() | |
dlg.Destroy() | |
if ans != wx.ID_YES: | |
self.SetStatusText("Load edge file cancelled by user", 0) | |
return | |
self.data.xmin = xmin | |
self.data.xmax = xmax | |
self.data.ymin = ymin | |
self.data.ymax = ymax | |
# View bounds | |
# Includes the origin | |
if xmin > 0: xmin = 0 | |
if xmax < 0: xmax = 0 | |
if ymin > 0: ymin = 0 | |
if ymax < 0: ymax = 0 | |
# Add margin | |
ww = (xmax - xmin)*0.1 | |
hh = (ymax - ymin)*0.1 | |
xmin -= ww | |
xmax += ww | |
ymin -= hh | |
ymax += hh | |
pcb_edges = brdoutline | |
pcb_edges = pyclipper.CleanPolygons(pcb_edges) | |
for poly in pcb_edges: | |
poly.append(poly[0]) # close off polygons | |
# Remove existing edge | |
layers = list(self.data.layers) | |
for gl in layers: | |
if gl.type == GerberLayer.TYPE_PCBEDGE: self.data.layers.remove(gl) | |
# Add edge data to existing data | |
self.data.layers.insert(-1, GerberLayer(True, "PCB Edge", pcb_edges, True, False, "blue", GerberLayer.TYPE_PCBEDGE)) | |
self.pcb_edges = pcb_edges | |
self.canvas.toolpaths = [] | |
self.canvas.loadData2(self.data, xmin, xmax, ymin, ymax) | |
self.layersPanel.loadLayersPanel(self.data, self.NotifyDataChange) | |
self.SetStatusText("Load edge file completed successfully", 0) | |
def LoadDrillFile(self, evt=None): | |
if self.data is None: | |
dlg = wx.MessageDialog(self, "You must load a Gerber file first", "Error", wx.OK | wx.ICON_ERROR) | |
dlg.ShowModal() | |
dlg.Destroy() | |
return | |
dlg = wx.FileDialog(self, "Open edge file", "", "", | |
"Drill files (*.drl;*.dbd;*.txt)|*.drl;*.dbd;*.txt|All Files (*.*)|*", | |
wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) | |
if dlg.ShowModal() == wx.ID_CANCEL: | |
self.SetStatusText("Load drill file cancelled by user", 0) | |
return | |
filename = dlg.GetPath() | |
dlg.Destroy() | |
self.SetStatusText("Loading drill: " + filename + "...", 0) | |
[drillPts, units] = ExcellonReader.load_file(filename) | |
fscale = 10**self.data.fraction | |
if self.data.units != units: | |
print "Conversion of drill file coords required" | |
if units == 0: | |
# mm -> in | |
fscale *= 25.4 | |
else: | |
# in -> mm | |
fscale /= 25.4 | |
drillPts2 = [] | |
for (x, y, dia) in drillPts: | |
drillPts2.append([int(x*fscale), int(y*fscale), int(dia*fscale)]) | |
self.canvas.drillPts = drillPts2 | |
print " Loaded ", len(drillPts), " drill points" | |
self.SetStatusText("Load drill file completed successfully", 0) | |
self.NotifyDataChange() | |
def ClearDrillData(self, evt): | |
self.canvas.drillPts = [] | |
self.NotifyDataChange() | |
self.SetStatusText("Drill data removed", 0) | |
def OnUpdateShowCutWidth(self, evt): | |
self.canvas.toolpathlinewidth = float(self.tooldiainput.GetValue()) \ | |
if self.showcutwidthcheckbox.IsChecked() else 0 | |
self.NotifyDataChange() | |
def addContour(self, evt=None): | |
if self.data is None: return | |
print "contouring boundary ..." | |
self.contours = [] | |
N_contour = int(self.ncontourinput.GetValue()) | |
overlap = float(self.contourundercutinput.GetValue()) | |
if self.showcutwidthcheckbox.IsChecked(): self.canvas.toolpathlinewidth = float(self.tooldiainput.GetValue()) | |
toolpaths = self.canvas.toolpaths = [] | |
toolrad = float(self.tooldiainput.GetValue()) / 2.0 / self.canvas.mousescale | |
self.contours = self.offset_poly(self.boundarys, toolrad) | |
toolpaths.extend(self.contours) | |
delrad = overlap * toolrad | |
for n in range(1, N_contour): | |
toolrad += delrad | |
self.contours = self.offset_poly(self.boundarys, toolrad) | |
toolpaths.extend(self.contours) | |
toolpaths.extend(self.pcb_edges) | |
for seg in toolpaths: | |
if len(seg) > 0: | |
seg.append(seg[0]) | |
self.contoolpaths = list(toolpaths) # cache to allow multiple raster | |
self.canvas.updateToolpathData() # update toolpath cahce of wxGerberCanvas - this is to workaround a bug | |
print " done" | |
self.SetStatusText("Contour completed", 0) | |
self.NotifyDataChange() | |
def clearToolpath(self, evt=None): | |
self.contours = [] | |
self.contoolpaths = [] | |
self.canvas.toolpaths = [] | |
self.NotifyDataChange() | |
self.SetStatusText("Toolpath data removed", 0) | |
def addToolpath(self, evt=None): | |
# previously called raster operation | |
if self.data is None: return | |
print "rastering interior ..." | |
tooldia = float(self.tooldiainput.GetValue()) / self.canvas.mousescale | |
if self.showcutwidthcheckbox.IsChecked(): self.canvas.toolpathlinewidth = float(self.tooldiainput.GetValue()) | |
self.canvas.toolpaths = [] | |
if self.contours == []: | |
edgepath = self.boundarys | |
delta = tooldia / 2.0 | |
else: | |
edgepath = self.contours | |
self.canvas.toolpaths.extend(self.contoolpaths) | |
delta = 0 # tooldia/4.0 | |
pcbedges = self.pcb_edges | |
# pcbedges = [] | |
# for layer in self.pcb_edges: | |
# tmp = [] | |
# for seg in layer: | |
# tmp.append(seg[0] + xoff, seg[0] + yoff) | |
rasterpath = self.raster_area(edgepath, pcbedges, delta, self.data.ymin, self.data.ymax, self.data.xmin, self.data.xmax) | |
# toolpaths[0].extend(pyclipper.PolyTreeToPaths(rasterpath)) | |
self.canvas.toolpaths.extend(rasterpath) | |
self.canvas.updateToolpathData() # update toolpath cahce of wxGerberCanvas - this is to workaround a bug | |
print " done" | |
self.SetStatusText("Raster completed", 0) | |
self.NotifyDataChange() | |
def raster_area(self, edgepath, pcbedges, delta, ymin1, ymax1, xmin1, xmax1): | |
# | |
# raster a 2D region | |
# | |
# find row-edge intersections | |
# | |
overlap = float(self.rasteroverlapinput.GetValue()) | |
tooldia = float(self.tooldiainput.GetValue()) / self.canvas.mousescale | |
rastlines = [] | |
starty = ymin1 | |
endy = ymax1 | |
startx = round(xmax1, 2) | |
endx = round(xmin1, 2) | |
numrows = int(math.floor((endy - starty) / (tooldia * overlap))) | |
crast = pyclipper.Pyclipper() | |
edgepath = self.offset_poly(edgepath, delta) | |
result = [] | |
for row in range(numrows + 1): | |
rastlines.append([]) | |
ty = round(starty + row * (tooldia * overlap), 4) | |
rastlines[row].append([startx, ty]) | |
rastlines[row].append([endx, ty]) | |
startx, endx = endx, startx | |
tmp = [] | |
for i in range(numrows): | |
tmp.append(rastlines[i][0][1]) | |
crast.AddPaths(pcbedges, pyclipper.PT_CLIP,True) | |
crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) | |
rastlines = crast.Execute2(pyclipper.CT_INTERSECTION, pyclipper.PFT_EVENODD, pyclipper.PFT_EVENODD) | |
crast.Clear() | |
rastlines = pyclipper.PolyTreeToPaths(rastlines) | |
## | |
crast.AddPaths(edgepath, pyclipper.PT_CLIP, True) | |
crast.AddPaths(rastlines, pyclipper.PT_SUBJECT, False) | |
rastlines = crast.Execute2(pyclipper.CT_DIFFERENCE, pyclipper.PFT_POSITIVE, pyclipper.PFT_POSITIVE) | |
crast.Clear() | |
rastlines = pyclipper.PolyTreeToPaths(rastlines) | |
# polyclip.sort(key=lambda x: (x[0][1],x[0][0])) | |
# polyclip.sort(key=lambda x: x[0][1]) | |
# polyclip.sort(key=lambda x: x[0][0]) | |
rastltor = [] | |
rastrtol = [] | |
for segs in rastlines: | |
if segs[0][0] < segs[1][0]: | |
rastltor.append(segs) | |
else: | |
rastrtol.append(segs) | |
rastltor.sort(key=lambda x: (x[0][1], x[0][0])) | |
rastrtol.sort(key=lambda x: (x[0][1], -x[0][0])) | |
result.extend(rastltor) | |
result.extend(rastrtol) | |
result.sort(key=lambda x: x[0][1]) | |
return result | |
def offset_poly(self, path, toolrad): | |
c_osp = pyclipper.PyclipperOffset() | |
c_osp.AddPaths(path, pyclipper.JT_SQUARE, pyclipper.ET_CLOSEDPOLYGON) | |
polyclip = c_osp.Execute(toolrad) | |
polyclip = pyclipper.CleanPolygons(polyclip) | |
c_osp.Clear() | |
return polyclip | |
def write_G(self): | |
# | |
# G code output | |
# | |
data = self.data | |
scale = self.canvas.mousescale # NOTE: this takes units into account as well! | |
feed = float(self.feedrateinput.GetValue()) | |
zup = self.zupinput.GetValue() | |
zdown = self.zdowninput.GetValue() | |
xoff = -data.originx * scale | |
yoff = -data.originy * scale | |
cool = self.coolantcheckbox.IsChecked() | |
text = self.fileoutputinput.GetValue() | |
file = open(text, 'w') | |
# file.write("%\n") | |
file.write("G20\n") | |
file.write("T" + self.toolinput.GetValue() + "M06\n") # tool | |
file.write("G90 G54\n") # absolute positioning with respect to set origin | |
file.write("F%0.3f\n" % feed) # feed rate | |
file.write("S" + self.spindlespeedinput.GetValue() + "\n") # spindle speed | |
if cool: file.write("M08\n") # coolant on | |
file.write("G0 Z" + zup + "\n") # move up before starting spindle | |
file.write("M3\n") # spindle on clockwise | |
nsegment = 0 | |
if self.canvas.toolpaths == []: | |
path = self.boundarys | |
else: | |
path = self.canvas.toolpaths | |
if zdown == " ": | |
raise StandardError("This line has an error") | |
# zdown = zoff + zmin + (layer-0.50)*dlayer | |
else: | |
zdown = float(self.zdowninput.GetValue()) | |
for segment in range(len(path)): | |
nsegment += 1 | |
vertex = 0 | |
x = path[segment][vertex][0] * scale + xoff | |
y = path[segment][vertex][1] * scale + yoff | |
file.write("G0 X%0.4f " % x + "Y%0.4f " % y + "Z" + self.zupinput.GetValue() + "\n") # rapid motion | |
file.write("G1 Z%0.4f " % zdown + "\n") # linear motion | |
for vertex in range(1, len(path[segment])): | |
x = path[segment][vertex][0] * scale + xoff | |
y = path[segment][vertex][1] * scale + yoff | |
file.write("G1 X%0.4f " % x + "Y%0.4f" % y + "\n") | |
file.write("Z" + zup + "\n") | |
# for layer in range((len(boundarys) - 1), -1, -1): | |
# if (toolpaths[layer] == []): | |
# path = boundarys[layer] | |
# else: | |
# path = toolpaths[layer] | |
# if (szdown.get() == " "): | |
# raise StandardError("This line has an error") | |
# # zdown = zoff + zmin + (layer-0.50)*dlayer | |
# else: | |
# zdown = float(szdown.get()) | |
# for segment in range(len(path)): | |
# nsegment += 1 | |
# vertex = 0 | |
# x = path[segment][vertex][0] * xyscale * (10 ** -gerber_data.fraction) + xoff | |
# y = path[segment][vertex][1] * xyscale * (10 ** -gerber_data.fraction) + yoff | |
# file.write("G0 X%0.4f " % x + "Y%0.4f " % y + "Z" + szup.get() + "\n") # rapid motion | |
# file.write("G1 Z%0.4f " % zdown + "\n") # linear motion | |
# for vertex in range(1, len(path[segment])): | |
# x = path[segment][vertex][0] * xyscale * (10 ** -gerber_data.fraction) + xoff | |
# y = path[segment][vertex][1] * xyscale * (10 ** -gerber_data.fraction) + yoff | |
# file.write("G1 X%0.4f " % x + "Y%0.4f" % y + "\n") | |
# file.write("Z" + szup.get() + "\n") | |
file.write("G0 Z" + zup + "\n") # move up before stopping spindle | |
file.write("M5\n") # spindle stop | |
if cool: file.write("M09\n") # coolant off | |
file.write("M30\n") # program end and reset | |
# file.write("%\n") | |
file.close() | |
self.SetStatusText("Successfully written " + str(nsegment) + " G code toolpath segments to " + text, 0) | |
def write_l(self): | |
# | |
# LaZoR Mk1 code output | |
# | |
data = self.data | |
scale = self.canvas.mousescale # NOTE: this takes units into account as well! | |
# dlayer = float(sthickness.get())/zscale | |
feed = float(self.feedrateinput.GetValue()) | |
xoff = -data.originx * scale | |
yoff = -data.originy * scale | |
cool = self.coolantcheckbox.IsChecked() | |
text = self.fileoutputinput.GetValue() | |
file = open(text, 'w') | |
# file.write("%\n") | |
# file.write("O1234\n") | |
# file.write("T"+stool.get()+"M06\n") # tool | |
file.write("G90G54\n") # absolute positioning with respect to set origin | |
# file.write("F%0.3f\n"%feed) # feed rate | |
file.write("S" + str(float(self.spindlespeedinput.GetValue()) / 100) + "\n") # spindle speed | |
# if (cool == TRUE): file.write("M08\n") # coolant on | |
# file.write("G00Z"+szup.get()+"\n") # move up before starting spindle | |
# file.write("M03\n") # spindle on clockwise | |
nsegment = 0 | |
for layer in range((len(self.boundarys) - 1), -1, -1): | |
# FIXME: toolpath will not have same number of layers as boundarys | |
if self.canvas.toolpaths[layer] == []: | |
path = self.boundarys[layer] | |
else: | |
path = self.canvas.toolpaths[layer] | |
## if (szdown.get() == " "): | |
## zdown = zoff + zmin + (layer-0.50)*dlayer | |
## else: | |
## zdown = float(szdown.get()) | |
for segment in range(len(path)): | |
## if (len(path[segment])==0): | |
## continue | |
nsegment += 1 | |
vertex = 0 | |
x = path[segment][vertex][0] * scale + xoff | |
y = path[segment][vertex][1] * scale + yoff | |
file.write("G00X%0.4f" % x + "Y%0.4f" % y + "\n") # rapid motion | |
# file.write("G01Z%0.4f"%zdown+"\n") # linear motion | |
file.write("M03\n") | |
for vertex in range(1, len(path[segment])): | |
x = path[segment][vertex][0] * scale + xoff | |
y = path[segment][vertex][1] * scale + yoff | |
file.write("G01X%0.4f" % x + "Y%0.4f" % y + "F%0.3f\n" % feed + "\n") | |
# file.write("Z"+szup.get()+"\n") | |
file.write("M05\n") | |
# file.write("G00Z"+szup.get()+"\n") # move up before stopping spindle | |
file.write("M05\n") # spindle stop | |
if cool: file.write("M09\n") # coolant off | |
file.write("M30\n") # program end and reset | |
# file.write("%\n") | |
file.close() | |
self.SetStatusText("Successfully written " + str(nsegment) + " LaZoR code toolpath segments to " + text, 0) | |
def NotifyDataChange(self): | |
self.canvas.redraw() | |
self.canvas.Refresh() | |
def setComp(self, comp, val): | |
if val is None: | |
comp.SetValue("") | |
comp.Disable() | |
else: | |
comp.Enable() | |
comp.SetValue(val) | |
class LayersPanel(wx.lib.scrolledpanel.ScrolledPanel): | |
""" Gerber layer's editing panel """ | |
def __init__(self, parent): | |
super(LayersPanel, self).__init__(parent, size=(300, 0)) | |
self.g = None | |
self.data = None | |
""" Gerber data :type data: GerberData """ | |
self.notify = None | |
""" Called when data has been modified """ | |
self.Col = -1 | |
def loadLayersPanel(self, data=None, change_notifier=None): | |
""" | |
:type data: GerberData | |
""" | |
if data is not None: self.data = data | |
if change_notifier is not None: self.notify = change_notifier | |
self.Freeze() | |
if self.g is not None: self.DestroyChildren() | |
g = wx.grid.Grid(self) | |
self.g = g | |
g.CreateGrid(0 if data is None else len(data.layers), 5) | |
g.HideRowLabels() | |
g.DisableDragRowSize() | |
g.SetColLabelValue(0, "Name") | |
g.SetColLabelValue(1, " ") | |
g.SetColLabelValue(2, "Visible") | |
g.SetColLabelValue(3, "Filled") | |
g.SetColLabelValue(4, "Colour") | |
# get the cell attribute for the top left row | |
# editor = layersPanel.GetCellEditor(0, 0) | |
attr = wx.grid.GridCellAttr() | |
attr.SetReadOnly(True) | |
g.SetColAttr(0, attr) | |
g.SetColAttr(1, attr) | |
g.SetColAttr(4, attr) | |
attr = wx.grid.GridCellAttr() | |
attr.SetEditor(wx.grid.GridCellBoolEditor()) | |
attr.SetRenderer(wx.grid.GridCellBoolRenderer()) | |
g.SetColAttr(2, attr) | |
g.SetColAttr(3, attr) | |
g.SetColSize(0, 125) | |
g.SetColSize(1, 25) | |
g.SetColSize(2, 50) | |
g.SetColSize(3, 50) | |
g.SetColSize(4, 50) | |
g.Bind(wx.grid.EVT_GRID_CELL_LEFT_CLICK, self.onMouse) | |
g.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.onCellSelected) | |
g.Bind(wx.grid.EVT_GRID_EDITOR_CREATED, self.onEditorCreated) | |
if data is not None: | |
r = 0 | |
for gl in data.layers: | |
g.SetCellValue(r, 0, gl.name) | |
g.SetCellValue(r, 1, "D" if gl.isDark else "C") | |
g.SetCellValue(r, 2, '1' if gl.visible else '') | |
g.SetCellValue(r, 3, '1' if gl.filled else '') | |
g.SetCellBackgroundColour(r, 4, gl.color) | |
if len(gl.points) == 0: g.SetCellTextColour(r, 0, "#808080") # grey out empty layers | |
r += 1 | |
# r = 0 # test values | |
# self.SetCellValue(r, 0, "Test name") | |
# self.SetCellValue(r, 1, "D") | |
# self.SetCellValue(r, 2, '1') | |
# self.SetCellValue(r, 3, '') | |
# self.SetCellValue(r, 5, "green") | |
sizedToFill(g) | |
self.SetupScrolling(False, True) | |
self.Thaw() | |
# Methods below used to hack wx.grid to allow one click toggling of checkboxed | |
# Also modified to activate colour picker | |
def onMouse(self,evt): | |
if evt.Col == 2 or evt.Col == 3: | |
self.Col = evt.Col | |
wx.CallLater(100, self.toggleCheckBox) | |
elif evt.Col == 4: | |
gl = self.data.layers[evt.Row] | |
# Change colour | |
cdata = wx.ColourData() | |
cdata.SetColour(gl.color) | |
cdata.ChooseFull = True | |
dlg = wx.ColourDialog(self, cdata) | |
if dlg.ShowModal() == wx.ID_OK: | |
newcol = dlg.GetColourData().Colour.GetAsString(wx.C2S_HTML_SYNTAX) | |
gl.color = newcol | |
# update ui | |
self.g.SetCellBackgroundColour(evt.Row, 4, gl.color) | |
print gl.color, evt.Row | |
self.notify() | |
self.g.Refresh() | |
dlg.Destroy() | |
evt.Skip() | |
def toggleCheckBox(self): | |
self.cb.SetValue(not self.cb.Value) | |
self.afterCheckBox(self.cb.Value) | |
def onCellSelected(self, evt): | |
if evt.Col == 2 or evt.Col == 3: | |
self.Col = evt.Col | |
wx.CallAfter(self.g.EnableCellEditControl) | |
evt.Skip() | |
def onEditorCreated(self, evt): | |
if evt.Col == 2 or evt.Col == 3: | |
self.cb = evt.Control | |
self.Col = evt.Col | |
self.cb.WindowStyle |= wx.WANTS_CHARS | |
self.cb.Bind(wx.EVT_KEY_DOWN, self.onKeyDown) | |
self.cb.Bind(wx.EVT_CHECKBOX, self.onCheckBox) | |
evt.Skip() | |
def onKeyDown(self, evt): | |
if evt.KeyCode == wx.WXK_UP: | |
if self.g.GridCursorRow > 0: | |
self.g.DisableCellEditControl() | |
self.g.MoveCursorUp(False) | |
elif evt.KeyCode == wx.WXK_DOWN: | |
if self.g.GridCursorRow < (self.g.NumberRows-1): | |
self.g.DisableCellEditControl() | |
self.g.MoveCursorDown(False) | |
elif evt.KeyCode == wx.WXK_LEFT: | |
if self.g.GridCursorCol > 0: | |
self.g.DisableCellEditControl() | |
self.g.MoveCursorLeft(False) | |
elif evt.KeyCode == wx.WXK_RIGHT: | |
if self.g.GridCursorCol < (self.g.NumberCols-1): | |
self.g.DisableCellEditControl() | |
self.g.MoveCursorRight(False) | |
else: | |
evt.Skip() | |
def onCheckBox(self, evt): | |
self.afterCheckBox(evt.IsChecked()) | |
evt.Skip() | |
def afterCheckBox(self, isChecked): | |
if self.Col == 2: | |
self.data.layers[self.g.GridCursorRow].visible = isChecked | |
else: | |
self.data.layers[self.g.GridCursorRow].filled = isChecked | |
self.notify() | |
#======================================================= | |
app = wx.App() | |
frame = LazorAppFrame() | |
frame.Show() | |
app.MainLoop() |