Convertion to Qt. Major refactoring.

This commit is contained in:
Juan Pablo Caram
2014-06-13 15:21:11 -04:00
parent 74a1331a7a
commit 16734f5d1a
28 changed files with 8272 additions and 3640 deletions

View File

@@ -1,13 +1,15 @@
############################################################ import sys
# FlatCAM: 2D Post-processing for Manufacturing # from PyQt4 import QtGui
# http://caram.cl/software/flatcam # from FlatCAMApp import App
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk def debug_trace():
from FlatCAMApp import * '''Set a tracepoint in the Python debugger that works with Qt'''
from PyQt4.QtCore import pyqtRemoveInputHook
#from pdb import set_trace
pyqtRemoveInputHook()
#set_trace()
app = App() debug_trace()
Gtk.main() app = QtGui.QApplication(sys.argv)
fc = App()
sys.exit(app.exec_())

File diff suppressed because it is too large Load Diff

39
FlatCAMCommon.py Normal file
View File

@@ -0,0 +1,39 @@
class LoudDict(dict):
"""
A Dictionary with a callback for
item changes.
"""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.callback = lambda x: None
def __setitem__(self, key, value):
"""
Overridden __setitem__ method. Will emit 'changed(QString)'
if the item was changed, with key as parameter.
"""
if key in self and self.__getitem__(key) == value:
return
dict.__setitem__(self, key, value)
self.callback(key)
def update(self, *args, **kwargs):
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
other = dict(*args, **kwargs)
for key in other:
self[key] = other[key]
def set_change_callback(self, callback):
"""
Assigns a function as callback on item change. The callback
will receive the key of the object that was changed.
:param callback: Function to call on item change.
:type callback: func
:return: None
"""
self.callback = callback

688
FlatCAMGUI.py Normal file
View File

@@ -0,0 +1,688 @@
from PyQt4 import QtGui, QtCore, Qt
from GUIElements import *
class FlatCAMGUI(QtGui.QMainWindow):
def __init__(self):
super(FlatCAMGUI, self).__init__()
# Divine icon pack by Ipapun @ finicons.com
############
### Menu ###
############
self.menu = self.menuBar()
### File ###
self.menufile = self.menu.addMenu('&File')
# New
self.menufilenew = QtGui.QAction(QtGui.QIcon('share/file16.png'), '&New', self)
self.menufile.addAction(self.menufilenew)
# Open recent
# Recent
self.recent = self.menufile.addMenu(QtGui.QIcon('share/folder16.png'), "Open recent ...")
# Open gerber
self.menufileopengerber = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Gerber ...', self)
self.menufile.addAction(self.menufileopengerber)
# Open Excellon ...
self.menufileopenexcellon = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Excellon ...', self)
self.menufile.addAction(self.menufileopenexcellon)
# Open G-Code ...
self.menufileopengcode = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open G-&Code ...', self)
self.menufile.addAction(self.menufileopengcode)
# Open Project ...
self.menufileopenproject = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Open &Project ...', self)
self.menufile.addAction(self.menufileopenproject)
# Save Project
self.menufilesaveproject = QtGui.QAction(QtGui.QIcon('share/floppy16.png'), '&Save Project', self)
self.menufile.addAction(self.menufilesaveproject)
# Save Project As ...
self.menufilesaveprojectas = QtGui.QAction(QtGui.QIcon('share/floppy16.png'), 'Save Project &As ...', self)
self.menufile.addAction(self.menufilesaveprojectas)
# Save Project Copy ...
self.menufilesaveprojectcopy = QtGui.QAction(QtGui.QIcon('share/floppy16.png'), 'Save Project C&opy ...', self)
self.menufile.addAction(self.menufilesaveprojectcopy)
# Save Defaults
self.menufilesavedefaults = QtGui.QAction(QtGui.QIcon('share/floppy16.png'), 'Save &Defaults', self)
self.menufile.addAction(self.menufilesavedefaults)
# Quit
exit_action = QtGui.QAction(QtGui.QIcon('share/power16.png'), '&Exit', self)
# exitAction.setShortcut('Ctrl+Q')
# exitAction.setStatusTip('Exit application')
exit_action.triggered.connect(QtGui.qApp.quit)
self.menufile.addAction(exit_action)
### Edit ###
self.menuedit = self.menu.addMenu('&Edit')
self.menueditdelete = self.menuedit.addAction(QtGui.QIcon('share/trash16.png'), 'Delete')
### Options ###
self.menuoptions = self.menu.addMenu('&Options')
self.menuoptions_transfer = self.menuoptions.addMenu('Transfer options')
self.menuoptions_transfer_a2p = self.menuoptions_transfer.addAction("Application to Project")
self.menuoptions_transfer_p2a = self.menuoptions_transfer.addAction("Project to Application")
self.menuoptions_transfer_p2o = self.menuoptions_transfer.addAction("Project to Object")
self.menuoptions_transfer_o2p = self.menuoptions_transfer.addAction("Object to Project")
self.menuoptions_transfer_a2o = self.menuoptions_transfer.addAction("Application to Object")
self.menuoptions_transfer_o2a = self.menuoptions_transfer.addAction("Object to Application")
### View ###
self.menuview = self.menu.addMenu('&View')
self.menuviewdisableall = self.menuview.addAction(QtGui.QIcon('share/clear_plot16.png'), 'Disable all plots')
self.menuviewdisableother = self.menuview.addAction(QtGui.QIcon('share/clear_plot16.png'),
'Disable all plots but this one')
self.menuviewenable = self.menuview.addAction(QtGui.QIcon('share/replot16.png'), 'Enable all plots')
### Tool ###
self.menutool = self.menu.addMenu('&Tool')
### Help ###
self.menuhelp = self.menu.addMenu('&Help')
self.menuhelp_about = self.menuhelp.addAction(QtGui.QIcon('share/tv16.png'), 'About FlatCAM')
self.menuhelp_manual = self.menuhelp.addAction(QtGui.QIcon('share/globe16.png'), 'Manual')
###############
### Toolbar ###
###############
self.toolbar = QtGui.QToolBar()
self.addToolBar(self.toolbar)
self.zoom_fit_btn = self.toolbar.addAction(QtGui.QIcon('share/zoom_fit32.png'), "&Zoom Fit")
self.zoom_out_btn = self.toolbar.addAction(QtGui.QIcon('share/zoom_out32.png'), "&Zoom Out")
self.zoom_in_btn = self.toolbar.addAction(QtGui.QIcon('share/zoom_in32.png'), "&Zoom In")
self.clear_plot_btn = self.toolbar.addAction(QtGui.QIcon('share/clear_plot32.png'), "&Clear Plot")
self.replot_btn = self.toolbar.addAction(QtGui.QIcon('share/replot32.png'), "&Replot")
self.delete_btn = self.toolbar.addAction(QtGui.QIcon('share/delete32.png'), "&Delete")
################
### Splitter ###
################
self.splitter = QtGui.QSplitter()
self.setCentralWidget(self.splitter)
################
### Notebook ###
################
self.notebook = QtGui.QTabWidget()
# self.notebook.setMinimumWidth(250)
### Projet ###
project_tab = QtGui.QWidget()
project_tab.setMinimumWidth(250) # Hack
self.project_tab_layout = QtGui.QVBoxLayout(project_tab)
self.project_tab_layout.setContentsMargins(2, 2, 2, 2)
self.notebook.addTab(project_tab, "Project")
### Selected ###
self.selected_tab = QtGui.QWidget()
self.selected_tab_layout = QtGui.QVBoxLayout(self.selected_tab)
self.selected_tab_layout.setContentsMargins(2, 2, 2, 2)
self.selected_scroll_area = VerticalScrollArea()
self.selected_tab_layout.addWidget(self.selected_scroll_area)
self.notebook.addTab(self.selected_tab, "Selected")
### Options ###
self.options_tab = QtGui.QWidget()
self.options_tab.setContentsMargins(0, 0, 0, 0)
self.options_tab_layout = QtGui.QVBoxLayout(self.options_tab)
self.options_tab_layout.setContentsMargins(2, 2, 2, 2)
hlay1 = QtGui.QHBoxLayout()
self.options_tab_layout.addLayout(hlay1)
self.icon = QtGui.QLabel()
self.icon.setPixmap(QtGui.QPixmap('share/gear48.png'))
hlay1.addWidget(self.icon)
self.options_combo = QtGui.QComboBox()
self.options_combo.addItem("APPLICATION DEFAULTS")
self.options_combo.addItem("PROJECT OPTIONS")
hlay1.addWidget(self.options_combo)
hlay1.addStretch()
self.options_scroll_area = VerticalScrollArea()
self.options_tab_layout.addWidget(self.options_scroll_area)
self.notebook.addTab(self.options_tab, "Options")
### Tool ###
self.tool_tab = QtGui.QWidget()
self.tool_tab_layout = QtGui.QVBoxLayout(self.tool_tab)
self.tool_tab_layout.setContentsMargins(2, 2, 2, 2)
self.notebook.addTab(self.tool_tab, "Tool")
self.tool_scroll_area = VerticalScrollArea()
self.tool_tab_layout.addWidget(self.tool_scroll_area)
self.splitter.addWidget(self.notebook)
######################
### Plot and other ###
######################
right_widget = QtGui.QWidget()
# right_widget.setContentsMargins(0, 0, 0, 0)
self.splitter.addWidget(right_widget)
self.right_layout = QtGui.QVBoxLayout()
self.right_layout.setMargin(0)
# self.right_layout.setContentsMargins(0, 0, 0, 0)
right_widget.setLayout(self.right_layout)
################
### Info bar ###
################
infobar = self.statusBar()
self.info_label = QtGui.QLabel("Welcome to FlatCAM.")
self.info_label.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
infobar.addWidget(self.info_label, stretch=1)
self.position_label = QtGui.QLabel("")
self.position_label.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
self.position_label.setMinimumWidth(110)
infobar.addWidget(self.position_label)
self.units_label = QtGui.QLabel("[in]")
# self.units_label.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
self.units_label.setMargin(2)
infobar.addWidget(self.units_label)
self.progress_bar = QtGui.QProgressBar()
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
infobar.addWidget(self.progress_bar)
#############
### Icons ###
#############
app_icon = QtGui.QIcon()
app_icon.addFile('share/flatcam_icon16.png', QtCore.QSize(16, 16))
app_icon.addFile('share/flatcam_icon24.png', QtCore.QSize(24, 24))
app_icon.addFile('share/flatcam_icon32.png', QtCore.QSize(32, 32))
app_icon.addFile('share/flatcam_icon48.png', QtCore.QSize(48, 48))
app_icon.addFile('share/flatcam_icon128.png', QtCore.QSize(128, 128))
app_icon.addFile('share/flatcam_icon256.png', QtCore.QSize(256, 256))
self.setWindowIcon(app_icon)
self.setGeometry(100, 100, 750, 500)
self.setWindowTitle('FlatCAM - 0.5')
self.show()
class OptionsGroupUI(QtGui.QGroupBox):
def __init__(self, title, parent=None):
QtGui.QGroupBox.__init__(self, title, parent=parent)
self.setStyleSheet("""
QGroupBox
{
font-size: 16px;
font-weight: bold;
}
""")
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
class GerberOptionsGroupUI(OptionsGroupUI):
def __init__(self, parent=None):
OptionsGroupUI.__init__(self, "Gerber Options", parent=parent)
## Plot options
self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.layout.addWidget(self.plot_options_label)
grid0 = QtGui.QGridLayout()
self.layout.addLayout(grid0)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_options_label.setToolTip(
"Plot (show) this object."
)
grid0.addWidget(self.plot_cb, 0, 0)
# Solid CB
self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.setToolTip(
"Solid color polygons."
)
grid0.addWidget(self.solid_cb, 0, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='Multicolored')
self.multicolored_cb.setToolTip(
"Draw polygons in different colors."
)
grid0.addWidget(self.multicolored_cb, 0, 2)
## Isolation Routing
self.isolation_routing_label = QtGui.QLabel("<b>Isolation Routing:</b>")
self.isolation_routing_label.setToolTip(
"Create a Geometry object with\n"
"toolpaths to cut outside polygons."
)
self.layout.addWidget(self.isolation_routing_label)
grid1 = QtGui.QGridLayout()
self.layout.addLayout(grid1)
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"Diameter of the cutting tool."
)
grid1.addWidget(tdlabel, 0, 0)
self.iso_tool_dia_entry = LengthEntry()
grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
passlabel = QtGui.QLabel('Width (# passes):')
passlabel.setToolTip(
"Width of the isolation gap in\n"
"number (integer) of tool widths."
)
grid1.addWidget(passlabel, 1, 0)
self.iso_width_entry = IntEntry()
grid1.addWidget(self.iso_width_entry, 1, 1)
overlabel = QtGui.QLabel('Pass overlap:')
overlabel.setToolTip(
"How much (fraction of tool width)\n"
"to overlap each pass."
)
grid1.addWidget(overlabel, 2, 0)
self.iso_overlap_entry = FloatEntry()
grid1.addWidget(self.iso_overlap_entry, 2, 1)
## Board cuttout
self.board_cutout_label = QtGui.QLabel("<b>Board cutout:</b>")
self.board_cutout_label.setToolTip(
"Create toolpaths to cut around\n"
"the PCB and separate it from\n"
"the original board."
)
self.layout.addWidget(self.board_cutout_label)
grid2 = QtGui.QGridLayout()
self.layout.addLayout(grid2)
tdclabel = QtGui.QLabel('Tool dia:')
tdclabel.setToolTip(
"Diameter of the cutting tool."
)
grid2.addWidget(tdclabel, 0, 0)
self.cutout_tooldia_entry = LengthEntry()
grid2.addWidget(self.cutout_tooldia_entry, 0, 1)
marginlabel = QtGui.QLabel('Margin:')
marginlabel.setToolTip(
"Distance from objects at which\n"
"to draw the cutout."
)
grid2.addWidget(marginlabel, 1, 0)
self.cutout_margin_entry = LengthEntry()
grid2.addWidget(self.cutout_margin_entry, 1, 1)
gaplabel = QtGui.QLabel('Gap size:')
gaplabel.setToolTip(
"Size of the gaps in the toolpath\n"
"that will remain to hold the\n"
"board in place."
)
grid2.addWidget(gaplabel, 2, 0)
self.cutout_gap_entry = LengthEntry()
grid2.addWidget(self.cutout_gap_entry, 2, 1)
gapslabel = QtGui.QLabel('Gaps:')
gapslabel.setToolTip(
"Where to place the gaps, Top/Bottom\n"
"Left/Rigt, or on all 4 sides."
)
grid2.addWidget(gapslabel, 3, 0)
self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'},
{'label': '2 (L/R)', 'value': 'lr'},
{'label': '4', 'value': '4'}])
grid2.addWidget(self.gaps_radio, 3, 1)
## Non-copper regions
self.noncopper_label = QtGui.QLabel("<b>Non-copper regions:</b>")
self.noncopper_label.setToolTip(
"Create polygons covering the\n"
"areas without copper on the PCB.\n"
"Equivalent to the inverse of this\n"
"object. Can be used to remove all\n"
"copper from a specified region."
)
self.layout.addWidget(self.noncopper_label)
grid3 = QtGui.QGridLayout()
self.layout.addLayout(grid3)
# Margin
bmlabel = QtGui.QLabel('Boundary Margin:')
bmlabel.setToolTip(
"Specify the edge of the PCB\n"
"by drawing a box around all\n"
"objects with this minimum\n"
"distance."
)
grid3.addWidget(bmlabel, 0, 0)
self.noncopper_margin_entry = LengthEntry()
grid3.addWidget(self.noncopper_margin_entry, 0, 1)
# Rounded corners
self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
self.noncopper_rounded_cb.setToolTip(
"Creates a Geometry objects with polygons\n"
"covering the copper-free areas of the PCB."
)
grid3.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
## Bounding box
self.boundingbox_label = QtGui.QLabel('<b>Bounding Box:</b>')
self.layout.addWidget(self.boundingbox_label)
grid4 = QtGui.QGridLayout()
self.layout.addLayout(grid4)
bbmargin = QtGui.QLabel('Boundary Margin:')
bbmargin.setToolTip(
"Distance of the edges of the box\n"
"to the nearest polygon."
)
grid4.addWidget(bbmargin, 0, 0)
self.bbmargin_entry = LengthEntry()
grid4.addWidget(self.bbmargin_entry, 0, 1)
self.bbrounded_cb = FCCheckBox(label="Rounded corners")
self.bbrounded_cb.setToolTip(
"If the bounding box is \n"
"to have rounded corners\n"
"their radius is equal to\n"
"the margin."
)
grid4.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
class ExcellonOptionsGroupUI(OptionsGroupUI):
def __init__(self, parent=None):
OptionsGroupUI.__init__(self, "Excellon Options", parent=parent)
## Plot options
self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.layout.addWidget(self.plot_options_label)
grid0 = QtGui.QGridLayout()
self.layout.addLayout(grid0)
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.setToolTip(
"Plot (show) this object."
)
grid0.addWidget(self.plot_cb, 0, 0)
self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.setToolTip(
"Solid circles."
)
grid0.addWidget(self.solid_cb, 0, 1)
## Create CNC Job
self.cncjob_label = QtGui.QLabel('<b>Create CNC Job</b>')
self.cncjob_label.setToolTip(
"Create a CNC Job object\n"
"for this drill object."
)
self.layout.addWidget(self.cncjob_label)
grid1 = QtGui.QGridLayout()
self.layout.addLayout(grid1)
cutzlabel = QtGui.QLabel('Cut Z:')
cutzlabel.setToolTip(
"Drill depth (negative)\n"
"below the copper surface."
)
grid1.addWidget(cutzlabel, 0, 0)
self.cutz_entry = LengthEntry()
grid1.addWidget(self.cutz_entry, 0, 1)
travelzlabel = QtGui.QLabel('Travel Z:')
travelzlabel.setToolTip(
"Tool height when travelling\n"
"across the XY plane."
)
grid1.addWidget(travelzlabel, 1, 0)
self.travelz_entry = LengthEntry()
grid1.addWidget(self.travelz_entry, 1, 1)
frlabel = QtGui.QLabel('Feed rate:')
frlabel.setToolTip(
"Tool speed while drilling\n"
"(in units per minute)."
)
grid1.addWidget(frlabel, 2, 0)
self.feedrate_entry = LengthEntry()
grid1.addWidget(self.feedrate_entry, 2, 1)
class GeometryOptionsGroupUI(OptionsGroupUI):
def __init__(self, parent=None):
OptionsGroupUI.__init__(self, "Geometry Options", parent=parent)
## Plot options
self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.layout.addWidget(self.plot_options_label)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.setToolTip(
"Plot (show) this object."
)
self.layout.addWidget(self.plot_cb)
## Create CNC Job
self.cncjob_label = QtGui.QLabel('<b>Create CNC Job:</b>')
self.cncjob_label.setToolTip(
"Create a CNC Job object\n"
"tracing the contours of this\n"
"Geometry object."
)
self.layout.addWidget(self.cncjob_label)
grid1 = QtGui.QGridLayout()
self.layout.addLayout(grid1)
cutzlabel = QtGui.QLabel('Cut Z:')
cutzlabel.setToolTip(
"Cutting depth (negative)\n"
"below the copper surface."
)
grid1.addWidget(cutzlabel, 0, 0)
self.cutz_entry = LengthEntry()
grid1.addWidget(self.cutz_entry, 0, 1)
# Travel Z
travelzlabel = QtGui.QLabel('Travel Z:')
travelzlabel.setToolTip(
"Height of the tool when\n"
"moving without cutting."
)
grid1.addWidget(travelzlabel, 1, 0)
self.travelz_entry = LengthEntry()
grid1.addWidget(self.travelz_entry, 1, 1)
# Feedrate
frlabel = QtGui.QLabel('Feed Rate:')
frlabel.setToolTip(
"Cutting speed in the XY\n"
"plane in units per minute"
)
grid1.addWidget(frlabel, 2, 0)
self.cncfeedrate_entry = LengthEntry()
grid1.addWidget(self.cncfeedrate_entry, 2, 1)
# Tooldia
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"The diameter of the cutting\n"
"tool (just for display)."
)
grid1.addWidget(tdlabel, 3, 0)
self.cnctooldia_entry = LengthEntry()
grid1.addWidget(self.cnctooldia_entry, 3, 1)
## Paint area
self.paint_label = QtGui.QLabel('<b>Paint Area:</b>')
self.paint_label.setToolTip(
"Creates tool paths to cover the\n"
"whole area of a polygon (remove\n"
"all copper). You will be asked\n"
"to click on the desired polygon."
)
self.layout.addWidget(self.paint_label)
grid2 = QtGui.QGridLayout()
self.layout.addLayout(grid2)
# Tool dia
ptdlabel = QtGui.QLabel('Tool dia:')
ptdlabel.setToolTip(
"Diameter of the tool to\n"
"be used in the operation."
)
grid2.addWidget(ptdlabel, 0, 0)
self.painttooldia_entry = LengthEntry()
grid2.addWidget(self.painttooldia_entry, 0, 1)
# Overlap
ovlabel = QtGui.QLabel('Overlap:')
ovlabel.setToolTip(
"How much (fraction) of the tool\n"
"width to overlap each tool pass."
)
grid2.addWidget(ovlabel, 1, 0)
self.paintoverlap_entry = LengthEntry()
grid2.addWidget(self.paintoverlap_entry, 1, 1)
# Margin
marginlabel = QtGui.QLabel('Margin:')
marginlabel.setToolTip(
"Distance by which to avoid\n"
"the edges of the polygon to\n"
"be painted."
)
grid2.addWidget(marginlabel, 2, 0)
self.paintmargin_entry = LengthEntry()
grid2.addWidget(self.paintmargin_entry)
class CNCJobOptionsGroupUI(OptionsGroupUI):
def __init__(self, parent=None):
OptionsGroupUI.__init__(self, "CNC Job Options", parent=None)
## Plot options
self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.layout.addWidget(self.plot_options_label)
grid0 = QtGui.QGridLayout()
self.layout.addLayout(grid0)
# Plot CB
# self.plot_cb = QtGui.QCheckBox('Plot')
self.plot_cb = FCCheckBox('Plot')
self.plot_cb.setToolTip(
"Plot (show) this object."
)
grid0.addWidget(self.plot_cb, 0, 0)
# Tool dia for plot
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"Diameter of the tool to be\n"
"rendered in the plot."
)
grid0.addWidget(tdlabel, 1, 0)
self.tooldia_entry = LengthEntry()
grid0.addWidget(self.tooldia_entry, 1, 1)
## Export G-Code
self.export_gcode_label = QtGui.QLabel("<b>Export G-Code:</b>")
self.export_gcode_label.setToolTip(
"Export and save G-Code to\n"
"make this object to a file."
)
self.layout.addWidget(self.export_gcode_label)
# Append text to Gerber
appendlabel = QtGui.QLabel('Append to G-Code:')
appendlabel.setToolTip(
"Type here any G-Code commands you would\n"
"like to append to the generated file.\n"
"I.e.: M2 (End of program)"
)
self.layout.addWidget(appendlabel)
self.append_text = FCTextArea()
self.layout.addWidget(self.append_text)
class GlobalOptionsUI(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent=parent)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
hlay1 = QtGui.QHBoxLayout()
layout.addLayout(hlay1)
unitslabel = QtGui.QLabel('Units:')
hlay1.addWidget(unitslabel)
self.units_radio = RadioSet([{'label': 'inch', 'value': 'IN'},
{'label': 'mm', 'value': 'MM'}])
hlay1.addWidget(self.units_radio)
####### Gerber #######
# gerberlabel = QtGui.QLabel('<b>Gerber Options</b>')
# layout.addWidget(gerberlabel)
self.gerber_group = GerberOptionsGroupUI()
# self.gerber_group.setFrameStyle(QtGui.QFrame.StyledPanel)
layout.addWidget(self.gerber_group)
####### Excellon #######
# excellonlabel = QtGui.QLabel('<b>Excellon Options</b>')
# layout.addWidget(excellonlabel)
self.excellon_group = ExcellonOptionsGroupUI()
# self.excellon_group.setFrameStyle(QtGui.QFrame.StyledPanel)
layout.addWidget(self.excellon_group)
####### Geometry #######
# geometrylabel = QtGui.QLabel('<b>Geometry Options</b>')
# layout.addWidget(geometrylabel)
self.geometry_group = GeometryOptionsGroupUI()
# self.geometry_group.setStyle(QtGui.QFrame.StyledPanel)
layout.addWidget(self.geometry_group)
####### CNC #######
# cnclabel = QtGui.QLabel('<b>CNC Job Options</b>')
# layout.addWidget(cnclabel)
self.cncjob_group = CNCJobOptionsGroupUI()
# self.cncjob_group.setStyle(QtGui.QFrame.StyledPanel)
layout.addWidget(self.cncjob_group)
# def main():
#
# app = QtGui.QApplication(sys.argv)
# fc = FlatCAMGUI()
# sys.exit(app.exec_())
#
#
# if __name__ == '__main__':
# main()

View File

@@ -1,66 +1,15 @@
############################################################ from PyQt4 import QtCore
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import GObject
import inspect # TODO: Remove
import FlatCAMApp
from camlib import *
from ObjectUI import * from ObjectUI import *
import FlatCAMApp
import inspect # TODO: For debugging only.
class LoudDict(dict): from camlib import *
""" from FlatCAMCommon import LoudDict
A Dictionary with a callback for
item changes.
"""
def __init__(self, *args, **kwargs):
super(LoudDict, self).__init__(*args, **kwargs)
self.callback = lambda x: None
self.silence = False
def set_change_callback(self, callback):
"""
Assigns a function as callback on item change. The callback
will receive the key of the object that was changed.
:param callback: Function to call on item change.
:type callback: func
:return: None
"""
self.callback = callback
def __setitem__(self, key, value):
"""
Overridden __setitem__ method. Will call self.callback
if the item was changed and self.silence is False.
"""
super(LoudDict, self).__setitem__(key, value)
try:
if self.__getitem__(key) == value:
return
except KeyError:
pass
if self.silence:
return
self.callback(key)
######################################## ########################################
## FlatCAMObj ## ## FlatCAMObj ##
######################################## ########################################
class FlatCAMObj(GObject.GObject, object): class FlatCAMObj(QtCore.QObject):
""" """
Base type of objects handled in FlatCAM. These become interactive Base type of objects handled in FlatCAM. These become interactive
in the GUI, can be plotted, and their options can be modified in the GUI, can be plotted, and their options can be modified
@@ -71,7 +20,7 @@ class FlatCAMObj(GObject.GObject, object):
# The app should set this value. # The app should set this value.
app = None app = None
def __init__(self, name, ui): def __init__(self, name):
""" """
:param name: Name of the object given by the user. :param name: Name of the object given by the user.
@@ -79,53 +28,60 @@ class FlatCAMObj(GObject.GObject, object):
:type ui: ObjectUI :type ui: ObjectUI
:return: FlatCAMObj :return: FlatCAMObj
""" """
GObject.GObject.__init__(self) QtCore.QObject.__init__(self)
# View # View
self.ui = ui self.ui = None
self.options = LoudDict(name=name) self.options = LoudDict(name=name)
self.options.set_change_callback(self.on_options_change) self.options.set_change_callback(self.on_options_change)
self.form_fields = {"name": self.ui.name_entry} self.form_fields = {}
self.radios = {} # Name value pairs for radio sets
self.radios_inv = {} # Inverse of self.radios
self.axes = None # Matplotlib axes self.axes = None # Matplotlib axes
self.kind = None # Override with proper name self.kind = None # Override with proper name
self.muted_ui = False self.muted_ui = False
self.ui.name_entry.connect('activate', self.on_name_activate) # assert isinstance(self.ui, ObjectUI)
self.ui.offset_button.connect('clicked', self.on_offset_button_click) # self.ui.name_entry.returnPressed.connect(self.on_name_activate)
self.ui.offset_button.connect('activate', self.on_offset_button_click) # self.ui.offset_button.clicked.connect(self.on_offset_button_click)
self.ui.scale_button.connect('clicked', self.on_scale_button_click) # self.ui.scale_button.clicked.connect(self.on_scale_button_click)
self.ui.scale_button.connect('activate', self.on_scale_button_click)
def on_options_change(self, key):
self.emit(QtCore.SIGNAL("optionChanged"), key)
def set_ui(self, ui):
self.ui = ui
self.form_fields = {"name": self.ui.name_entry}
assert isinstance(self.ui, ObjectUI)
self.ui.name_entry.returnPressed.connect(self.on_name_activate)
self.ui.offset_button.clicked.connect(self.on_offset_button_click)
self.ui.scale_button.clicked.connect(self.on_scale_button_click)
def __str__(self): def __str__(self):
return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"]) return "<FlatCAMObj({:12s}): {:20s}>".format(self.kind, self.options["name"])
def on_name_activate(self, *args): def on_name_activate(self):
old_name = copy(self.options["name"]) old_name = copy(self.options["name"])
new_name = self.ui.name_entry.get_text() new_name = self.ui.name_entry.get_value()
self.options["name"] = self.ui.name_entry.get_text() self.options["name"] = self.ui.name_entry.get_value()
self.app.info("Name changed from %s to %s" % (old_name, new_name)) self.app.info("Name changed from %s to %s" % (old_name, new_name))
def on_offset_button_click(self, *args): def on_offset_button_click(self):
self.read_form() self.read_form()
vect = self.ui.offsetvector_entry.get_value() vect = self.ui.offsetvector_entry.get_value()
self.offset(vect) self.offset(vect)
self.plot() self.plot()
def on_scale_button_click(self, *args): def on_scale_button_click(self):
self.read_form() self.read_form()
factor = self.ui.scale_entry.get_value() factor = self.ui.scale_entry.get_value()
self.scale(factor) self.scale(factor)
self.plot() self.plot()
def on_options_change(self, key):
self.form_fields[key].set_value(self.options[key])
return
def setup_axes(self, figure): def setup_axes(self, figure):
""" """
1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches 1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches
@@ -189,21 +145,24 @@ class FlatCAMObj(GObject.GObject, object):
self.muted_ui = True self.muted_ui = True
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()") FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> FlatCAMObj.build_ui()")
# Where the UI for this object is drawn
# box_selected = self.app.builder.get_object("box_selected")
box_selected = self.app.builder.get_object("vp_selected")
# Remove anything else in the box # Remove anything else in the box
box_children = box_selected.get_children() # box_children = self.app.ui.notebook.selected_contents.get_children()
for child in box_children: # for child in box_children:
box_selected.remove(child) # self.app.ui.notebook.selected_contents.remove(child)
# while self.app.ui.selected_layout.count():
# self.app.ui.selected_layout.takeAt(0)
# Put in the UI # Put in the UI
# box_selected.pack_start(sw, True, True, 0) # box_selected.pack_start(sw, True, True, 0)
box_selected.add(self.ui) # self.app.ui.notebook.selected_contents.add(self.ui)
# self.app.ui.selected_layout.addWidget(self.ui)
try:
self.app.ui.selected_scroll_area.takeWidget()
except:
self.app.log.debug("Nothing to remove")
self.app.ui.selected_scroll_area.setWidget(self.ui)
self.to_form() self.to_form()
GLib.idle_add(box_selected.show_all)
GLib.idle_add(self.ui.show_all)
self.muted_ui = False self.muted_ui = False
def set_form_item(self, option): def set_form_item(self, option):
@@ -243,6 +202,7 @@ class FlatCAMObj(GObject.GObject, object):
:return: Whether to continue plotting or not depending on the "plot" option. :return: Whether to continue plotting or not depending on the "plot" option.
:rtype: bool :rtype: bool
""" """
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
# Axes must exist and be attached to canvas. # Axes must exist and be attached to canvas.
if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes: if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes:
@@ -254,7 +214,7 @@ class FlatCAMObj(GObject.GObject, object):
return False return False
# Clear axes or we will plot on top of them. # Clear axes or we will plot on top of them.
self.axes.cla() self.axes.cla() # TODO: Thread safe?
# GLib.idle_add(self.axes.cla) # GLib.idle_add(self.axes.cla)
return True return True
@@ -284,29 +244,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
Represents Gerber code. Represents Gerber code.
""" """
ui_type = GerberObjectUI
def __init__(self, name): def __init__(self, name):
Gerber.__init__(self) Gerber.__init__(self)
FlatCAMObj.__init__(self, name, GerberObjectUI()) FlatCAMObj.__init__(self, name)
self.kind = "gerber" self.kind = "gerber"
self.form_fields.update({
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"solid": self.ui.solid_cb,
"isotooldia": self.ui.iso_tool_dia_entry,
"isopasses": self.ui.iso_width_entry,
"isooverlap": self.ui.iso_overlap_entry,
"cutouttooldia": self.ui.cutout_tooldia_entry,
"cutoutmargin": self.ui.cutout_margin_entry,
"cutoutgapsize": self.ui.cutout_gap_entry,
"gaps": self.ui.gaps_radio,
"noncoppermargin": self.ui.noncopper_margin_entry,
"noncopperrounded": self.ui.noncopper_rounded_cb,
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb
})
# The 'name' is already in self.options from FlatCAMObj # The 'name' is already in self.options from FlatCAMObj
# Automatically updates the UI # Automatically updates the UI
self.options.update({ self.options.update({
@@ -331,21 +276,45 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# from predecessors. # from predecessors.
self.ser_attrs += ['options', 'kind'] self.ser_attrs += ['options', 'kind']
# assert isinstance(self.ui, GerberObjectUI)
# self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
# self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
# self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
# self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
# self.ui.generate_cutout_button.clicked.connect(self.on_generatecutout_button_click)
# self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
# self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
FlatCAMApp.App.log.debug("FlatCAMGerber.set_ui()")
self.form_fields.update({
"plot": self.ui.plot_cb,
"multicolored": self.ui.multicolored_cb,
"solid": self.ui.solid_cb,
"isotooldia": self.ui.iso_tool_dia_entry,
"isopasses": self.ui.iso_width_entry,
"isooverlap": self.ui.iso_overlap_entry,
"cutouttooldia": self.ui.cutout_tooldia_entry,
"cutoutmargin": self.ui.cutout_margin_entry,
"cutoutgapsize": self.ui.cutout_gap_entry,
"gaps": self.ui.gaps_radio,
"noncoppermargin": self.ui.noncopper_margin_entry,
"noncopperrounded": self.ui.noncopper_rounded_cb,
"bboxmargin": self.ui.bbmargin_entry,
"bboxrounded": self.ui.bbrounded_cb
})
assert isinstance(self.ui, GerberObjectUI) assert isinstance(self.ui, GerberObjectUI)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click) self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click) self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click) self.ui.multicolored_cb.stateChanged.connect(self.on_multicolored_cb_click)
self.ui.solid_cb.connect('activate', self.on_solid_cb_click) self.ui.generate_iso_button.clicked.connect(self.on_iso_button_click)
self.ui.multicolored_cb.connect('clicked', self.on_multicolored_cb_click) self.ui.generate_cutout_button.clicked.connect(self.on_generatecutout_button_click)
self.ui.multicolored_cb.connect('activate', self.on_multicolored_cb_click) self.ui.generate_bb_button.clicked.connect(self.on_generatebb_button_click)
self.ui.generate_iso_button.connect('clicked', self.on_iso_button_click) self.ui.generate_noncopper_button.clicked.connect(self.on_generatenoncopper_button_click)
self.ui.generate_iso_button.connect('activate', self.on_iso_button_click)
self.ui.generate_cutout_button.connect('clicked', self.on_generatecutout_button_click)
self.ui.generate_cutout_button.connect('activate', self.on_generatecutout_button_click)
self.ui.generate_bb_button.connect('clicked', self.on_generatebb_button_click)
self.ui.generate_bb_button.connect('activate', self.on_generatebb_button_click)
self.ui.generate_noncopper_button.connect('clicked', self.on_generatenoncopper_button_click)
self.ui.generate_noncopper_button.connect('activate', self.on_generatenoncopper_button_click)
def on_generatenoncopper_button_click(self, *args): def on_generatenoncopper_button_click(self, *args):
self.read_form() self.read_form()
@@ -478,17 +447,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def plot(self): def plot(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
# Does all the required setup and returns False # Does all the required setup and returns False
# if the 'ptint' option is set to False. # if the 'ptint' option is set to False.
if not FlatCAMObj.plot(self): if not FlatCAMObj.plot(self):
return return
# if self.options["mergepolys"]:
# geometry = self.solid_geometry
# else:
# geometry = self.buffered_paths + \
# [poly['polygon'] for poly in self.regions] + \
# self.flash_geometry
geometry = self.solid_geometry geometry = self.solid_geometry
# Make sure geometry is iterable. # Make sure geometry is iterable.
@@ -523,8 +488,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
x, y = ints.coords.xy x, y = ints.coords.xy
self.axes.plot(x, y, linespec) self.axes.plot(x, y, linespec)
# self.app.plotcanvas.auto_adjust_axes() self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes) #GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
#self.emit(QtCore.SIGNAL("plotChanged"), self)
def serialize(self): def serialize(self):
return { return {
@@ -538,29 +504,21 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
Represents Excellon/Drill code. Represents Excellon/Drill code.
""" """
ui_type = ExcellonObjectUI
def __init__(self, name): def __init__(self, name):
Excellon.__init__(self) Excellon.__init__(self)
FlatCAMObj.__init__(self, name, ExcellonObjectUI()) FlatCAMObj.__init__(self, name)
self.kind = "excellon" self.kind = "excellon"
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
"solid": self.ui.solid_cb,
"drillz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.feedrate_entry,
"toolselection": self.ui.tools_entry
})
self.options.update({ self.options.update({
"plot": True, "plot": True,
"solid": False, "solid": False,
"drillz": -0.1, "drillz": -0.1,
"travelz": 0.1, "travelz": 0.1,
"feedrate": 5.0, "feedrate": 5.0,
"toolselection": "" # "toolselection": ""
}) })
# TODO: Document this. # TODO: Document this.
@@ -571,49 +529,100 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# from predecessors. # from predecessors.
self.ser_attrs += ['options', 'kind'] self.ser_attrs += ['options', 'kind']
def build_ui(self):
FlatCAMObj.build_ui(self)
# Populate tool list
n = len(self.tools)
self.ui.tools_table.setColumnCount(2)
self.ui.tools_table.setHorizontalHeaderLabels(['#', 'Diameter'])
self.ui.tools_table.setRowCount(n)
self.ui.tools_table.setSortingEnabled(False)
i = 0
for tool in self.tools:
id = QtGui.QTableWidgetItem(tool)
id.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)
self.ui.tools_table.setItem(i, 0, id) # Tool name/id
dia = QtGui.QTableWidgetItem(str(self.tools[tool]['C']))
dia.setFlags(QtCore.Qt.ItemIsEnabled)
self.ui.tools_table.setItem(i, 1, dia) # Diameter
i += 1
self.ui.tools_table.resizeColumnsToContents()
self.ui.tools_table.resizeRowsToContents()
self.ui.tools_table.horizontalHeader().setStretchLastSection(True)
self.ui.tools_table.verticalHeader().hide()
self.ui.tools_table.setSortingEnabled(True)
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
FlatCAMApp.App.log.debug("FlatCAMExcellon.set_ui()")
self.form_fields.update({
"plot": self.ui.plot_cb,
"solid": self.ui.solid_cb,
"drillz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.feedrate_entry,
# "toolselection": self.ui.tools_entry
})
assert isinstance(self.ui, ExcellonObjectUI) assert isinstance(self.ui, ExcellonObjectUI)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click) self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click) self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click) # self.ui.choose_tools_button.clicked.connect(self.show_tool_chooser)
self.ui.solid_cb.connect('activate', self.on_solid_cb_click) self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
self.ui.choose_tools_button.connect('clicked', lambda args: self.show_tool_chooser())
self.ui.choose_tools_button.connect('activate', lambda args: self.show_tool_chooser())
self.ui.generate_cnc_button.connect('clicked', self.on_create_cncjob_button_click)
self.ui.generate_cnc_button.connect('activate', self.on_create_cncjob_button_click)
def on_create_cncjob_button_click(self, *args): def on_create_cncjob_button_click(self, *args):
self.read_form() self.read_form()
# Get the tools from the list
tools = [str(x.text()) for x in self.ui.tools_table.selectedItems()]
if len(tools) == 0:
self.app.inform.emit("Please select one or more tools from the list and try again.")
return
job_name = self.options["name"] + "_cnc" job_name = self.options["name"] + "_cnc"
# Object initialization function for app.new_object() # Object initialization function for app.new_object()
def job_init(job_obj, app_obj): def job_init(job_obj, app_obj):
assert isinstance(job_obj, FlatCAMCNCjob) assert isinstance(job_obj, FlatCAMCNCjob)
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
app_obj.progress.emit(20)
job_obj.z_cut = self.options["drillz"] job_obj.z_cut = self.options["drillz"]
job_obj.z_move = self.options["travelz"] job_obj.z_move = self.options["travelz"]
job_obj.feedrate = self.options["feedrate"] job_obj.feedrate = self.options["feedrate"]
# There could be more than one drill size... # There could be more than one drill size...
# job_obj.tooldia = # TODO: duplicate variable! # job_obj.tooldia = # TODO: duplicate variable!
# job_obj.options["tooldia"] = # job_obj.options["tooldia"] =
job_obj.generate_from_excellon_by_tool(self, self.options["toolselection"])
GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code...")) tools_csv = ','.join(tools)
# job_obj.generate_from_excellon_by_tool(self, self.options["toolselection"])
job_obj.generate_from_excellon_by_tool(self, tools_csv)
# GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
app_obj.progress.emit(50)
job_obj.gcode_parse() job_obj.gcode_parse()
GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
app_obj.progress.emit(60)
job_obj.create_geometry() job_obj.create_geometry()
GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
app_obj.progress.emit(80)
# To be run in separate thread # To be run in separate thread
def job_thread(app_obj): def job_thread(app_obj):
app_obj.new_object("cncjob", job_name, job_init) app_obj.new_object("cncjob", job_name, job_init)
GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!")) # GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, "")) app_obj.progress.emit(100)
# GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
# Send to worker # Send to worker
self.app.worker.add_task(job_thread, [self.app]) # self.app.worker.add_task(job_thread, [self.app])
self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def on_plot_cb_click(self, *args): def on_plot_cb_click(self, *args):
if self.muted_ui: if self.muted_ui:
@@ -663,32 +672,34 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
x, y = ints.coords.xy x, y = ints.coords.xy
self.axes.plot(x, y, 'g-') self.axes.plot(x, y, 'g-')
#self.app.plotcanvas.auto_adjust_axes() self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes) # GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
# self.emit(QtCore.SIGNAL("plotChanged"), self)
def show_tool_chooser(self): def show_tool_chooser(self):
win = Gtk.Window() # win = Gtk.Window()
box = Gtk.Box(spacing=2) # box = Gtk.Box(spacing=2)
box.set_orientation(Gtk.Orientation(1)) # box.set_orientation(Gtk.Orientation(1))
win.add(box) # win.add(box)
for tool in self.tools: # for tool in self.tools:
self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + str(self.tools[tool])) # self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + str(self.tools[tool]))
box.pack_start(self.tool_cbs[tool], False, False, 1) # box.pack_start(self.tool_cbs[tool], False, False, 1)
button = Gtk.Button(label="Accept") # button = Gtk.Button(label="Accept")
box.pack_start(button, False, False, 1) # box.pack_start(button, False, False, 1)
win.show_all() # win.show_all()
#
def on_accept(widget): # def on_accept(widget):
win.destroy() # win.destroy()
tool_list = [] # tool_list = []
for toolx in self.tool_cbs: # for toolx in self.tool_cbs:
if self.tool_cbs[toolx].get_active(): # if self.tool_cbs[toolx].get_active():
tool_list.append(toolx) # tool_list.append(toolx)
self.options["toolselection"] = ", ".join(tool_list) # self.options["toolselection"] = ", ".join(tool_list)
self.to_form() # self.to_form()
#
button.connect("activate", on_accept) # button.connect("activate", on_accept)
button.connect("clicked", on_accept) # button.connect("clicked", on_accept)
return
class FlatCAMCNCjob(FlatCAMObj, CNCjob): class FlatCAMCNCjob(FlatCAMObj, CNCjob):
@@ -696,23 +707,21 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
Represents G-Code. Represents G-Code.
""" """
ui_type = CNCObjectUI
def __init__(self, name, units="in", kind="generic", z_move=0.1, def __init__(self, name, units="in", kind="generic", z_move=0.1,
feedrate=3.0, z_cut=-0.002, tooldia=0.0): feedrate=3.0, z_cut=-0.002, tooldia=0.0):
FlatCAMApp.App.log.debug("Creating CNCJob object...")
CNCjob.__init__(self, units=units, kind=kind, z_move=z_move, CNCjob.__init__(self, units=units, kind=kind, z_move=z_move,
feedrate=feedrate, z_cut=z_cut, tooldia=tooldia) feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
FlatCAMObj.__init__(self, name, CNCObjectUI()) FlatCAMObj.__init__(self, name)
self.kind = "cncjob" self.kind = "cncjob"
self.options.update({ self.options.update({
"plot": True, "plot": True,
"tooldia": 0.4 / 25.4 # 0.4mm in inches "tooldia": 0.4 / 25.4, # 0.4mm in inches
}) "append": ""
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
"tooldia": self.ui.tooldia_entry
}) })
# Attributes to be included in serialization # Attributes to be included in serialization
@@ -720,25 +729,47 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
# from predecessors. # from predecessors.
self.ser_attrs += ['options', 'kind'] self.ser_attrs += ['options', 'kind']
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click) def set_ui(self, ui):
self.ui.plot_cb.connect('activate', self.on_plot_cb_click) FlatCAMObj.set_ui(self, ui)
self.ui.updateplot_button.connect('clicked', self.on_updateplot_button_click)
self.ui.updateplot_button.connect('activate', self.on_updateplot_button_click) FlatCAMApp.App.log.debug("FlatCAMCNCJob.set_ui()")
self.ui.export_gcode_button.connect('clicked', self.on_exportgcode_button_click)
self.ui.export_gcode_button.connect('activate', self.on_exportgcode_button_click) assert isinstance(self.ui, CNCObjectUI)
self.form_fields.update({
"plot": self.ui.plot_cb,
"tooldia": self.ui.tooldia_entry,
"append": self.ui.append_text
})
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.updateplot_button.clicked.connect(self.on_updateplot_button_click)
self.ui.export_gcode_button.clicked.connect(self.on_exportgcode_button_click)
def on_updateplot_button_click(self, *args): def on_updateplot_button_click(self, *args):
"""
Callback for the "Updata Plot" button. Reads the form for updates
and plots the object.
"""
self.read_form() self.read_form()
self.plot() self.plot()
def on_exportgcode_button_click(self, *args): def on_exportgcode_button_click(self, *args):
def on_success(app_obj, filename):
f = open(filename, 'w')
f.write(self.gcode)
f.close()
app_obj.info("Saved to: " + filename)
self.app.file_chooser_save_action(on_success) try:
filename = QtGui.QFileDialog.getSaveFileName(caption="Export G-Code ...",
directory=self.app.last_folder)
except TypeError:
filename = QtGui.QFileDialog.getSaveFileName(caption="Export G-Code ...")
postamble = str(self.ui.append_text.get_value())
f = open(filename, 'w')
f.write(self.gcode + "\n" + postamble)
f.close()
self.app.file_opened.emit("cncjob", filename)
self.app.inform.emit("Saved to: " + filename)
def on_plot_cb_click(self, *args): def on_plot_cb_click(self, *args):
if self.muted_ui: if self.muted_ui:
@@ -755,8 +786,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.plot2(self.axes, tooldia=self.options["tooldia"]) self.plot2(self.axes, tooldia=self.options["tooldia"])
#self.app.plotcanvas.auto_adjust_axes() self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
def convert_units(self, units): def convert_units(self, units):
factor = CNCjob.convert_units(self, units) factor = CNCjob.convert_units(self, units)
@@ -770,26 +800,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
format. format.
""" """
ui_type = GeometryObjectUI
def __init__(self, name): def __init__(self, name):
FlatCAMObj.__init__(self, name, GeometryObjectUI()) FlatCAMObj.__init__(self, name)
Geometry.__init__(self) Geometry.__init__(self)
self.kind = "geometry" self.kind = "geometry"
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
# "solid": self.ui.sol,
# "multicolored": self.ui.,
"cutz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.cncfeedrate_entry,
"cnctooldia": self.ui.cnctooldia_entry,
"painttooldia": self.ui.painttooldia_entry,
"paintoverlap": self.ui.paintoverlap_entry,
"paintmargin": self.ui.paintmargin_entry
})
self.options.update({ self.options.update({
"plot": True, "plot": True,
# "solid": False, # "solid": False,
@@ -803,31 +821,34 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"paintmargin": 0.01 "paintmargin": 0.01
}) })
# self.form_kinds.update({
# "plot": "cb",
# "solid": "cb",
# "multicolored": "cb",
# "cutz": "entry_eval",
# "travelz": "entry_eval",
# "feedrate": "entry_eval",
# "cnctooldia": "entry_eval",
# "painttooldia": "entry_eval",
# "paintoverlap": "entry_eval",
# "paintmargin": "entry_eval"
# })
# Attributes to be included in serialization # Attributes to be included in serialization
# Always append to it because it carries contents # Always append to it because it carries contents
# from predecessors. # from predecessors.
self.ser_attrs += ['options', 'kind'] self.ser_attrs += ['options', 'kind']
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
FlatCAMApp.App.log.debug("FlatCAMGeometry.set_ui()")
assert isinstance(self.ui, GeometryObjectUI) assert isinstance(self.ui, GeometryObjectUI)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click) self.form_fields.update({
self.ui.generate_cnc_button.connect('clicked', self.on_generatecnc_button_click) "plot": self.ui.plot_cb,
self.ui.generate_cnc_button.connect('activate', self.on_generatecnc_button_click) # "solid": self.ui.sol,
self.ui.generate_paint_button.connect('clicked', self.on_paint_button_click) # "multicolored": self.ui.,
self.ui.generate_paint_button.connect('activate', self.on_paint_button_click) "cutz": self.ui.cutz_entry,
"travelz": self.ui.travelz_entry,
"feedrate": self.ui.cncfeedrate_entry,
"cnctooldia": self.ui.cnctooldia_entry,
"painttooldia": self.ui.painttooldia_entry,
"paintoverlap": self.ui.paintoverlap_entry,
"paintmargin": self.ui.paintmargin_entry
})
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.generate_cnc_button.clicked.connect(self.on_generatecnc_button_click)
self.ui.generate_paint_button.clicked.connect(self.on_paint_button_click)
def on_paint_button_click(self, *args): def on_paint_button_click(self, *args):
self.app.info("Click inside the desired polygon.") self.app.info("Click inside the desired polygon.")
@@ -868,35 +889,41 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# Propagate options # Propagate options
job_obj.options["tooldia"] = self.options["cnctooldia"] job_obj.options["tooldia"] = self.options["cnctooldia"]
GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.2, "Creating CNC Job..."))
app_obj.progress.emit(20)
job_obj.z_cut = self.options["cutz"] job_obj.z_cut = self.options["cutz"]
job_obj.z_move = self.options["travelz"] job_obj.z_move = self.options["travelz"]
job_obj.feedrate = self.options["feedrate"] job_obj.feedrate = self.options["feedrate"]
GLib.idle_add(lambda: app_obj.set_progress_bar(0.4, "Analyzing Geometry...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.4, "Analyzing Geometry..."))
app_obj.progress.emit(40)
# TODO: The tolerance should not be hard coded. Just for testing. # TODO: The tolerance should not be hard coded. Just for testing.
job_obj.generate_from_geometry(self, tolerance=0.0005) job_obj.generate_from_geometry(self, tolerance=0.0005)
GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.5, "Parsing G-Code..."))
app_obj.progress.emit(50)
job_obj.gcode_parse() job_obj.gcode_parse()
# TODO: job_obj.create_geometry creates stuff that is not used. # TODO: job_obj.create_geometry creates stuff that is not used.
#GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry...")) #GLib.idle_add(lambda: app_obj.set_progress_bar(0.6, "Creating New Geometry..."))
#job_obj.create_geometry() #job_obj.create_geometry()
GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting...")) # GLib.idle_add(lambda: app_obj.set_progress_bar(0.8, "Plotting..."))
app_obj.progress.emit(80)
# To be run in separate thread # To be run in separate thread
def job_thread(app_obj): def job_thread(app_obj):
app_obj.new_object("cncjob", job_name, job_init) app_obj.new_object("cncjob", job_name, job_init)
GLib.idle_add(lambda: app_obj.info("CNCjob created: %s" % job_name)) # GLib.idle_add(lambda: app_obj.info("CNCjob created: %s" % job_name))
GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!")) # GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, "Idle")) # GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, "Idle"))
app_obj.inform.emit("CNCjob created: %s" % job_name)
app_obj.progress.emit(100)
# Send to worker # Send to worker
self.app.worker.add_task(job_thread, [self.app]) self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
def on_plot_cb_click(self, *args): def on_plot_cb_click(self, *args): # TODO: args not needed
if self.muted_ui: if self.muted_ui:
return return
self.read_form_item('plot') self.read_form_item('plot')
@@ -994,5 +1021,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
FlatCAMApp.App.log.warning("Did not plot:", str(type(geo))) FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
#self.app.plotcanvas.auto_adjust_axes() self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes) # GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
# self.emit(QtCore.SIGNAL("plotChanged"), self)

258
FlatCAMTool.py Normal file
View File

@@ -0,0 +1,258 @@
from PyQt4 import QtGui, QtCore
from shapely.geometry import Point
from shapely import affinity
from math import sqrt
import FlatCAMApp
from GUIElements import *
from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon
class FlatCAMTool(QtGui.QWidget):
toolName = "FlatCAM Generic Tool"
def __init__(self, app, parent=None):
"""
:param app: The application this tool will run in.
:type app: App
:param parent: Qt Parent
:return: FlatCAMTool
"""
QtGui.QWidget.__init__(self, parent)
# self.setSizePolicy(QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Maximum)
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
self.app = app
self.menuAction = None
def install(self):
self.menuAction = self.app.ui.menutool.addAction(self.toolName)
self.menuAction.triggered.connect(self.run)
def run(self):
# Remove anything else in the GUI
self.app.ui.tool_scroll_area.takeWidget()
# Put ourself in the GUI
self.app.ui.tool_scroll_area.setWidget(self)
# Switch notebook to tool page
self.app.ui.notebook.setCurrentWidget(self.app.ui.tool_tab)
self.show()
class DblSidedTool(FlatCAMTool):
toolName = "Double-Sided PCB Tool"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
## Title
title_label = QtGui.QLabel("<font size=4><b>%s</b></font>" % self.toolName)
self.layout.addWidget(title_label)
## Form Layout
form_layout = QtGui.QFormLayout()
self.layout.addLayout(form_layout)
## Layer to mirror
self.object_combo = QtGui.QComboBox()
self.object_combo.setModel(self.app.collection)
form_layout.addRow("Bottom Layer:", self.object_combo)
## Axis
self.mirror_axis = RadioSet([{'label': 'X', 'value': 'X'},
{'label': 'Y', 'value': 'Y'}])
form_layout.addRow("Mirror Axis:", self.mirror_axis)
## Axis Location
self.axis_location = RadioSet([{'label': 'Point', 'value': 'point'},
{'label': 'Box', 'value': 'box'}])
form_layout.addRow("Axis Location:", self.axis_location)
## Point/Box
self.point_box_container = QtGui.QVBoxLayout()
form_layout.addRow("Point/Box:", self.point_box_container)
self.point = EvalEntry()
self.point_box_container.addWidget(self.point)
self.box_combo = QtGui.QComboBox()
self.box_combo.setModel(self.app.collection)
self.point_box_container.addWidget(self.box_combo)
self.box_combo.hide()
## Alignment holes
self.alignment_holes = EvalEntry()
form_layout.addRow("Alignment Holes:", self.alignment_holes)
## Drill diameter for alignment holes
self.drill_dia = LengthEntry()
form_layout.addRow("Drill diam.:", self.drill_dia)
## Buttons
hlay = QtGui.QHBoxLayout()
self.layout.addLayout(hlay)
hlay.addStretch()
self.create_alignment_hole_button = QtGui.QPushButton("Create Alignment Drill")
self.mirror_object_button = QtGui.QPushButton("Mirror Object")
hlay.addWidget(self.create_alignment_hole_button)
hlay.addWidget(self.mirror_object_button)
self.layout.addStretch()
## Signals
self.create_alignment_hole_button.clicked.connect(self.on_create_alignment_holes)
self.mirror_object_button.clicked.connect(self.on_mirror)
self.axis_location.group_toggle_fn = self.on_toggle_pointbox
## Initialize form
self.mirror_axis.set_value('X')
self.axis_location.set_value('point')
def on_create_alignment_holes(self):
axis = self.mirror_axis.get_value()
mode = self.axis_location.get_value()
if mode == "point":
px, py = self.point.get_value()
else:
selection_index = self.box_combo.currentIndex()
bb_obj = self.app.collection.object_list[selection_index] # TODO: Direct access??
xmin, ymin, xmax, ymax = bb_obj.bounds()
px = 0.5*(xmin+xmax)
py = 0.5*(ymin+ymax)
xscale, yscale = {"X": (1.0, -1.0), "Y": (-1.0, 1.0)}[axis]
dia = self.drill_dia.get_value()
tools = {"1": {"C": dia}}
holes = self.alignment_holes.get_value()
drills = []
for hole in holes:
point = Point(hole)
point_mirror = affinity.scale(point, xscale, yscale, origin=(px, py))
drills.append({"point": point, "tool": "1"})
drills.append({"point": point_mirror, "tool": "1"})
def obj_init(obj_inst, app_inst):
obj_inst.tools = tools
obj_inst.drills = drills
obj_inst.create_geometry()
self.app.new_object("excellon", "Alignment Drills", obj_init)
def on_mirror(self):
selection_index = self.object_combo.currentIndex()
fcobj = self.app.collection.object_list[selection_index]
# For now, lets limit to Gerbers and Excellons.
# assert isinstance(gerb, FlatCAMGerber)
if not isinstance(fcobj, FlatCAMGerber) and not isinstance(fcobj, FlatCAMExcellon):
self.info("ERROR: Only Gerber and Excellon objects can be mirrored.")
return
axis = self.mirror_axis.get_value()
mode = self.axis_location.get_value()
if mode == "point":
px, py = self.point.get_value()
else:
selection_index = self.box_combo.currentIndex()
bb_obj = self.app.collection.object_list[selection_index] # TODO: Direct access??
xmin, ymin, xmax, ymax = bb_obj.bounds()
px = 0.5*(xmin+xmax)
py = 0.5*(ymin+ymax)
fcobj.mirror(axis, [px, py])
fcobj.plot()
def on_toggle_pointbox(self):
if self.axis_location.get_value() == "point":
self.point.show()
self.box_combo.hide()
else:
self.point.hide()
self.box_combo.show()
class Measurement(FlatCAMTool):
toolName = "Measurement Tool"
def __init__(self, app):
FlatCAMTool.__init__(self, app)
# self.setContentsMargins(0, 0, 0, 0)
self.layout.setMargin(0)
self.layout.setContentsMargins(0, 0, 3, 0)
self.setSizePolicy(QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Maximum)
self.point1 = None
self.point2 = None
self.label = QtGui.QLabel("Click on a reference point ...")
self.label.setFrameStyle(QtGui.QFrame.StyledPanel | QtGui.QFrame.Plain)
self.label.setMargin(3)
self.layout.addWidget(self.label)
# self.layout.setMargin(0)
self.setVisible(False)
self.click_subscription = None
self.move_subscription = None
def install(self):
FlatCAMTool.install(self)
self.app.ui.right_layout.addWidget(self)
self.app.plotcanvas.mpl_connect('key_press_event', self.on_key_press)
def run(self):
self.toggle()
def on_click(self, event):
if self.point1 is None:
self.point1 = (event.xdata, event.ydata)
else:
self.point2 = copy(self.point1)
self.point1 = (event.xdata, event.ydata)
self.on_move(event)
def on_key_press(self, event):
if event.key == 'm':
self.toggle()
def toggle(self):
if self.isVisible():
self.setVisible(False)
self.app.plotcanvas.mpl_disconnect(self.move_subscription)
self.app.plotcanvas.mpl_disconnect(self.click_subscription)
else:
self.setVisible(True)
self.move_subscription = self.app.plotcanvas.mpl_connect('motion_notify_event', self.on_move)
self.click_subscription = self.app.plotcanvas.mpl_connect('button_press_event', self.on_click)
def on_move(self, event):
if self.point1 is None:
self.label.setText("Click on a reference point...")
else:
try:
dx = event.xdata - self.point1[0]
dy = event.ydata - self.point1[1]
d = sqrt(dx**2 + dy**2)
self.label.setText("D = %.4f D(x) = %.4f D(y) = %.4f" % (d, dx, dy))
except TypeError:
pass
if self.update is not None:
self.update()

View File

@@ -1,43 +1,29 @@
############################################################ from PyQt4 import QtCore
# FlatCAM: 2D Post-processing for Manufacturing # #import Queue
# http://caram.cl/software/flatcam # import FlatCAMApp
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
import threading
import Queue
class Worker(threading.Thread): class Worker(QtCore.QObject):
""" """
Implements a queue of tasks to be carried out in order Implements a queue of tasks to be carried out in order
in a single independent thread. in a single independent thread.
""" """
def __init__(self): def __init__(self, app, name=None):
super(Worker, self).__init__() super(Worker, self).__init__()
self.queue = Queue.Queue() self.app = app
self.stoprequest = threading.Event() self.name = name
def run(self): def run(self):
while not self.stoprequest.isSet(): FlatCAMApp.App.log.debug("Worker Started!")
try: self.app.worker_task.connect(self.do_worker_task)
task = self.queue.get(True, 0.05)
self.do_task(task)
except Queue.Empty:
continue
@staticmethod def do_worker_task(self, task):
def do_task(task): FlatCAMApp.App.log.debug("Running task: %s" % str(task))
task['fcn'](*task['params']) if 'worker_name' in task and task['worker_name'] == self.name:
return task['fcn'](*task['params'])
return
def add_task(self, target, params=list()): if 'worker_name' not in task and self.name is None:
self.queue.put({'fcn': target, 'params': params}) task['fcn'](*task['params'])
return return
def join(self, timeout=None):
self.stoprequest.set()
super(Worker, self).join()

46
FlatCAM_GTK/FCNoteBook.py Normal file
View File

@@ -0,0 +1,46 @@
from gi.repository import Gtk
class FCNoteBook(Gtk.Notebook):
def __init__(self):
Gtk.Notebook.__init__(self, vexpand=True, vexpand_set=True, valign=1, expand=True)
###############
### Project ###
###############
self.project_contents = Gtk.VBox(vexpand=True, valign=0, vexpand_set=True, expand=True)
sw1 = Gtk.ScrolledWindow(vexpand=True, valign=0, vexpand_set=True, expand=True)
sw1.add_with_viewport(self.project_contents)
self.project_page_num = self.append_page(sw1, Gtk.Label("Project"))
################
### Selected ###
################
self.selected_contents = Gtk.VBox()
sw2 = Gtk.ScrolledWindow()
sw2.add_with_viewport(self.selected_contents)
self.selected_page_num = self.append_page(sw2, Gtk.Label("Selected"))
###############
### Options ###
###############
self.options_contents_super = Gtk.VBox()
sw3 = Gtk.ScrolledWindow()
sw3.add_with_viewport(self.options_contents_super)
self.options_page_num = self.append_page(sw3, Gtk.Label("Options"))
hb = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
ico = Gtk.Image.new_from_file("share/gear32.png")
hb.pack_start(ico, expand=False, fill=False, padding=0)
self.combo_options = Gtk.ComboBoxText()
hb.pack_start(self.combo_options, expand=True, fill=True, padding=0)
self.options_contents_super.pack_start(hb, expand=False, fill=False, padding=0)
self.options_contents = Gtk.VBox()
self.options_contents_super.pack_start(self.options_contents, expand=False, fill=False, padding=0)
############
### Tool ###
############
self.tool_contents = Gtk.VBox()
self.tool_page_num = self.append_page(self.tool_contents, Gtk.Label("Tool"))

15
FlatCAM_GTK/FlatCAM.py Normal file
View File

@@ -0,0 +1,15 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk
from FlatCAM_GTK.FlatCAMApp import *
app = App()
Gtk.main()

2478
FlatCAM_GTK/FlatCAMApp.py Normal file

File diff suppressed because it is too large Load Diff

303
FlatCAM_GTK/FlatCAMGUI.py Normal file
View File

@@ -0,0 +1,303 @@
from gi.repository import Gtk
from FlatCAM_GTK import FCNoteBook
class FlatCAMGUI(Gtk.Window):
MENU = """
<ui>
<menubar name='MenuBar'>
<menu action='FileMenu'>
<menuitem action='FileNew'>
<separator />
<menuitem action='FileQuit' />
</menu>
</menubar>
<toolbar name='ToolBar'>
<toolitem action='FileNewStandard' />
<toolitem action='FileQuit' />
</toolbar>
</ui>
"""
def __init__(self):
"""
:return: The FlatCAM window.
:rtype: FlatCAM
"""
Gtk.Window.__init__(self, title="FlatCAM - 0.5")
self.set_default_size(200, 200)
vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
### Menu
# action_group = Gtk.ActionGroup("my_actions")
# self.add_file_menu_actions(action_group)
# #self.add_edit_menu_actions(action_group)
# #self.add_choices_menu_actions(action_group)
#
# uimanager = self.create_ui_manager()
# uimanager.insert_action_group(action_group)
#
# menubar = uimanager.get_widget("/MenuBar")
# vbox1.pack_start(menubar, False, False, 0)
#
# toolbar = uimanager.get_widget("/ToolBar")
# vbox1.pack_start(toolbar, False, False, 0)
menu = Gtk.MenuBar()
## File
menufile = Gtk.MenuItem.new_with_label('File')
menufile_menu = Gtk.Menu()
menufile.set_submenu(menufile_menu)
# New
self.menufilenew = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_NEW, None)
menufile_menu.append(self.menufilenew)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Open recent
self.menufilerecent = Gtk.ImageMenuItem("Open Recent", image=Gtk.Image(stock=Gtk.STOCK_OPEN))
menufile_menu.append(self.menufilerecent)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Open Gerber ...
self.menufileopengerber = Gtk.ImageMenuItem("Open Gerber ...", image=Gtk.Image(stock=Gtk.STOCK_OPEN))
menufile_menu.append(self.menufileopengerber)
# Open Excellon ...
self.menufileopenexcellon = Gtk.ImageMenuItem("Open Excellon ...", image=Gtk.Image(stock=Gtk.STOCK_OPEN))
menufile_menu.append(self.menufileopenexcellon)
# Open G-Code ...
self.menufileopengcode = Gtk.ImageMenuItem("Open G-Code ...", image=Gtk.Image(stock=Gtk.STOCK_OPEN))
menufile_menu.append(self.menufileopengcode)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Open Project ...
self.menufileopenproject = Gtk.ImageMenuItem("Open Project ...", image=Gtk.Image(stock=Gtk.STOCK_OPEN))
menufile_menu.append(self.menufileopenproject)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Save Project
self.menufilesaveproject = Gtk.ImageMenuItem("Save Project", image=Gtk.Image(stock=Gtk.STOCK_SAVE))
menufile_menu.append(self.menufilesaveproject)
# Save Project As ...
self.menufilesaveprojectas = Gtk.ImageMenuItem("Save Project As ...", image=Gtk.Image(stock=Gtk.STOCK_SAVE_AS))
menufile_menu.append(self.menufilesaveprojectas)
# Save Project Copy ...
self.menufilesaveprojectcopy = Gtk.ImageMenuItem("Save Project Copy ...", image=Gtk.Image(stock=Gtk.STOCK_SAVE_AS))
menufile_menu.append(self.menufilesaveprojectcopy)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Save Defaults
self.menufilesavedefaults = Gtk.ImageMenuItem("Save Defaults", image=Gtk.Image(stock=Gtk.STOCK_SAVE))
menufile_menu.append(self.menufilesavedefaults)
menufile_menu.append(Gtk.SeparatorMenuItem())
# Quit
self.menufilequit = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None)
menufile_menu.append(self.menufilequit)
menu.append(menufile)
## Edit
menuedit = Gtk.MenuItem.new_with_label('Edit')
menu.append(menuedit)
menuedit_menu = Gtk.Menu()
menuedit.set_submenu(menuedit_menu)
# Delete
self.menueditdelete = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_DELETE, None)
menuedit_menu.append(self.menueditdelete)
## View
menuview = Gtk.MenuItem.new_with_label('View')
menu.append(menuview)
menuview_menu = Gtk.Menu()
menuview.set_submenu(menuview_menu)
# Disable all plots
self.menuviewdisableall = Gtk.ImageMenuItem("Disable all plots", image=Gtk.Image.new_from_file('share/clear_plot16.png'))
menuview_menu.append(self.menuviewdisableall)
self.menuviewdisableallbutthis = Gtk.ImageMenuItem("Disable all plots but this one", image=Gtk.Image.new_from_file('share/clear_plot16.png'))
menuview_menu.append(self.menuviewdisableallbutthis)
self.menuviewenableall = Gtk.ImageMenuItem("Enable all plots", image=Gtk.Image.new_from_file('share/replot16.png'))
menuview_menu.append(self.menuviewenableall)
## Options
menuoptions = Gtk.MenuItem.new_with_label('Options')
menu.append(menuoptions)
menuoptions_menu = Gtk.Menu()
menuoptions.set_submenu(menuoptions_menu)
# Transfer Options
menutransferoptions = Gtk.ImageMenuItem("Transfer Options", image=Gtk.Image.new_from_file('share/copy16.png'))
menuoptions_menu.append(menutransferoptions)
menutransferoptions_menu = Gtk.Menu()
menutransferoptions.set_submenu(menutransferoptions_menu)
self.menutransferoptions_p2a = Gtk.ImageMenuItem("Project to App", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_p2a)
self.menutransferoptions_a2p = Gtk.ImageMenuItem("App to Project", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_a2p)
self.menutransferoptions_o2p = Gtk.ImageMenuItem("Object to Project", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_o2p)
self.menutransferoptions_o2a = Gtk.ImageMenuItem("Object to App", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_o2a)
self.menutransferoptions_p2o = Gtk.ImageMenuItem("Project to Object", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_p2o)
self.menutransferoptions_a2o = Gtk.ImageMenuItem("App to Object", image=Gtk.Image.new_from_file('share/copy16.png'))
menutransferoptions_menu.append(self.menutransferoptions_a2o)
## Tools
menutools = Gtk.MenuItem.new_with_label('Tools')
menu.append(menutools)
menutools_menu = Gtk.Menu()
menutools.set_submenu(menutools_menu)
# Double Sided PCB tool
self.menutools_dblsided = Gtk.ImageMenuItem("Double-Sided PCB Tool", image=Gtk.Image(stock=Gtk.STOCK_PREFERENCES))
menutools_menu.append(self.menutools_dblsided)
## Help
menuhelp = Gtk.MenuItem.new_with_label('Help')
menu.append(menuhelp)
menuhelp_menu = Gtk.Menu()
menuhelp.set_submenu(menuhelp_menu)
# About
self.menuhelpabout = Gtk.ImageMenuItem("About", image=Gtk.Image(stock=Gtk.STOCK_ABOUT))
menuhelp_menu.append(self.menuhelpabout)
# Updates
self.menuhelpupdates = Gtk.ImageMenuItem("Check for updates", image=Gtk.Image(stock=Gtk.STOCK_DIALOG_INFO))
menuhelp_menu.append(self.menuhelpupdates)
vbox1.pack_start(menu, False, False, 0)
### End of menu
###############
### Toolbar ###
###############
self.toolbar = Gtk.Toolbar(toolbar_style=Gtk.ToolbarStyle.ICONS)
vbox1.pack_start(self.toolbar, False, False, 0)
# Zoom fit
zf_ico = Gtk.Image.new_from_file('share/zoom_fit32.png')
self.zoom_fit_btn = Gtk.ToolButton.new(zf_ico, "")
#zoom_fit.connect("clicked", self.on_zoom_fit)
self.zoom_fit_btn.set_tooltip_markup("Zoom Fit.\n(Click on plot and hit <b>1</b>)")
self.toolbar.insert(self.zoom_fit_btn, -1)
# Zoom out
zo_ico = Gtk.Image.new_from_file('share/zoom_out32.png')
self.zoom_out_btn = Gtk.ToolButton.new(zo_ico, "")
#zoom_out.connect("clicked", self.on_zoom_out)
self.zoom_out_btn.set_tooltip_markup("Zoom Out.\n(Click on plot and hit <b>2</b>)")
self.toolbar.insert(self.zoom_out_btn, -1)
# Zoom in
zi_ico = Gtk.Image.new_from_file('share/zoom_in32.png')
self.zoom_in_btn = Gtk.ToolButton.new(zi_ico, "")
#zoom_in.connect("clicked", self.on_zoom_in)
self.zoom_in_btn.set_tooltip_markup("Zoom In.\n(Click on plot and hit <b>3</b>)")
self.toolbar.insert(self.zoom_in_btn, -1)
# Clear plot
cp_ico = Gtk.Image.new_from_file('share/clear_plot32.png')
self.clear_plot_btn = Gtk.ToolButton.new(cp_ico, "")
#clear_plot.connect("clicked", self.on_clear_plots)
self.clear_plot_btn.set_tooltip_markup("Clear Plot")
self.toolbar.insert(self.clear_plot_btn, -1)
# Replot
rp_ico = Gtk.Image.new_from_file('share/replot32.png')
self.replot_btn = Gtk.ToolButton.new(rp_ico, "")
#replot.connect("clicked", self.on_toolbar_replot)
self.replot_btn.set_tooltip_markup("Re-plot all")
self.toolbar.insert(self.replot_btn, -1)
# Delete item
del_ico = Gtk.Image.new_from_file('share/delete32.png')
self.delete_btn = Gtk.ToolButton.new(del_ico, "")
#delete.connect("clicked", self.on_delete)
self.delete_btn.set_tooltip_markup("Delete selected\nobject.")
self.toolbar.insert(self.delete_btn, -1)
#############
### Paned ###
#############
hpane = Gtk.Paned.new(Gtk.Orientation.HORIZONTAL)
vbox1.pack_start(hpane, expand=True, fill=True, padding=0)
################
### Notebook ###
################
self.notebook = FCNoteBook()
hpane.pack1(self.notebook)
#################
### Plot area ###
#################
# self.plotarea = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.plotarea = Gtk.Grid()
self.plotarea_super = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.plotarea_super.pack_start(self.plotarea, expand=True, fill=True, padding=0)
hpane.pack2(self.plotarea_super)
################
### Info bar ###
################
infobox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
vbox1.pack_start(infobox, expand=False, fill=True, padding=0)
## Frame
frame = Gtk.Frame(margin=2, hexpand=True, halign=0)
infobox.pack_start(frame, expand=True, fill=True, padding=0)
self.info_label = Gtk.Label("Not started.", margin=2, hexpand=True)
frame.add(self.info_label)
## Coordinate Label
self.position_label = Gtk.Label("X: 0.0 Y: 0.0", margin_left=4, margin_right=4)
infobox.pack_start(self.position_label, expand=False, fill=False, padding=0)
## Units label
self.units_label = Gtk.Label("[in]", margin_left=4, margin_right=4)
infobox.pack_start(self.units_label, expand=False, fill=False, padding=0)
## Progress bar
self.progress_bar = Gtk.ProgressBar(margin=2)
infobox.pack_start(self.progress_bar, expand=False, fill=False, padding=0)
self.add(vbox1)
self.show_all()
# def create_ui_manager(self):
# uimanager = Gtk.UIManager()
#
# # Throws exception if something went wrong
# uimanager.add_ui_from_string(FlatCAM.MENU)
#
# # Add the accelerator group to the toplevel window
# accelgroup = uimanager.get_accel_group()
# self.add_accel_group(accelgroup)
# return uimanager
#
# def add_file_menu_actions(self, action_group):
# action_filemenu = Gtk.Action("FileMenu", "File", None, None)
# action_group.add_action(action_filemenu)
#
# action_filenewmenu = Gtk.Action("FileNew", None, None, Gtk.STOCK_NEW)
# action_group.add_action(action_filenewmenu)
#
# action_new = Gtk.Action("FileNewStandard", "_New",
# "Create a new file", Gtk.STOCK_NEW)
# action_new.connect("activate", self.on_menu_file_new_generic)
# action_group.add_action_with_accel(action_new, None)
#
# action_group.add_actions([
# ("FileNewFoo", None, "New Foo", None, "Create new foo",
# self.on_menu_file_new_generic),
# ("FileNewGoo", None, "_New Goo", None, "Create new goo",
# self.on_menu_file_new_generic),
# ])
#
# action_filequit = Gtk.Action("FileQuit", None, None, Gtk.STOCK_QUIT)
# action_filequit.connect("activate", self.on_menu_file_quit)
# action_group.add_action(action_filequit)
#
# def on_menu_file_new_generic(self, widget):
# print("A File|New menu item was selected.")
#
# def on_menu_file_quit(self, widget):
# Gtk.main_quit()
if __name__ == "__main__":
flatcam = FlatCAMGUI()
Gtk.main()

1007
FlatCAM_GTK/FlatCAMObj.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
import threading
import Queue
class Worker(threading.Thread):
"""
Implements a queue of tasks to be carried out in order
in a single independent thread.
"""
def __init__(self):
super(Worker, self).__init__()
self.queue = Queue.Queue()
self.stoprequest = threading.Event()
def run(self):
while not self.stoprequest.isSet():
try:
task = self.queue.get(True, 0.05)
self.do_task(task)
except Queue.Empty:
continue
@staticmethod
def do_task(task):
task['fcn'](*task['params'])
return
def add_task(self, target, params=list()):
self.queue.put({'fcn': target, 'params': params})
return
def join(self, timeout=None):
self.stoprequest.set()
super(Worker, self).join()

249
FlatCAM_GTK/GUIElements.py Normal file
View File

@@ -0,0 +1,249 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
import re
from copy import copy
from gi.repository import Gtk
from FlatCAM_GTK import FlatCAMApp
class RadioSet(Gtk.Box):
def __init__(self, choices):
"""
The choices are specified as a list of dictionaries containing:
* 'label': Shown in the UI
* 'value': The value returned is selected
:param choices: List of choices. See description.
:type choices: list
"""
Gtk.Box.__init__(self)
self.choices = copy(choices)
self.group = None
for choice in self.choices:
if self.group is None:
choice['radio'] = Gtk.RadioButton.new_with_label(None, choice['label'])
self.group = choice['radio']
else:
choice['radio'] = Gtk.RadioButton.new_with_label_from_widget(self.group, choice['label'])
self.pack_start(choice['radio'], expand=True, fill=False, padding=2)
choice['radio'].connect('toggled', self.on_toggle)
self.group_toggle_fn = lambda x, y: None
def on_toggle(self, btn):
if btn.get_active():
self.group_toggle_fn(btn, self.get_value)
return
def get_value(self):
for choice in self.choices:
if choice['radio'].get_active():
return choice['value']
FlatCAMApp.App.log.error("No button was toggled in RadioSet.")
return None
def set_value(self, val):
for choice in self.choices:
if choice['value'] == val:
choice['radio'].set_active(True)
return
FlatCAMApp.App.log.error("Value given is not part of this RadioSet: %s" % str(val))
class LengthEntry(Gtk.Entry):
"""
A text entry that interprets its string as a
length, with or without specified units. When the user reads
the value, it is interpreted and replaced by a floating
point representation of the value in the default units. When
the entry is activated, its string is repalced by the interpreted
value.
Example:
Default units are 'IN', input is "1.0 mm", value returned
is 1.0/25.4 = 0.03937.
"""
def __init__(self, output_units='IN'):
"""
:param output_units: The default output units, 'IN' or 'MM'
:return: LengthEntry
"""
Gtk.Entry.__init__(self)
self.output_units = output_units
self.format_re = re.compile(r"^([^\s]+)(?:\s([a-zA-Z]+))?$")
# Unit conversion table OUTPUT-INPUT
self.scales = {
'IN': {'IN': 1.0,
'MM': 1/25.4},
'MM': {'IN': 25.4,
'MM': 1.0}
}
self.connect('activate', self.on_activate)
def on_activate(self, *args):
"""
Entry "activate" callback. Replaces the text in the
entry with the value returned by `get_value()`.
:param args: Ignored.
:return: None.
"""
val = self.get_value()
if val is not None:
self.set_text(str(val))
else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self):
"""
Fetches, interprets and returns the value in the entry. The text
is parsed to find the numerical expression and the (input) units (if any).
The numerical expression is interpreted and scaled acording to the
input and output units `self.output_units`.
:return: Floating point representation of the value in the entry.
:rtype: float
"""
raw = self.get_text().strip(' ')
match = self.format_re.search(raw)
if not match:
return None
try:
if match.group(2) is not None and match.group(2).upper() in self.scales:
return float(eval(match.group(1)))*self.scales[self.output_units][match.group(2).upper()]
else:
return float(eval(match.group(1)))
except:
FlatCAMApp.App.log.warning("Could not parse value in entry: %s" % str(raw))
return None
def set_value(self, val):
self.set_text(str(val))
class FloatEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
self.connect('activate', self.on_activate)
def on_activate(self, *args):
val = self.get_value()
if val is not None:
self.set_text(str(val))
else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self):
raw = self.get_text().strip(' ')
try:
evaled = eval(raw)
except:
FlatCAMApp.App.log.error("Could not evaluate: %s" % str(raw))
return None
return float(evaled)
def set_value(self, val):
self.set_text(str(val))
class IntEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
def get_value(self):
return int(self.get_text())
def set_value(self, val):
self.set_text(str(val))
class FCEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
def get_value(self):
return self.get_text()
def set_value(self, val):
self.set_text(str(val))
class EvalEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
def on_activate(self, *args):
val = self.get_value()
if val is not None:
self.set_text(str(val))
else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self):
raw = self.get_text().strip(' ')
try:
return eval(raw)
except:
FlatCAMApp.App.log.error("Could not evaluate: %s" % str(raw))
return None
def set_value(self, val):
self.set_text(str(val))
class FCCheckBox(Gtk.CheckButton):
def __init__(self, label=''):
Gtk.CheckButton.__init__(self, label=label)
def get_value(self):
return self.get_active()
def set_value(self, val):
self.set_active(val)
class FCTextArea(Gtk.ScrolledWindow):
def __init__(self):
# Gtk.ScrolledWindow.__init__(self)
# FlatCAMApp.App.log.debug('Gtk.ScrolledWindow.__init__(self)')
super(FCTextArea, self).__init__()
FlatCAMApp.App.log.debug('super(FCTextArea, self).__init__()')
self.set_size_request(250, 100)
FlatCAMApp.App.log.debug('self.set_size_request(250, 100)')
textview = Gtk.TextView()
#print textview
#FlatCAMApp.App.log.debug('self.textview = Gtk.TextView()')
#self.textbuffer = self.textview.get_buffer()
#FlatCAMApp.App.log.debug('self.textbuffer = self.textview.get_buffer()')
#self.textbuffer.set_text("(Nothing here!)")
#FlatCAMApp.App.log.debug('self.textbuffer.set_text("(Nothing here!)")')
#self.add(self.textview)
#FlatCAMApp.App.log.debug('self.add(self.textview)')
#self.show()
def set_value(self, val):
#self.textbuffer.set_text(str(val))
return
def get_value(self):
#return self.textbuffer.get_text()
return ""

View File

@@ -0,0 +1,261 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 4/20/2014 #
# MIT Licence #
############################################################
import inspect # TODO: Remove
from gi.repository import Gtk, GdkPixbuf, GLib
from FlatCAMObj import *
from FlatCAM_GTK import FlatCAMApp
class ObjectCollection:
classdict = {
"gerber": FlatCAMGerber,
"excellon": FlatCAMExcellon,
"cncjob": FlatCAMCNCjob,
"geometry": FlatCAMGeometry
}
icon_files = {
"gerber": "share/flatcam_icon16.png",
"excellon": "share/drill16.png",
"cncjob": "share/cnc16.png",
"geometry": "share/geometry16.png"
}
def __init__(self):
### Icons for the list view
self.icons = {}
for kind in ObjectCollection.icon_files:
self.icons[kind] = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icon_files[kind])
### GUI List components
## Model
self.store = Gtk.ListStore(FlatCAMObj)
## View
self.view = Gtk.TreeView(model=self.store)
#self.view.connect("row_activated", self.on_row_activated)
self.tree_selection = self.view.get_selection()
self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change)
## Renderers
# Icon
renderer_pixbuf = Gtk.CellRendererPixbuf()
column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf)
def _set_cell_icon(column, cell, model, it, data):
obj = model.get_value(it, 0)
cell.set_property('pixbuf', self.icons[obj.kind])
column_pixbuf.set_cell_data_func(renderer_pixbuf, _set_cell_icon)
self.view.append_column(column_pixbuf)
# Name
renderer_text = Gtk.CellRendererText()
column_text = Gtk.TreeViewColumn("Name", renderer_text)
def _set_cell_text(column, cell, model, it, data):
obj = model.get_value(it, 0)
cell.set_property('text', obj.options["name"])
column_text.set_cell_data_func(renderer_text, _set_cell_text)
self.view.append_column(column_text)
def print_list(self):
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
print obj
iterat = self.store.iter_next(iterat)
def delete_all(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
self.store.clear()
def delete_active(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_active()")
try:
model, treeiter = self.tree_selection.get_selected()
self.store.remove(treeiter)
except:
pass
def on_list_selection_change(self, selection):
"""
Callback for change in selection on the objects' list.
Instructs the new selection to build the UI for its options.
:param selection: Ignored.
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.on_list_selection_change()")
active = self.get_active()
active.build_ui()
def set_active(self, name):
"""
Sets an object as the active object in the program. Same
as `set_list_selection()`.
:param name: Name of the object.
:type name: str
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_active()")
self.set_list_selection(name)
def get_active(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_active()")
try:
model, treeiter = self.tree_selection.get_selected()
return model[treeiter][0]
except (TypeError, ValueError):
return None
def set_list_selection(self, name):
"""
Sets which object should be selected in the list.
:param name: Name of the object.
:rtype name: str
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_list_selection()")
iterat = self.store.get_iter_first()
while iterat is not None and self.store[iterat][0].options["name"] != name:
iterat = self.store.iter_next(iterat)
self.tree_selection.select_iter(iterat)
def append(self, obj, active=False):
"""
Add a FlatCAMObj the the collection. This method is thread-safe.
:param obj: FlatCAMObj to append
:type obj: FlatCAMObj
:param active: If it is to become the active object after appending
:type active: bool
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.append()")
def guitask():
self.store.append([obj])
if active:
self.set_list_selection(obj.options["name"])
GLib.idle_add(guitask)
def get_names(self):
"""
Gets a list of the names of all objects in the collection.
:return: List of names.
:rtype: list
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_names()")
names = []
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
names.append(obj.options["name"])
iterat = self.store.iter_next(iterat)
return names
def get_bounds(self):
"""
Finds coordinates bounding all objects in the collection.
:return: [xmin, ymin, xmax, ymax]
:rtype: list
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_bounds()")
# TODO: Move the operation out of here.
xmin = Inf
ymin = Inf
xmax = -Inf
ymax = -Inf
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
try:
gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin])
ymin = min([ymin, gymin])
xmax = max([xmax, gxmax])
ymax = max([ymax, gymax])
except:
FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry.")
iterat = self.store.iter_next(iterat)
return [xmin, ymin, xmax, ymax]
def get_list(self):
"""
Returns a list with all FlatCAMObj.
:return: List with all FlatCAMObj.
:rtype: list
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_list()")
collection_list = []
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
collection_list.append(obj)
iterat = self.store.iter_next(iterat)
return collection_list
def get_by_name(self, name):
"""
Fetches the FlatCAMObj with the given `name`.
:param name: The name of the object.
:type name: str
:return: The requested object or None if no such object.
:rtype: FlatCAMObj or None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
if obj.options["name"] == name:
return obj
iterat = self.store.iter_next(iterat)
return None
# def change_name(self, old_name, new_name):
# """
# Changes the name of `FlatCAMObj` named `old_name` to `new_name`.
#
# :param old_name: Name of the object to change.
# :type old_name: str
# :param new_name: New name.
# :type new_name: str
# :return: True if name change succeeded, False otherwise. Will fail
# if no object with `old_name` is found.
# :rtype: bool
# """
# print inspect.stack()[1][3], "--> OC.change_name()"
# iterat = self.store.get_iter_first()
# while iterat is not None:
# obj = self.store[iterat][0]
# if obj.options["name"] == old_name:
# obj.options["name"] = new_name
# self.store.row_changed(0, iterat)
# return True
# iterat = self.store.iter_next(iterat)
# return False

627
FlatCAM_GTK/ObjectUI.py Normal file
View File

@@ -0,0 +1,627 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk
from FlatCAM_GTK.GUIElements import *
class ObjectUI(Gtk.VBox):
"""
Base class for the UI of FlatCAM objects. Deriving classes should
put UI elements in ObjectUI.custom_box (Gtk.VBox).
"""
def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object'):
Gtk.VBox.__init__(self, spacing=3, margin=5, vexpand=False)
## Page Title box (spacing between children)
self.title_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)
self.pack_start(self.title_box, expand=False, fill=False, padding=2)
## Page Title icon
self.icon = Gtk.Image.new_from_file(icon_file)
self.title_box.pack_start(self.icon, expand=False, fill=False, padding=2)
## Title label
self.title_label = Gtk.Label()
self.title_label.set_markup("<b>" + title + "</b>")
self.title_label.set_justify(Gtk.Justification.CENTER)
self.title_box.pack_start(self.title_label, expand=False, fill=False, padding=2)
## Object name
self.name_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)
self.pack_start(self.name_box, expand=False, fill=False, padding=2)
name_label = Gtk.Label('Name:')
name_label.set_justify(Gtk.Justification.RIGHT)
self.name_box.pack_start(name_label,
expand=False, fill=False, padding=2)
self.name_entry = FCEntry()
self.name_box.pack_start(self.name_entry, expand=True, fill=False, padding=2)
## Box box for custom widgets
self.custom_box = Gtk.VBox(spacing=3, margin=0, vexpand=False)
self.pack_start(self.custom_box, expand=False, fill=False, padding=0)
## Common to all objects
## Scale
self.scale_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.scale_label.set_markup('<b>Scale:</b>')
self.scale_label.set_tooltip_markup(
"Change the size of the object."
)
self.pack_start(self.scale_label, expand=False, fill=False, padding=2)
grid5 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.pack_start(grid5, expand=False, fill=False, padding=2)
# Factor
l10 = Gtk.Label('Factor:', xalign=1)
l10.set_tooltip_markup(
"Factor by which to multiply\n"
"geometric features of this object."
)
grid5.attach(l10, 0, 0, 1, 1)
self.scale_entry = FloatEntry()
self.scale_entry.set_text("1.0")
grid5.attach(self.scale_entry, 1, 0, 1, 1)
# GO Button
self.scale_button = Gtk.Button(label='Scale')
self.scale_button.set_tooltip_markup(
"Perform scaling operation."
)
self.pack_start(self.scale_button, expand=False, fill=False, padding=2)
## Offset
self.offset_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.offset_label.set_markup('<b>Offset:</b>')
self.offset_label.set_tooltip_markup(
"Change the position of this object."
)
self.pack_start(self.offset_label, expand=False, fill=False, padding=2)
grid6 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.pack_start(grid6, expand=False, fill=False, padding=2)
# Vector
l11 = Gtk.Label('Offset Vector:', xalign=1)
l11.set_tooltip_markup(
"Amount by which to move the object\n"
"in the x and y axes in (x, y) format."
)
grid6.attach(l11, 0, 0, 1, 1)
self.offsetvector_entry = EvalEntry()
self.offsetvector_entry.set_text("(0.0, 0.0)")
grid6.attach(self.offsetvector_entry, 1, 0, 1, 1)
self.offset_button = Gtk.Button(label='Scale')
self.offset_button.set_tooltip_markup(
"Perform the offset operation."
)
self.pack_start(self.offset_button, expand=False, fill=False, padding=2)
def set_field(self, name, value):
getattr(self, name).set_value(value)
def get_field(self, name):
return getattr(self, name).get_value()
class CNCObjectUI(ObjectUI):
"""
User interface for CNCJob objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=False, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 2, 1)
# Tool dia for plot
l1 = Gtk.Label('Tool dia:', xalign=1)
l1.set_tooltip_markup(
"Diameter of the tool to be\n"
"rendered in the plot."
)
grid0.attach(l1, 0, 1, 1, 1)
self.tooldia_entry = LengthEntry()
grid0.attach(self.tooldia_entry, 1, 1, 1, 1)
# Update plot button
self.updateplot_button = Gtk.Button(label='Update Plot')
self.updateplot_button.set_tooltip_markup(
"Update the plot."
)
self.custom_box.pack_start(self.updateplot_button, expand=False, fill=False, padding=2)
## Export G-Code
self.export_gcode_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.export_gcode_label.set_markup("<b>Export G-Code:</b>")
self.export_gcode_label.set_tooltip_markup(
"Export and save G-Code to\n"
"make this object to a file."
)
self.custom_box.pack_start(self.export_gcode_label, expand=False, fill=False, padding=2)
# Append text to Gerber
l2 = Gtk.Label('Append to G-Code:')
l2.set_tooltip_markup(
"Type here any G-Code commands you would\n"
"like to append to the generated file.\n"
"I.e.: M2 (End of program)"
)
self.custom_box.pack_start(l2, expand=False, fill=False, padding=2)
#self.append_gtext = FCTextArea()
#self.custom_box.pack_start(self.append_gtext, expand=False, fill=False, padding=2)
# GO Button
self.export_gcode_button = Gtk.Button(label='Export G-Code')
self.export_gcode_button.set_tooltip_markup(
"Opens dialog to save G-Code\n"
"file."
)
self.custom_box.pack_start(self.export_gcode_button, expand=False, fill=False, padding=2)
class GeometryObjectUI(ObjectUI):
"""
User interface for Geometry objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Geometry Object', icon_file='share/geometry32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 1, 1)
## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.cncjob_label.set_markup('<b>Create CNC Job:</b>')
self.cncjob_label.set_tooltip_markup(
"Create a CNC Job object\n"
"tracing the contours of this\n"
"Geometry object."
)
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
# Cut Z
l1 = Gtk.Label('Cut Z:', xalign=1)
l1.set_tooltip_markup(
"Cutting depth (negative)\n"
"below the copper surface."
)
grid1.attach(l1, 0, 0, 1, 1)
self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1)
# Travel Z
l2 = Gtk.Label('Travel Z:', xalign=1)
l2.set_tooltip_markup(
"Height of the tool when\n"
"moving without cutting."
)
grid1.attach(l2, 0, 1, 1, 1)
self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1)
l3.set_tooltip_markup(
"Cutting speed in the XY\n"
"plane in units per minute"
)
grid1.attach(l3, 0, 2, 1, 1)
self.cncfeedrate_entry = LengthEntry()
grid1.attach(self.cncfeedrate_entry, 1, 2, 1, 1)
l4 = Gtk.Label('Tool dia:', xalign=1)
l4.set_tooltip_markup(
"The diameter of the cutting\n"
"tool (just for display)."
)
grid1.attach(l4, 0, 3, 1, 1)
self.cnctooldia_entry = LengthEntry()
grid1.attach(self.cnctooldia_entry, 1, 3, 1, 1)
self.generate_cnc_button = Gtk.Button(label='Generate')
self.generate_cnc_button.set_tooltip_markup(
"Generate the CNC Job object."
)
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2)
## Paint Area
self.paint_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.paint_label.set_markup('<b>Paint Area:</b>')
self.paint_label.set_tooltip_markup(
"Creates tool paths to cover the\n"
"whole area of a polygon (remove\n"
"all copper). You will be asked\n"
"to click on the desired polygon."
)
self.custom_box.pack_start(self.paint_label, expand=True, fill=False, padding=2)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
# Tool dia
l5 = Gtk.Label('Tool dia:', xalign=1)
l5.set_tooltip_markup(
"Diameter of the tool to\n"
"be used in the operation."
)
grid2.attach(l5, 0, 0, 1, 1)
self.painttooldia_entry = LengthEntry()
grid2.attach(self.painttooldia_entry, 1, 0, 1, 1)
# Overlap
l6 = Gtk.Label('Overlap:', xalign=1)
l6.set_tooltip_markup(
"How much (fraction) of the tool\n"
"width to overlap each tool pass."
)
grid2.attach(l6, 0, 1, 1, 1)
self.paintoverlap_entry = LengthEntry()
grid2.attach(self.paintoverlap_entry, 1, 1, 1, 1)
# Margin
l7 = Gtk.Label('Margin:', xalign=1)
l7.set_tooltip_markup(
"Distance by which to avoid\n"
"the edges of the polygon to\n"
"be painted."
)
grid2.attach(l7, 0, 2, 1, 1)
self.paintmargin_entry = LengthEntry()
grid2.attach(self.paintmargin_entry, 1, 2, 1, 1)
# GO Button
self.generate_paint_button = Gtk.Button(label='Generate')
self.generate_paint_button.set_tooltip_markup(
"After clicking here, click inside\n"
"the polygon you wish to be painted.\n"
"A new Geometry object with the tool\n"
"paths will be created."
)
self.custom_box.pack_start(self.generate_paint_button, expand=True, fill=False, padding=2)
class ExcellonObjectUI(ObjectUI):
"""
User interface for Excellon objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 1, 1)
self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.set_tooltip_markup(
"Solid circles."
)
grid0.attach(self.solid_cb, 1, 0, 1, 1)
## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.cncjob_label.set_markup('<b>Create CNC Job</b>')
self.cncjob_label.set_tooltip_markup(
"Create a CNC Job object\n"
"for this drill object."
)
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
l1 = Gtk.Label('Cut Z:', xalign=1)
l1.set_tooltip_markup(
"Drill depth (negative)\n"
"below the copper surface."
)
grid1.attach(l1, 0, 0, 1, 1)
self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1)
l2 = Gtk.Label('Travel Z:', xalign=1)
l2.set_tooltip_markup(
"Tool height when travelling\n"
"across the XY plane."
)
grid1.attach(l2, 0, 1, 1, 1)
self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1)
l3.set_tooltip_markup(
"Tool speed while drilling\n"
"(in units per minute)."
)
grid1.attach(l3, 0, 2, 1, 1)
self.feedrate_entry = LengthEntry()
grid1.attach(self.feedrate_entry, 1, 2, 1, 1)
l4 = Gtk.Label('Tools:', xalign=1)
l4.set_tooltip_markup(
"Which tools to include\n"
"in the CNC Job."
)
grid1.attach(l4, 0, 3, 1, 1)
boxt = Gtk.Box()
grid1.attach(boxt, 1, 3, 1, 1)
self.tools_entry = FCEntry()
boxt.pack_start(self.tools_entry, expand=True, fill=False, padding=2)
self.choose_tools_button = Gtk.Button(label='Choose...')
self.choose_tools_button.set_tooltip_markup(
"Choose the tools\n"
"from a list."
)
boxt.pack_start(self.choose_tools_button, expand=True, fill=False, padding=2)
self.generate_cnc_button = Gtk.Button(label='Generate')
self.generate_cnc_button.set_tooltip_markup(
"Generate the CNC Job."
)
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2)
class GerberObjectUI(ObjectUI):
"""
User interface for Gerber objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Gerber Object')
## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.plot_options_label.set_markup("<b>Plot Options:</b>")
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 1, 1)
# Solid CB
self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.set_tooltip_markup(
"Solid color polygons."
)
grid0.attach(self.solid_cb, 1, 0, 1, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='Multicolored')
self.multicolored_cb.set_tooltip_markup(
"Draw polygons in different colors."
)
grid0.attach(self.multicolored_cb, 2, 0, 1, 1)
## Isolation Routing
self.isolation_routing_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.isolation_routing_label.set_markup("<b>Isolation Routing:</b>")
self.isolation_routing_label.set_tooltip_markup(
"Create a Geometry object with\n"
"toolpaths to cut outside polygons."
)
self.custom_box.pack_start(self.isolation_routing_label, expand=True, fill=False, padding=2)
grid = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid, expand=True, fill=False, padding=2)
l1 = Gtk.Label('Tool diam:', xalign=1)
l1.set_tooltip_markup(
"Diameter of the cutting tool."
)
grid.attach(l1, 0, 0, 1, 1)
self.iso_tool_dia_entry = LengthEntry()
grid.attach(self.iso_tool_dia_entry, 1, 0, 1, 1)
l2 = Gtk.Label('Width (# passes):', xalign=1)
l2.set_tooltip_markup(
"Width of the isolation gap in\n"
"number (integer) of tool widths."
)
grid.attach(l2, 0, 1, 1, 1)
self.iso_width_entry = IntEntry()
grid.attach(self.iso_width_entry, 1, 1, 1, 1)
l3 = Gtk.Label('Pass overlap:', xalign=1)
l3.set_tooltip_markup(
"How much (fraction of tool width)\n"
"to overlap each pass."
)
grid.attach(l3, 0, 2, 1, 1)
self.iso_overlap_entry = FloatEntry()
grid.attach(self.iso_overlap_entry, 1, 2, 1, 1)
self.generate_iso_button = Gtk.Button(label='Generate Geometry')
self.generate_iso_button.set_tooltip_markup(
"Create the Geometry Object\n"
"for isolation routing."
)
self.custom_box.pack_start(self.generate_iso_button, expand=True, fill=False, padding=2)
## Board cuttout
self.board_cutout_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.board_cutout_label.set_markup("<b>Board cutout:</b>")
self.board_cutout_label.set_tooltip_markup(
"Create toolpaths to cut around\n"
"the PCB and separate it from\n"
"the original board."
)
self.custom_box.pack_start(self.board_cutout_label, expand=True, fill=False, padding=2)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
l4 = Gtk.Label('Tool dia:', xalign=1)
l4.set_tooltip_markup(
"Diameter of the cutting tool."
)
grid2.attach(l4, 0, 0, 1, 1)
self.cutout_tooldia_entry = LengthEntry()
grid2.attach(self.cutout_tooldia_entry, 1, 0, 1, 1)
l5 = Gtk.Label('Margin:', xalign=1)
l5.set_tooltip_markup(
"Distance from objects at which\n"
"to draw the cutout."
)
grid2.attach(l5, 0, 1, 1, 1)
self.cutout_margin_entry = LengthEntry()
grid2.attach(self.cutout_margin_entry, 1, 1, 1, 1)
l6 = Gtk.Label('Gap size:', xalign=1)
l6.set_tooltip_markup(
"Size of the gaps in the toolpath\n"
"that will remain to hold the\n"
"board in place."
)
grid2.attach(l6, 0, 2, 1, 1)
self.cutout_gap_entry = LengthEntry()
grid2.attach(self.cutout_gap_entry, 1, 2, 1, 1)
l7 = Gtk.Label('Gaps:', xalign=1)
l7.set_tooltip_markup(
"Where to place the gaps, Top/Bottom\n"
"Left/Rigt, or on all 4 sides."
)
grid2.attach(l7, 0, 3, 1, 1)
self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'},
{'label': '2 (L/R)', 'value': 'lr'},
{'label': '4', 'value': '4'}])
grid2.attach(self.gaps_radio, 1, 3, 1, 1)
self.generate_cutout_button = Gtk.Button(label='Generate Geometry')
self.generate_cutout_button.set_tooltip_markup(
"Generate the geometry for\n"
"the board cutout."
)
self.custom_box.pack_start(self.generate_cutout_button, expand=True, fill=False, padding=2)
## Non-copper regions
self.noncopper_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.noncopper_label.set_markup("<b>Non-copper regions:</b>")
self.noncopper_label.set_tooltip_markup(
"Create polygons covering the\n"
"areas without copper on the PCB.\n"
"Equivalent to the inverse of this\n"
"object. Can be used to remove all\n"
"copper from a specified region."
)
self.custom_box.pack_start(self.noncopper_label, expand=True, fill=False, padding=2)
grid3 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid3, expand=True, fill=False, padding=2)
l8 = Gtk.Label('Boundary margin:', xalign=1)
l8.set_tooltip_markup(
"Specify the edge of the PCB\n"
"by drawing a box around all\n"
"objects with this minimum\n"
"distance."
)
grid3.attach(l8, 0, 0, 1, 1)
self.noncopper_margin_entry = LengthEntry()
grid3.attach(self.noncopper_margin_entry, 1, 0, 1, 1)
self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
self.noncopper_rounded_cb.set_tooltip_markup(
"If the boundary of the board\n"
"is to have rounded corners\n"
"their radius is equal to the margin."
)
grid3.attach(self.noncopper_rounded_cb, 0, 1, 2, 1)
self.generate_noncopper_button = Gtk.Button(label='Generate Geometry')
self.generate_noncopper_button.set_tooltip_markup(
"Creates a Geometry objects with polygons\n"
"covering the copper-free areas of the PCB."
)
self.custom_box.pack_start(self.generate_noncopper_button, expand=True, fill=False, padding=2)
## Bounding box
self.boundingbox_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5)
self.boundingbox_label.set_markup('<b>Bounding Box:</b>')
self.boundingbox_label.set_tooltip_markup(
"Create a Geometry object with a rectangle\n"
"enclosing all polygons at a given distance."
)
self.custom_box.pack_start(self.boundingbox_label, expand=True, fill=False, padding=2)
grid4 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid4, expand=True, fill=False, padding=2)
l9 = Gtk.Label('Boundary Margin:', xalign=1)
l9.set_tooltip_markup(
"Distance of the edges of the box\n"
"to the nearest polygon."
)
grid4.attach(l9, 0, 0, 1, 1)
self.bbmargin_entry = LengthEntry()
grid4.attach(self.bbmargin_entry, 1, 0, 1, 1)
self.bbrounded_cb = FCCheckBox(label="Rounded corners")
self.bbrounded_cb.set_tooltip_markup(
"If the bounding box is \n"
"to have rounded corners\n"
"their radius is equal to\n"
"the margin."
)
grid4.attach(self.bbrounded_cb, 0, 1, 2, 1)
self.generate_bb_button = Gtk.Button(label='Generate Geometry')
self.generate_bb_button.set_tooltip_markup(
"Genrate the Geometry object."
)
self.custom_box.pack_start(self.generate_bb_button, expand=True, fill=False, padding=2)

318
FlatCAM_GTK/PlotCanvas.py Normal file
View File

@@ -0,0 +1,318 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gdk
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
#from FlatCAMApp import *
from FlatCAM_GTK import FlatCAMApp
class PlotCanvas:
"""
Class handling the plotting area in the application.
"""
def __init__(self, container):
"""
The constructor configures the Matplotlib figure that
will contain all plots, creates the base axes and connects
events to the plotting area.
:param container: The parent container in which to draw plots.
:rtype: PlotCanvas
"""
# Options
self.x_margin = 15 # pixels
self.y_margin = 25 # Pixels
# Parent container
self.container = container
# Plots go onto a single matplotlib.figure
self.figure = Figure(dpi=50) # TODO: dpi needed?
self.figure.patch.set_visible(False)
# These axes show the ticks and grid. No plotting done here.
# New axes must have a label, otherwise mpl returns an existing one.
self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0)
self.axes.set_aspect(1)
self.axes.grid(True)
# The canvas is the top level container (Gtk.DrawingArea)
self.canvas = FigureCanvas(self.figure)
self.canvas.set_hexpand(1)
self.canvas.set_vexpand(1)
self.canvas.set_can_focus(True) # For key press
# Attach to parent
self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns??
# Events
self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
self.canvas.connect('configure-event', self.auto_adjust_axes)
self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
self.canvas.connect("scroll-event", self.on_scroll)
self.canvas.mpl_connect('key_press_event', self.on_key_down)
self.canvas.mpl_connect('key_release_event', self.on_key_up)
self.mouse = [0, 0]
self.key = None
def on_key_down(self, event):
"""
:param event:
:return:
"""
self.key = event.key
def on_key_up(self, event):
"""
:param event:
:return:
"""
self.key = None
def mpl_connect(self, event_name, callback):
"""
Attach an event handler to the canvas through the Matplotlib interface.
:param event_name: Name of the event
:type event_name: str
:param callback: Function to call
:type callback: func
:return: Connection id
:rtype: int
"""
return self.canvas.mpl_connect(event_name, callback)
def mpl_disconnect(self, cid):
"""
Disconnect callback with the give id.
:param cid: Callback id.
:return: None
"""
self.canvas.mpl_disconnect(cid)
def connect(self, event_name, callback):
"""
Attach an event handler to the canvas through the native GTK interface.
:param event_name: Name of the event
:type event_name: str
:param callback: Function to call
:type callback: function
:return: Nothing
"""
self.canvas.connect(event_name, callback)
def clear(self):
"""
Clears axes and figure.
:return: None
"""
# Clear
self.axes.cla()
try:
self.figure.clf()
except KeyError:
FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()")
# Re-build
self.figure.add_axes(self.axes)
self.axes.set_aspect(1)
self.axes.grid(True)
# Re-draw
self.canvas.queue_draw()
def adjust_axes(self, xmin, ymin, xmax, ymax):
"""
Adjusts all axes while maintaining the use of the whole canvas
and an aspect ratio to 1:1 between x and y axes. The parameters are an original
request that will be modified to fit these restrictions.
:param xmin: Requested minimum value for the X axis.
:type xmin: float
:param ymin: Requested minimum value for the Y axis.
:type ymin: float
:param xmax: Requested maximum value for the X axis.
:type xmax: float
:param ymax: Requested maximum value for the Y axis.
:type ymax: float
:return: None
"""
FlatCAMApp.App.log.debug("PC.adjust_axes()")
width = xmax - xmin
height = ymax - ymin
try:
r = width / height
except ZeroDivisionError:
FlatCAMApp.App.log.error("Height is %f" % height)
return
canvas_w, canvas_h = self.canvas.get_width_height()
canvas_r = float(canvas_w) / canvas_h
x_ratio = float(self.x_margin) / canvas_w
y_ratio = float(self.y_margin) / canvas_h
if r > canvas_r:
ycenter = (ymin + ymax) / 2.0
newheight = height * r / canvas_r
ymin = ycenter - newheight / 2.0
ymax = ycenter + newheight / 2.0
else:
xcenter = (xmax + xmin) / 2.0
newwidth = width * canvas_r / r
xmin = xcenter - newwidth / 2.0
xmax = xcenter + newwidth / 2.0
# Adjust axes
for ax in self.figure.get_axes():
if ax._label != 'base':
ax.set_frame_on(False) # No frame
ax.set_xticks([]) # No tick
ax.set_yticks([]) # No ticks
ax.patch.set_visible(False) # No background
ax.set_aspect(1)
ax.set_xlim((xmin, xmax))
ax.set_ylim((ymin, ymax))
ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
# Re-draw
self.canvas.queue_draw()
def auto_adjust_axes(self, *args):
"""
Calls ``adjust_axes()`` using the extents of the base axes.
:rtype : None
:return: None
"""
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
self.adjust_axes(xmin, ymin, xmax, ymax)
def zoom(self, factor, center=None):
"""
Zooms the plot by factor around a given
center point. Takes care of re-drawing.
:param factor: Number by which to scale the plot.
:type factor: float
:param center: Coordinates [x, y] of the point around which to scale the plot.
:type center: list
:return: None
"""
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
width = xmax - xmin
height = ymax - ymin
if center is None or center == [None, None]:
center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0]
# For keeping the point at the pointer location
relx = (xmax - center[0]) / width
rely = (ymax - center[1]) / height
new_width = width / factor
new_height = height / factor
xmin = center[0] - new_width * (1 - relx)
xmax = center[0] + new_width * relx
ymin = center[1] - new_height * (1 - rely)
ymax = center[1] + new_height * rely
# Adjust axes
for ax in self.figure.get_axes():
ax.set_xlim((xmin, xmax))
ax.set_ylim((ymin, ymax))
# Re-draw
self.canvas.queue_draw()
def pan(self, x, y):
xmin, xmax = self.axes.get_xlim()
ymin, ymax = self.axes.get_ylim()
width = xmax - xmin
height = ymax - ymin
# Adjust axes
for ax in self.figure.get_axes():
ax.set_xlim((xmin + x*width, xmax + x*width))
ax.set_ylim((ymin + y*height, ymax + y*height))
# Re-draw
self.canvas.queue_draw()
def new_axes(self, name):
"""
Creates and returns an Axes object attached to this object's Figure.
:param name: Unique label for the axes.
:return: Axes attached to the figure.
:rtype: Axes
"""
return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
def on_scroll(self, canvas, event):
"""
Scroll event handler.
:param canvas: The widget generating the event. Ignored.
:param event: Event object containing the event information.
:return: None
"""
# So it can receive key presses
self.canvas.grab_focus()
# Event info
z, direction = event.get_scroll_direction()
if self.key is None:
if direction is Gdk.ScrollDirection.UP:
self.zoom(1.5, self.mouse)
else:
self.zoom(1/1.5, self.mouse)
return
if self.key == 'shift':
if direction is Gdk.ScrollDirection.UP:
self.pan(0.3, 0)
else:
self.pan(-0.3, 0)
return
if self.key == 'ctrl+control':
if direction is Gdk.ScrollDirection.UP:
self.pan(0, 0.3)
else:
self.pan(0, -0.3)
return
def on_mouse_move(self, event):
"""
Mouse movement event hadler. Stores the coordinates.
:param event: Contains information about the event.
:return: None
"""
self.mouse = [event.xdata, event.ydata]

View File

@@ -1,19 +1,11 @@
############################################################ from PyQt4 import QtGui, QtCore
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk
import re
from copy import copy from copy import copy
import FlatCAMApp import FlatCAMApp
import re
class RadioSet(Gtk.Box): class RadioSet(QtGui.QWidget):
def __init__(self, choices): def __init__(self, choices, orientation='horizontal', parent=None):
""" """
The choices are specified as a list of dictionaries containing: The choices are specified as a list of dictionaries containing:
@@ -23,28 +15,37 @@ class RadioSet(Gtk.Box):
:param choices: List of choices. See description. :param choices: List of choices. See description.
:type choices: list :type choices: list
""" """
Gtk.Box.__init__(self) super(RadioSet, self).__init__(parent)
self.choices = copy(choices) self.choices = copy(choices)
self.group = None
if orientation == 'horizontal':
layout = QtGui.QHBoxLayout()
else:
layout = QtGui.QVBoxLayout()
group = QtGui.QButtonGroup(self)
for choice in self.choices: for choice in self.choices:
if self.group is None: choice['radio'] = QtGui.QRadioButton(choice['label'])
choice['radio'] = Gtk.RadioButton.new_with_label(None, choice['label']) group.addButton(choice['radio'])
self.group = choice['radio'] layout.addWidget(choice['radio'], stretch=0)
else: choice['radio'].toggled.connect(self.on_toggle)
choice['radio'] = Gtk.RadioButton.new_with_label_from_widget(self.group, choice['label'])
self.pack_start(choice['radio'], expand=True, fill=False, padding=2)
choice['radio'].connect('toggled', self.on_toggle)
self.group_toggle_fn = lambda x, y: None layout.addStretch()
self.setLayout(layout)
def on_toggle(self, btn): self.group_toggle_fn = lambda: None
if btn.get_active():
self.group_toggle_fn(btn, self.get_value) def on_toggle(self):
FlatCAMApp.App.log.debug("Radio toggled")
radio = self.sender()
if radio.isChecked():
self.group_toggle_fn()
return return
def get_value(self): def get_value(self):
for choice in self.choices: for choice in self.choices:
if choice['radio'].get_active(): if choice['radio'].isChecked():
return choice['value'] return choice['value']
FlatCAMApp.App.log.error("No button was toggled in RadioSet.") FlatCAMApp.App.log.error("No button was toggled in RadioSet.")
return None return None
@@ -52,33 +53,15 @@ class RadioSet(Gtk.Box):
def set_value(self, val): def set_value(self, val):
for choice in self.choices: for choice in self.choices:
if choice['value'] == val: if choice['value'] == val:
choice['radio'].set_active(True) choice['radio'].setChecked(True)
return return
FlatCAMApp.App.log.error("Value given is not part of this RadioSet: %s" % str(val)) FlatCAMApp.App.log.error("Value given is not part of this RadioSet: %s" % str(val))
class LengthEntry(Gtk.Entry): class LengthEntry(QtGui.QLineEdit):
""" def __init__(self, output_units='IN', parent=None):
A text entry that interprets its string as a super(LengthEntry, self).__init__(parent)
length, with or without specified units. When the user reads
the value, it is interpreted and replaced by a floating
point representation of the value in the default units. When
the entry is activated, its string is repalced by the interpreted
value.
Example:
Default units are 'IN', input is "1.0 mm", value returned
is 1.0/25.4 = 0.03937.
"""
def __init__(self, output_units='IN'):
"""
:param output_units: The default output units, 'IN' or 'MM'
:return: LengthEntry
"""
Gtk.Entry.__init__(self)
self.output_units = output_units self.output_units = output_units
self.format_re = re.compile(r"^([^\s]+)(?:\s([a-zA-Z]+))?$") self.format_re = re.compile(r"^([^\s]+)(?:\s([a-zA-Z]+))?$")
@@ -90,40 +73,22 @@ class LengthEntry(Gtk.Entry):
'MM': 1.0} 'MM': 1.0}
} }
self.connect('activate', self.on_activate) def returnPressed(self, *args, **kwargs):
def on_activate(self, *args):
"""
Entry "activate" callback. Replaces the text in the
entry with the value returned by `get_value()`.
:param args: Ignored.
:return: None.
"""
val = self.get_value() val = self.get_value()
if val is not None: if val is not None:
self.set_text(str(val)) self.set_text(QtCore.QString(str(val)))
else: else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text()) FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self): def get_value(self):
""" raw = str(self.text()).strip(' ')
Fetches, interprets and returns the value in the entry. The text
is parsed to find the numerical expression and the (input) units (if any).
The numerical expression is interpreted and scaled acording to the
input and output units `self.output_units`.
:return: Floating point representation of the value in the entry.
:rtype: float
"""
raw = self.get_text().strip(' ')
match = self.format_re.search(raw) match = self.format_re.search(raw)
if not match: if not match:
return None return None
try: try:
if match.group(2) is not None and match.group(2).upper() in self.scales: if match.group(2) is not None and match.group(2).upper() in self.scales:
return float(eval(match.group(1)))*self.scales[self.output_units][match.group(2).upper()] return float(eval(match.group(1)))*float(self.scales[self.output_units][match.group(2).upper()])
else: else:
return float(eval(match.group(1))) return float(eval(match.group(1)))
except: except:
@@ -131,24 +96,22 @@ class LengthEntry(Gtk.Entry):
return None return None
def set_value(self, val): def set_value(self, val):
self.set_text(str(val)) self.setText(QtCore.QString(str(val)))
class FloatEntry(Gtk.Entry): class FloatEntry(QtGui.QLineEdit):
def __init__(self): def __init__(self, parent=None):
Gtk.Entry.__init__(self) super(FloatEntry, self).__init__(parent)
self.connect('activate', self.on_activate) def returnPressed(self, *args, **kwargs):
def on_activate(self, *args):
val = self.get_value() val = self.get_value()
if val is not None: if val is not None:
self.set_text(str(val)) self.set_text(QtCore.QString(str(val)))
else: else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text()) FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.text())
def get_value(self): def get_value(self):
raw = self.get_text().strip(' ') raw = str(self.text()).strip(' ')
try: try:
evaled = eval(raw) evaled = eval(raw)
except: except:
@@ -158,44 +121,44 @@ class FloatEntry(Gtk.Entry):
return float(evaled) return float(evaled)
def set_value(self, val): def set_value(self, val):
self.set_text(str(val)) self.setText("%.6f"%val)
class IntEntry(Gtk.Entry): class IntEntry(QtGui.QLineEdit):
def __init__(self): def __init__(self, parent=None):
Gtk.Entry.__init__(self) super(IntEntry, self).__init__(parent)
def get_value(self): def get_value(self):
return int(self.get_text()) return int(self.text())
def set_value(self, val): def set_value(self, val):
self.set_text(str(val)) self.setText(QtCore.QString(str(val)))
class FCEntry(Gtk.Entry): class FCEntry(QtGui.QLineEdit):
def __init__(self): def __init__(self, parent=None):
Gtk.Entry.__init__(self) super(FCEntry, self).__init__(parent)
def get_value(self): def get_value(self):
return self.get_text() return str(self.text())
def set_value(self, val): def set_value(self, val):
self.set_text(str(val)) self.setText(QtCore.QString(str(val)))
class EvalEntry(Gtk.Entry): class EvalEntry(QtGui.QLineEdit):
def __init__(self): def __init__(self, parent=None):
Gtk.Entry.__init__(self) super(EvalEntry, self).__init__(parent)
def on_activate(self, *args): def returnPressed(self, *args, **kwargs):
val = self.get_value() val = self.get_value()
if val is not None: if val is not None:
self.set_text(str(val)) self.setText(QtCore.QString(str(val)))
else: else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text()) FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self): def get_value(self):
raw = self.get_text().strip(' ') raw = str(self.text()).strip(' ')
try: try:
return eval(raw) return eval(raw)
except: except:
@@ -203,15 +166,65 @@ class EvalEntry(Gtk.Entry):
return None return None
def set_value(self, val): def set_value(self, val):
self.set_text(str(val)) self.setText(QtCore.QString(str(val)))
class FCCheckBox(Gtk.CheckButton): class FCCheckBox(QtGui.QCheckBox):
def __init__(self, label=''): def __init__(self, label='', parent=None):
Gtk.CheckButton.__init__(self, label=label) super(FCCheckBox, self).__init__(QtCore.QString(label), parent)
def get_value(self): def get_value(self):
return self.get_active() return self.isChecked()
def set_value(self, val): def set_value(self, val):
self.set_active(val) self.setChecked(val)
class FCTextArea(QtGui.QPlainTextEdit):
def __init__(self, parent=None):
super(FCTextArea, self).__init__(parent)
def set_value(self, val):
self.setPlainText(val)
def get_value(self):
return str(self.toPlainText())
class VerticalScrollArea(QtGui.QScrollArea):
"""
This widget extends QtGui.QScrollArea to make a vertical-only
scroll area that also expands horizontally to accomodate
its contents.
"""
def __init__(self, parent=None):
QtGui.QScrollArea.__init__(self, parent=parent)
self.setWidgetResizable(True)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
def eventFilter(self, source, event):
"""
The event filter gets automatically installed when setWidget()
is called.
:param source:
:param event:
:return:
"""
if event.type() == QtCore.QEvent.Resize and source == self.widget():
# FlatCAMApp.App.log.debug("VerticalScrollArea: Widget resized:")
# FlatCAMApp.App.log.debug(" minimumSizeHint().width() = %d" % self.widget().minimumSizeHint().width())
# FlatCAMApp.App.log.debug(" verticalScrollBar().width() = %d" % self.verticalScrollBar().width())
self.setMinimumWidth(self.widget().sizeHint().width() +
self.verticalScrollBar().sizeHint().width())
# if self.verticalScrollBar().isVisible():
# FlatCAMApp.App.log.debug(" Scroll bar visible")
# self.setMinimumWidth(self.widget().minimumSizeHint().width() +
# self.verticalScrollBar().width())
# else:
# FlatCAMApp.App.log.debug(" Scroll bar hidden")
# self.setMinimumWidth(self.widget().minimumSizeHint().width())
return QtGui.QWidget.eventFilter(self, source, event)

View File

@@ -1,18 +1,11 @@
############################################################ from PyQt4.QtCore import QModelIndex
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 4/20/2014 #
# MIT Licence #
############################################################
from FlatCAMObj import * from FlatCAMObj import *
from gi.repository import Gtk, GdkPixbuf
import inspect # TODO: Remove import inspect # TODO: Remove
import FlatCAMApp import FlatCAMApp
from PyQt4 import Qt, QtGui, QtCore
class ObjectCollection: class ObjectCollection(QtCore.QAbstractListModel):
classdict = { classdict = {
"gerber": FlatCAMGerber, "gerber": FlatCAMGerber,
@@ -28,130 +21,51 @@ class ObjectCollection:
"geometry": "share/geometry16.png" "geometry": "share/geometry16.png"
} }
def __init__(self): def __init__(self, parent=None):
QtCore.QAbstractListModel.__init__(self, parent=parent)
### Icons for the list view ### Icons for the list view
self.icons = {} self.icons = {}
for kind in ObjectCollection.icon_files: for kind in ObjectCollection.icon_files:
self.icons[kind] = GdkPixbuf.Pixbuf.new_from_file(ObjectCollection.icon_files[kind]) self.icons[kind] = QtGui.QPixmap(ObjectCollection.icon_files[kind])
### GUI List components self.object_list = []
## Model
self.store = Gtk.ListStore(FlatCAMObj)
## View self.view = QtGui.QListView()
self.view = Gtk.TreeView(model=self.store) self.view.setModel(self)
#self.view.connect("row_activated", self.on_row_activated) self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
self.tree_selection = self.view.get_selection() self.view.activated.connect(self.on_item_activated)
self.change_subscription = self.tree_selection.connect("changed", self.on_list_selection_change)
## Renderers def rowCount(self, parent=QtCore.QModelIndex(), *args, **kwargs):
# Icon return len(self.object_list)
renderer_pixbuf = Gtk.CellRendererPixbuf()
column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf)
def _set_cell_icon(column, cell, model, it, data): def columnCount(self, *args, **kwargs):
obj = model.get_value(it, 0) return 1
cell.set_property('pixbuf', self.icons[obj.kind])
column_pixbuf.set_cell_data_func(renderer_pixbuf, _set_cell_icon) def data(self, index, role=Qt.Qt.DisplayRole):
self.view.append_column(column_pixbuf) if not index.isValid() or not 0 <= index.row() < self.rowCount():
return QtCore.QVariant()
# Name row = index.row()
renderer_text = Gtk.CellRendererText() if role == Qt.Qt.DisplayRole:
column_text = Gtk.TreeViewColumn("Name", renderer_text) return self.object_list[row].options["name"]
if role == Qt.Qt.DecorationRole:
def _set_cell_text(column, cell, model, it, data): return self.icons[self.object_list[row].kind]
obj = model.get_value(it, 0)
cell.set_property('text', obj.options["name"])
column_text.set_cell_data_func(renderer_text, _set_cell_text)
self.view.append_column(column_text)
def print_list(self): def print_list(self):
iterat = self.store.get_iter_first() for obj in self.object_list:
while iterat is not None:
obj = self.store[iterat][0]
print obj print obj
iterat = self.store.iter_next(iterat)
def delete_all(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
self.store.clear()
def delete_active(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_active()")
try:
model, treeiter = self.tree_selection.get_selected()
self.store.remove(treeiter)
except:
pass
def on_list_selection_change(self, selection):
"""
Callback for change in selection on the objects' list.
Instructs the new selection to build the UI for its options.
:param selection: Ignored.
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.on_list_selection_change()")
try:
self.get_active().build_ui()
except AttributeError: # For None being active
pass
def set_active(self, name):
"""
Sets an object as the active object in the program. Same
as `set_list_selection()`.
:param name: Name of the object.
:type name: str
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_active()")
self.set_list_selection(name)
def get_active(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_active()")
try:
model, treeiter = self.tree_selection.get_selected()
return model[treeiter][0]
except (TypeError, ValueError):
return None
def set_list_selection(self, name):
"""
Sets which object should be selected in the list.
:param name: Name of the object.
:rtype name: str
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.set_list_selection()")
iterat = self.store.get_iter_first()
while iterat is not None and self.store[iterat][0].options["name"] != name:
iterat = self.store.iter_next(iterat)
self.tree_selection.select_iter(iterat)
def append(self, obj, active=False): def append(self, obj, active=False):
""" FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
Add a FlatCAMObj the the collection. This method is thread-safe.
:param obj: FlatCAMObj to append obj.set_ui(obj.ui_type())
:type obj: FlatCAMObj
:param active: If it is to become the active object after appending
:type active: bool
:return: None
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.append()")
def guitask(): # Required before appending
self.store.append([obj]) self.beginInsertRows(QtCore.QModelIndex(), len(self.object_list), len(self.object_list))
if active:
self.set_list_selection(obj.options["name"]) self.object_list.append(obj)
GLib.idle_add(guitask)
# Required after appending
self.endInsertRows()
def get_names(self): def get_names(self):
""" """
@@ -160,14 +74,9 @@ class ObjectCollection:
:return: List of names. :return: List of names.
:rtype: list :rtype: list
""" """
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_names()")
names = [] FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
iterat = self.store.get_iter_first() return [x.options['name'] for x in self.object_list]
while iterat is not None:
obj = self.store[iterat][0]
names.append(obj.options["name"])
iterat = self.store.iter_next(iterat)
return names
def get_bounds(self): def get_bounds(self):
""" """
@@ -185,9 +94,7 @@ class ObjectCollection:
xmax = -Inf xmax = -Inf
ymax = -Inf ymax = -Inf
iterat = self.store.get_iter_first() for obj in self.object_list:
while iterat is not None:
obj = self.store[iterat][0]
try: try:
gxmin, gymin, gxmax, gymax = obj.bounds() gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin]) xmin = min([xmin, gxmin])
@@ -196,25 +103,9 @@ class ObjectCollection:
ymax = max([ymax, gymax]) ymax = max([ymax, gymax])
except: except:
FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry.") FlatCAMApp.App.log.warning("DEV WARNING: Tried to get bounds of empty geometry.")
iterat = self.store.iter_next(iterat)
return [xmin, ymin, xmax, ymax] return [xmin, ymin, xmax, ymax]
def get_list(self):
"""
Returns a list with all FlatCAMObj.
:return: List with all FlatCAMObj.
:rtype: list
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_list()")
collection_list = []
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
collection_list.append(obj)
iterat = self.store.iter_next(iterat)
return collection_list
def get_by_name(self, name): def get_by_name(self, name):
""" """
Fetches the FlatCAMObj with the given `name`. Fetches the FlatCAMObj with the given `name`.
@@ -226,33 +117,57 @@ class ObjectCollection:
""" """
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()") FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.get_by_name()")
iterat = self.store.get_iter_first() for obj in self.object_list:
while iterat is not None: if obj.options['name'] == name:
obj = self.store[iterat][0]
if obj.options["name"] == name:
return obj return obj
iterat = self.store.iter_next(iterat)
return None return None
# def change_name(self, old_name, new_name): def delete_active(self):
# """ selections = self.view.selectedIndexes()
# Changes the name of `FlatCAMObj` named `old_name` to `new_name`. if len(selections) == 0:
# return
# :param old_name: Name of the object to change. row = selections[0].row()
# :type old_name: str
# :param new_name: New name. self.beginRemoveRows(QtCore.QModelIndex(), row, row)
# :type new_name: str
# :return: True if name change succeeded, False otherwise. Will fail self.object_list.pop(row)
# if no object with `old_name` is found.
# :rtype: bool self.endRemoveRows()
# """
# print inspect.stack()[1][3], "--> OC.change_name()" def get_active(self):
# iterat = self.store.get_iter_first() selections = self.view.selectedIndexes()
# while iterat is not None: if len(selections) == 0:
# obj = self.store[iterat][0] return None
# if obj.options["name"] == old_name: row = selections[0].row()
# obj.options["name"] = new_name return self.object_list[row]
# self.store.row_changed(0, iterat)
# return True def set_active(self, name):
# iterat = self.store.iter_next(iterat) iobj = self.createIndex(self.get_names().index(name))
# return False self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel)
def on_list_selection_change(self, current, previous):
FlatCAMApp.App.log.debug("on_list_selection_change()")
FlatCAMApp.App.log.debug("Current: %s, Previous %s" % (str(current), str(previous)))
try:
selection_index = current.indexes()[0].row()
except IndexError:
FlatCAMApp.App.log.debug("on_list_selection_change(): Index Error (Nothing selected?)")
return
self.object_list[selection_index].build_ui()
def on_item_activated(self, index):
self.object_list[index.row()].build_ui()
def delete_all(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + "--> OC.delete_all()")
self.beginResetModel()
self.object_list = []
self.endResetModel()
def get_list(self):
return self.object_list

View File

@@ -1,114 +1,103 @@
############################################################ import sys
# FlatCAM: 2D Post-processing for Manufacturing # from PyQt4 import QtGui, QtCore
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from gi.repository import Gtk
from GUIElements import * from GUIElements import *
class ObjectUI(Gtk.VBox): class ObjectUI(QtGui.QWidget):
""" """
Base class for the UI of FlatCAM objects. Base class for the UI of FlatCAM objects. Deriving classes should
put UI elements in ObjectUI.custom_box (QtGui.QLayout).
""" """
def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object'): def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object', parent=None):
Gtk.VBox.__init__(self, spacing=3, margin=5, vexpand=False) QtGui.QWidget.__init__(self, parent=parent)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
## Page Title box (spacing between children) ## Page Title box (spacing between children)
self.title_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2) self.title_box = QtGui.QHBoxLayout()
self.pack_start(self.title_box, expand=False, fill=False, padding=2) layout.addLayout(self.title_box)
## Page Title icon ## Page Title icon
self.icon = Gtk.Image.new_from_file(icon_file) pixmap = QtGui.QPixmap(icon_file)
self.title_box.pack_start(self.icon, expand=False, fill=False, padding=2) self.icon = QtGui.QLabel()
self.icon.setPixmap(pixmap)
self.title_box.addWidget(self.icon, stretch=0)
## Title label ## Title label
self.title_label = Gtk.Label() self.title_label = QtGui.QLabel("<font size=5><b>" + title + "</b></font>")
self.title_label.set_markup("<b>" + title + "</b>") self.title_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.title_label.set_justify(Gtk.Justification.CENTER) self.title_box.addWidget(self.title_label, stretch=1)
self.title_box.pack_start(self.title_label, expand=False, fill=False, padding=2)
## Object name ## Object name
self.name_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2) self.name_box = QtGui.QHBoxLayout()
self.pack_start(self.name_box, expand=False, fill=False, padding=2) layout.addLayout(self.name_box)
name_label = Gtk.Label('Name:') name_label = QtGui.QLabel("Name:")
name_label.set_justify(Gtk.Justification.RIGHT) self.name_box.addWidget(name_label)
self.name_box.pack_start(name_label,
expand=False, fill=False, padding=2)
self.name_entry = FCEntry() self.name_entry = FCEntry()
self.name_box.pack_start(self.name_entry, expand=True, fill=False, padding=2) self.name_box.addWidget(self.name_entry)
## Box box for custom widgets ## Box box for custom widgets
self.custom_box = Gtk.VBox(spacing=3, margin=0, vexpand=False) self.custom_box = QtGui.QVBoxLayout()
self.pack_start(self.custom_box, expand=False, fill=False, padding=0) layout.addLayout(self.custom_box)
## Common to all objects ## Common to all objects
## Scale ## Scale
self.scale_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.scale_label = QtGui.QLabel('<b>Scale:</b>')
self.scale_label.set_markup('<b>Scale:</b>') self.scale_label.setToolTip(
self.scale_label.set_tooltip_markup(
"Change the size of the object." "Change the size of the object."
) )
self.pack_start(self.scale_label, expand=False, fill=False, padding=2) layout.addWidget(self.scale_label)
grid5 = Gtk.Grid(column_spacing=3, row_spacing=2) grid1 = QtGui.QGridLayout()
self.pack_start(grid5, expand=False, fill=False, padding=2) layout.addLayout(grid1)
# Factor # Factor
l10 = Gtk.Label('Factor:', xalign=1) faclabel = QtGui.QLabel('Factor:')
l10.set_tooltip_markup( faclabel.setToolTip(
"Factor by which to multiply\n" "Factor by which to multiply\n"
"geometric features of this object." "geometric features of this object."
) )
grid5.attach(l10, 0, 0, 1, 1) grid1.addWidget(faclabel, 0, 0)
self.scale_entry = FloatEntry() self.scale_entry = FloatEntry()
self.scale_entry.set_text("1.0") self.scale_entry.set_value(1.0)
grid5.attach(self.scale_entry, 1, 0, 1, 1) grid1.addWidget(self.scale_entry, 0, 1)
# GO Button # GO Button
self.scale_button = Gtk.Button(label='Scale') self.scale_button = QtGui.QPushButton('Scale')
self.scale_button.set_tooltip_markup( self.scale_button.setToolTip(
"Perform scaling operation." "Perform scaling operation."
) )
self.pack_start(self.scale_button, expand=False, fill=False, padding=2) layout.addWidget(self.scale_button)
## Offset ## Offset
self.offset_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.offset_label = QtGui.QLabel('<b>Offset:</b>')
self.offset_label.set_markup('<b>Offset:</b>') self.offset_label.setToolTip(
self.offset_label.set_tooltip_markup(
"Change the position of this object." "Change the position of this object."
) )
self.pack_start(self.offset_label, expand=False, fill=False, padding=2) layout.addWidget(self.offset_label)
grid6 = Gtk.Grid(column_spacing=3, row_spacing=2) grid2 = QtGui.QGridLayout()
self.pack_start(grid6, expand=False, fill=False, padding=2) layout.addLayout(grid2)
# Vector self.offset_label = QtGui.QLabel('Vector:')
l11 = Gtk.Label('Offset Vector:', xalign=1) self.offset_label.setToolTip(
l11.set_tooltip_markup(
"Amount by which to move the object\n" "Amount by which to move the object\n"
"in the x and y axes in (x, y) format." "in the x and y axes in (x, y) format."
) )
grid6.attach(l11, 0, 0, 1, 1) grid2.addWidget(self.offset_label, 0, 0)
self.offsetvector_entry = EvalEntry() self.offsetvector_entry = EvalEntry()
self.offsetvector_entry.set_text("(0.0, 0.0)") self.offsetvector_entry.setText("(0.0, 0.0)")
grid6.attach(self.offsetvector_entry, 1, 0, 1, 1) grid2.addWidget(self.offsetvector_entry, 0, 1)
self.offset_button = Gtk.Button(label='Scale') self.offset_button = QtGui.QPushButton('Offset')
self.offset_button.set_tooltip_markup( self.offset_button.setToolTip(
"Perform the offset operation." "Perform the offset operation."
) )
self.pack_start(self.offset_button, expand=False, fill=False, padding=2) layout.addWidget(self.offset_button)
def set_field(self, name, value): layout.addStretch()
getattr(self, name).set_value(value)
def get_field(self, name):
return getattr(self, name).get_value()
class CNCObjectUI(ObjectUI): class CNCObjectUI(ObjectUI):
@@ -116,57 +105,68 @@ class CNCObjectUI(ObjectUI):
User interface for CNCJob objects. User interface for CNCJob objects.
""" """
def __init__(self): def __init__(self, parent=None):
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png') ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png', parent=parent)
## Plot options ## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.plot_options_label.set_markup("<b>Plot Options:</b>") self.custom_box.addWidget(self.plot_options_label)
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2) grid0 = QtGui.QGridLayout()
self.custom_box.pack_start(grid0, expand=False, fill=False, padding=2) self.custom_box.addLayout(grid0)
# Plot CB # Plot CB
self.plot_cb = FCCheckBox(label='Plot') # self.plot_cb = QtGui.QCheckBox('Plot')
self.plot_cb.set_tooltip_markup( self.plot_cb = FCCheckBox('Plot')
self.plot_cb.setToolTip(
"Plot (show) this object." "Plot (show) this object."
) )
grid0.attach(self.plot_cb, 0, 0, 2, 1) grid0.addWidget(self.plot_cb, 0, 0)
# Tool dia for plot # Tool dia for plot
l1 = Gtk.Label('Tool dia:', xalign=1) tdlabel = QtGui.QLabel('Tool dia:')
l1.set_tooltip_markup( tdlabel.setToolTip(
"Diameter of the tool to be\n" "Diameter of the tool to be\n"
"rendered in the plot." "rendered in the plot."
) )
grid0.attach(l1, 0, 1, 1, 1) grid0.addWidget(tdlabel, 1, 0)
self.tooldia_entry = LengthEntry() self.tooldia_entry = LengthEntry()
grid0.attach(self.tooldia_entry, 1, 1, 1, 1) grid0.addWidget(self.tooldia_entry, 1, 1)
# Update plot button # Update plot button
self.updateplot_button = Gtk.Button(label='Update Plot') self.updateplot_button = QtGui.QPushButton('Update Plot')
self.updateplot_button.set_tooltip_markup( self.updateplot_button.setToolTip(
"Update the plot." "Update the plot."
) )
self.custom_box.pack_start(self.updateplot_button, expand=False, fill=False, padding=2) self.custom_box.addWidget(self.updateplot_button)
## Export G-Code ## Export G-Code
self.export_gcode_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.export_gcode_label = QtGui.QLabel("<b>Export G-Code:</b>")
self.export_gcode_label.set_markup("<b>Export G-Code:</b>") self.export_gcode_label.setToolTip(
self.export_gcode_label.set_tooltip_markup(
"Export and save G-Code to\n" "Export and save G-Code to\n"
"make this object to a file." "make this object to a file."
) )
self.custom_box.pack_start(self.export_gcode_label, expand=False, fill=False, padding=2) self.custom_box.addWidget(self.export_gcode_label)
# Append text to Gerber
appendlabel = QtGui.QLabel('Append to G-Code:')
appendlabel.setToolTip(
"Type here any G-Code commands you would\n"
"like to append to the generated file.\n"
"I.e.: M2 (End of program)"
)
self.custom_box.addWidget(appendlabel)
self.append_text = FCTextArea()
self.custom_box.addWidget(self.append_text)
# GO Button # GO Button
self.export_gcode_button = Gtk.Button(label='Export G-Code') self.export_gcode_button = QtGui.QPushButton('Export G-Code')
self.export_gcode_button.set_tooltip_markup( self.export_gcode_button.setToolTip(
"Opens dialog to save G-Code\n" "Opens dialog to save G-Code\n"
"file." "file."
) )
self.custom_box.pack_start(self.export_gcode_button, expand=False, fill=False, padding=2) self.custom_box.addWidget(self.export_gcode_button)
class GeometryObjectUI(ObjectUI): class GeometryObjectUI(ObjectUI):
@@ -174,135 +174,131 @@ class GeometryObjectUI(ObjectUI):
User interface for Geometry objects. User interface for Geometry objects.
""" """
def __init__(self): def __init__(self, parent=None):
ObjectUI.__init__(self, title='Geometry Object', icon_file='share/geometry32.png') super(GeometryObjectUI, self).__init__(title='Geometry Object', icon_file='share/geometry32.png', parent=parent)
## Plot options ## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.plot_options_label.set_markup("<b>Plot Options:</b>") self.custom_box.addWidget(self.plot_options_label)
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
# Plot CB # Plot CB
self.plot_cb = FCCheckBox(label='Plot') self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup( self.plot_cb.setToolTip(
"Plot (show) this object." "Plot (show) this object."
) )
grid0.attach(self.plot_cb, 0, 0, 1, 1) self.custom_box.addWidget(self.plot_cb)
## Create CNC Job ## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.cncjob_label = QtGui.QLabel('<b>Create CNC Job:</b>')
self.cncjob_label.set_markup('<b>Create CNC Job:</b>') self.cncjob_label.setToolTip(
self.cncjob_label.set_tooltip_markup(
"Create a CNC Job object\n" "Create a CNC Job object\n"
"tracing the contours of this\n" "tracing the contours of this\n"
"Geometry object." "Geometry object."
) )
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.cncjob_label)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2) grid1 = QtGui.QGridLayout()
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid1)
# Cut Z cutzlabel = QtGui.QLabel('Cut Z:')
l1 = Gtk.Label('Cut Z:', xalign=1) cutzlabel.setToolTip(
l1.set_tooltip_markup(
"Cutting depth (negative)\n" "Cutting depth (negative)\n"
"below the copper surface." "below the copper surface."
) )
grid1.attach(l1, 0, 0, 1, 1) grid1.addWidget(cutzlabel, 0, 0)
self.cutz_entry = LengthEntry() self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1) grid1.addWidget(self.cutz_entry, 0, 1)
# Travel Z # Travel Z
l2 = Gtk.Label('Travel Z:', xalign=1) travelzlabel = QtGui.QLabel('Travel Z:')
l2.set_tooltip_markup( travelzlabel.setToolTip(
"Height of the tool when\n" "Height of the tool when\n"
"moving without cutting." "moving without cutting."
) )
grid1.attach(l2, 0, 1, 1, 1) grid1.addWidget(travelzlabel, 1, 0)
self.travelz_entry = LengthEntry() self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1) grid1.addWidget(self.travelz_entry, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1) # Feedrate
l3.set_tooltip_markup( frlabel = QtGui.QLabel('Feed Rate:')
frlabel.setToolTip(
"Cutting speed in the XY\n" "Cutting speed in the XY\n"
"plane in units per minute" "plane in units per minute"
) )
grid1.attach(l3, 0, 2, 1, 1) grid1.addWidget(frlabel, 2, 0)
self.cncfeedrate_entry = LengthEntry() self.cncfeedrate_entry = LengthEntry()
grid1.attach(self.cncfeedrate_entry, 1, 2, 1, 1) grid1.addWidget(self.cncfeedrate_entry, 2, 1)
l4 = Gtk.Label('Tool dia:', xalign=1) # Tooldia
l4.set_tooltip_markup( tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"The diameter of the cutting\n" "The diameter of the cutting\n"
"tool (just for display)." "tool (just for display)."
) )
grid1.attach(l4, 0, 3, 1, 1) grid1.addWidget(tdlabel, 3, 0)
self.cnctooldia_entry = LengthEntry() self.cnctooldia_entry = LengthEntry()
grid1.attach(self.cnctooldia_entry, 1, 3, 1, 1) grid1.addWidget(self.cnctooldia_entry, 3, 1)
self.generate_cnc_button = Gtk.Button(label='Generate') self.generate_cnc_button = QtGui.QPushButton('Generate')
self.generate_cnc_button.set_tooltip_markup( self.generate_cnc_button.setToolTip(
"Generate the CNC Job object." "Generate the CNC Job object."
) )
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_cnc_button)
## Paint Area ## Paint area
self.paint_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.paint_label = QtGui.QLabel('<b>Paint Area:</b>')
self.paint_label.set_markup('<b>Paint Area:</b>') self.paint_label.setToolTip(
self.paint_label.set_tooltip_markup(
"Creates tool paths to cover the\n" "Creates tool paths to cover the\n"
"whole area of a polygon (remove\n" "whole area of a polygon (remove\n"
"all copper). You will be asked\n" "all copper). You will be asked\n"
"to click on the desired polygon." "to click on the desired polygon."
) )
self.custom_box.pack_start(self.paint_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.paint_label)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2) grid2 = QtGui.QGridLayout()
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid2)
# Tool dia # Tool dia
l5 = Gtk.Label('Tool dia:', xalign=1) ptdlabel = QtGui.QLabel('Tool dia:')
l5.set_tooltip_markup( ptdlabel.setToolTip(
"Diameter of the tool to\n" "Diameter of the tool to\n"
"be used in the operation." "be used in the operation."
) )
grid2.attach(l5, 0, 0, 1, 1) grid2.addWidget(ptdlabel, 0, 0)
self.painttooldia_entry = LengthEntry() self.painttooldia_entry = LengthEntry()
grid2.attach(self.painttooldia_entry, 1, 0, 1, 1) grid2.addWidget(self.painttooldia_entry, 0, 1)
# Overlap # Overlap
l6 = Gtk.Label('Overlap:', xalign=1) ovlabel = QtGui.QLabel('Overlap:')
l6.set_tooltip_markup( ovlabel.setToolTip(
"How much (fraction) of the tool\n" "How much (fraction) of the tool\n"
"width to overlap each tool pass." "width to overlap each tool pass."
) )
grid2.attach(l6, 0, 1, 1, 1) grid2.addWidget(ovlabel, 1, 0)
self.paintoverlap_entry = LengthEntry() self.paintoverlap_entry = LengthEntry()
grid2.attach(self.paintoverlap_entry, 1, 1, 1, 1) grid2.addWidget(self.paintoverlap_entry, 1, 1)
# Margin # Margin
l7 = Gtk.Label('Margin:', xalign=1) marginlabel = QtGui.QLabel('Margin:')
l7.set_tooltip_markup( marginlabel.setToolTip(
"Distance by which to avoid\n" "Distance by which to avoid\n"
"the edges of the polygon to\n" "the edges of the polygon to\n"
"be painted." "be painted."
) )
grid2.attach(l7, 0, 2, 1, 1) grid2.addWidget(marginlabel, 2, 0)
self.paintmargin_entry = LengthEntry() self.paintmargin_entry = LengthEntry()
grid2.attach(self.paintmargin_entry, 1, 2, 1, 1) grid2.addWidget(self.paintmargin_entry)
# GO Button # GO Button
self.generate_paint_button = Gtk.Button(label='Generate') self.generate_paint_button = QtGui.QPushButton('Generate')
self.generate_paint_button.set_tooltip_markup( self.generate_paint_button.setToolTip(
"After clicking here, click inside\n" "After clicking here, click inside\n"
"the polygon you wish to be painted.\n" "the polygon you wish to be painted.\n"
"A new Geometry object with the tool\n" "A new Geometry object with the tool\n"
"paths will be created." "paths will be created."
) )
self.custom_box.pack_start(self.generate_paint_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_paint_button)
class ExcellonObjectUI(ObjectUI): class ExcellonObjectUI(ObjectUI):
@@ -310,90 +306,85 @@ class ExcellonObjectUI(ObjectUI):
User interface for Excellon objects. User interface for Excellon objects.
""" """
def __init__(self): def __init__(self, parent=None):
ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png') ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png', parent=parent)
## Plot options ## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.plot_options_label.set_markup("<b>Plot Options:</b>") self.custom_box.addWidget(self.plot_options_label)
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
grid0 = QtGui.QGridLayout()
self.custom_box.addLayout(grid0)
self.plot_cb = FCCheckBox(label='Plot') self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup( self.plot_cb.setToolTip(
"Plot (show) this object." "Plot (show) this object."
) )
grid0.attach(self.plot_cb, 0, 0, 1, 1) grid0.addWidget(self.plot_cb, 0, 0)
self.solid_cb = FCCheckBox(label='Solid') self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.set_tooltip_markup( self.solid_cb.setToolTip(
"Solid circles." "Solid circles."
) )
grid0.attach(self.solid_cb, 1, 0, 1, 1) grid0.addWidget(self.solid_cb, 0, 1)
## Tools
self.tools_table_label = QtGui.QLabel('<b>Tools</b>')
self.tools_table_label.setToolTip(
"Tools in this Excellon object."
)
self.custom_box.addWidget(self.tools_table_label)
self.tools_table = QtGui.QTableWidget()
self.tools_table.setFixedHeight(100)
self.custom_box.addWidget(self.tools_table)
## Create CNC Job ## Create CNC Job
self.cncjob_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.cncjob_label = QtGui.QLabel('<b>Create CNC Job</b>')
self.cncjob_label.set_markup('<b>Create CNC Job</b>') self.cncjob_label.setToolTip(
self.cncjob_label.set_tooltip_markup(
"Create a CNC Job object\n" "Create a CNC Job object\n"
"for this drill object." "for this drill object."
) )
self.custom_box.pack_start(self.cncjob_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.cncjob_label)
grid1 = Gtk.Grid(column_spacing=3, row_spacing=2) grid1 = QtGui.QGridLayout()
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid1)
l1 = Gtk.Label('Cut Z:', xalign=1) cutzlabel = QtGui.QLabel('Cut Z:')
l1.set_tooltip_markup( cutzlabel.setToolTip(
"Drill depth (negative)\n" "Drill depth (negative)\n"
"below the copper surface." "below the copper surface."
) )
grid1.attach(l1, 0, 0, 1, 1) grid1.addWidget(cutzlabel, 0, 0)
self.cutz_entry = LengthEntry() self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1) grid1.addWidget(self.cutz_entry, 0, 1)
l2 = Gtk.Label('Travel Z:', xalign=1) travelzlabel = QtGui.QLabel('Travel Z:')
l2.set_tooltip_markup( travelzlabel.setToolTip(
"Tool height when travelling\n" "Tool height when travelling\n"
"across the XY plane." "across the XY plane."
) )
grid1.attach(l2, 0, 1, 1, 1) grid1.addWidget(travelzlabel, 1, 0)
self.travelz_entry = LengthEntry() self.travelz_entry = LengthEntry()
grid1.attach(self.travelz_entry, 1, 1, 1, 1) grid1.addWidget(self.travelz_entry, 1, 1)
l3 = Gtk.Label('Feed rate:', xalign=1) frlabel = QtGui.QLabel('Feed rate:')
l3.set_tooltip_markup( frlabel.setToolTip(
"Tool speed while drilling\n" "Tool speed while drilling\n"
"(in units per minute)." "(in units per minute)."
) )
grid1.attach(l3, 0, 2, 1, 1) grid1.addWidget(frlabel, 2, 0)
self.feedrate_entry = LengthEntry() self.feedrate_entry = LengthEntry()
grid1.attach(self.feedrate_entry, 1, 2, 1, 1) grid1.addWidget(self.feedrate_entry, 2, 1)
l4 = Gtk.Label('Tools:', xalign=1) choose_tools_label = QtGui.QLabel(
l4.set_tooltip_markup( "Select from the tools section above\n"
"Which tools to include\n" "the tools you want to include."
"in the CNC Job."
) )
grid1.attach(l4, 0, 3, 1, 1) self.custom_box.addWidget(choose_tools_label)
boxt = Gtk.Box()
grid1.attach(boxt, 1, 3, 1, 1)
self.tools_entry = FCEntry()
boxt.pack_start(self.tools_entry, expand=True, fill=False, padding=2)
self.choose_tools_button = Gtk.Button(label='Choose...')
self.choose_tools_button.set_tooltip_markup(
"Choose the tools\n"
"from a list."
)
boxt.pack_start(self.choose_tools_button, expand=True, fill=False, padding=2)
self.generate_cnc_button = Gtk.Button(label='Generate') self.generate_cnc_button = QtGui.QPushButton('Generate')
self.generate_cnc_button.set_tooltip_markup( self.generate_cnc_button.setToolTip(
"Generate the CNC Job." "Generate the CNC Job."
) )
self.custom_box.pack_start(self.generate_cnc_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_cnc_button)
class GerberObjectUI(ObjectUI): class GerberObjectUI(ObjectUI):
@@ -401,214 +392,210 @@ class GerberObjectUI(ObjectUI):
User interface for Gerber objects. User interface for Gerber objects.
""" """
def __init__(self): def __init__(self, parent=None):
ObjectUI.__init__(self, title='Gerber Object') ObjectUI.__init__(self, title='Gerber Object', parent=parent)
## Plot options ## Plot options
self.plot_options_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.plot_options_label.set_markup("<b>Plot Options:</b>") self.custom_box.addWidget(self.plot_options_label)
self.custom_box.pack_start(self.plot_options_label, expand=False, fill=True, padding=2)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=True, fill=False, padding=2)
grid0 = QtGui.QGridLayout()
self.custom_box.addLayout(grid0)
# Plot CB # Plot CB
self.plot_cb = FCCheckBox(label='Plot') self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup( self.plot_options_label.setToolTip(
"Plot (show) this object." "Plot (show) this object."
) )
grid0.attach(self.plot_cb, 0, 0, 1, 1) grid0.addWidget(self.plot_cb, 0, 0)
# Solid CB # Solid CB
self.solid_cb = FCCheckBox(label='Solid') self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.set_tooltip_markup( self.solid_cb.setToolTip(
"Solid color polygons." "Solid color polygons."
) )
grid0.attach(self.solid_cb, 1, 0, 1, 1) grid0.addWidget(self.solid_cb, 0, 1)
# Multicolored CB # Multicolored CB
self.multicolored_cb = FCCheckBox(label='Multicolored') self.multicolored_cb = FCCheckBox(label='Multicolored')
self.multicolored_cb.set_tooltip_markup( self.multicolored_cb.setToolTip(
"Draw polygons in different colors." "Draw polygons in different colors."
) )
grid0.attach(self.multicolored_cb, 2, 0, 1, 1) grid0.addWidget(self.multicolored_cb, 0, 2)
## Isolation Routing ## Isolation Routing
self.isolation_routing_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.isolation_routing_label = QtGui.QLabel("<b>Isolation Routing:</b>")
self.isolation_routing_label.set_markup("<b>Isolation Routing:</b>") self.isolation_routing_label.setToolTip(
self.isolation_routing_label.set_tooltip_markup(
"Create a Geometry object with\n" "Create a Geometry object with\n"
"toolpaths to cut outside polygons." "toolpaths to cut outside polygons."
) )
self.custom_box.pack_start(self.isolation_routing_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.isolation_routing_label)
grid = Gtk.Grid(column_spacing=3, row_spacing=2) grid1 = QtGui.QGridLayout()
self.custom_box.pack_start(grid, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid1)
tdlabel = QtGui.QLabel('Tool dia:')
l1 = Gtk.Label('Tool diam:', xalign=1) tdlabel.setToolTip(
l1.set_tooltip_markup(
"Diameter of the cutting tool." "Diameter of the cutting tool."
) )
grid.attach(l1, 0, 0, 1, 1) grid1.addWidget(tdlabel, 0, 0)
self.iso_tool_dia_entry = LengthEntry() self.iso_tool_dia_entry = LengthEntry()
grid.attach(self.iso_tool_dia_entry, 1, 0, 1, 1) grid1.addWidget(self.iso_tool_dia_entry, 0, 1)
l2 = Gtk.Label('Width (# passes):', xalign=1) passlabel = QtGui.QLabel('Width (# passes):')
l2.set_tooltip_markup( passlabel.setToolTip(
"Width of the isolation gap in\n" "Width of the isolation gap in\n"
"number (integer) of tool widths." "number (integer) of tool widths."
) )
grid.attach(l2, 0, 1, 1, 1) grid1.addWidget(passlabel, 1, 0)
self.iso_width_entry = IntEntry() self.iso_width_entry = IntEntry()
grid.attach(self.iso_width_entry, 1, 1, 1, 1) grid1.addWidget(self.iso_width_entry, 1, 1)
l3 = Gtk.Label('Pass overlap:', xalign=1) overlabel = QtGui.QLabel('Pass overlap:')
l3.set_tooltip_markup( overlabel.setToolTip(
"How much (fraction of tool width)\n" "How much (fraction of tool width)\n"
"to overlap each pass." "to overlap each pass."
) )
grid.attach(l3, 0, 2, 1, 1) grid1.addWidget(overlabel, 2, 0)
self.iso_overlap_entry = FloatEntry() self.iso_overlap_entry = FloatEntry()
grid.attach(self.iso_overlap_entry, 1, 2, 1, 1) grid1.addWidget(self.iso_overlap_entry, 2, 1)
self.generate_iso_button = Gtk.Button(label='Generate Geometry') self.generate_iso_button = QtGui.QPushButton('Generate Geometry')
self.generate_iso_button.set_tooltip_markup( self.generate_iso_button.setToolTip(
"Create the Geometry Object\n" "Create the Geometry Object\n"
"for isolation routing." "for isolation routing."
) )
self.custom_box.pack_start(self.generate_iso_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_iso_button)
## Board cuttout ## Board cuttout
self.board_cutout_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.board_cutout_label = QtGui.QLabel("<b>Board cutout:</b>")
self.board_cutout_label.set_markup("<b>Board cutout:</b>") self.board_cutout_label.setToolTip(
self.board_cutout_label.set_tooltip_markup(
"Create toolpaths to cut around\n" "Create toolpaths to cut around\n"
"the PCB and separate it from\n" "the PCB and separate it from\n"
"the original board." "the original board."
) )
self.custom_box.pack_start(self.board_cutout_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.board_cutout_label)
grid2 = Gtk.Grid(column_spacing=3, row_spacing=2) grid2 = QtGui.QGridLayout()
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid2)
tdclabel = QtGui.QLabel('Tool dia:')
l4 = Gtk.Label('Tool dia:', xalign=1) tdclabel.setToolTip(
l4.set_tooltip_markup(
"Diameter of the cutting tool." "Diameter of the cutting tool."
) )
grid2.attach(l4, 0, 0, 1, 1) grid2.addWidget(tdclabel, 0, 0)
self.cutout_tooldia_entry = LengthEntry() self.cutout_tooldia_entry = LengthEntry()
grid2.attach(self.cutout_tooldia_entry, 1, 0, 1, 1) grid2.addWidget(self.cutout_tooldia_entry, 0, 1)
l5 = Gtk.Label('Margin:', xalign=1) marginlabel = QtGui.QLabel('Margin:')
l5.set_tooltip_markup( marginlabel.setToolTip(
"Distance from objects at which\n" "Distance from objects at which\n"
"to draw the cutout." "to draw the cutout."
) )
grid2.attach(l5, 0, 1, 1, 1) grid2.addWidget(marginlabel, 1, 0)
self.cutout_margin_entry = LengthEntry() self.cutout_margin_entry = LengthEntry()
grid2.attach(self.cutout_margin_entry, 1, 1, 1, 1) grid2.addWidget(self.cutout_margin_entry, 1, 1)
l6 = Gtk.Label('Gap size:', xalign=1) gaplabel = QtGui.QLabel('Gap size:')
l6.set_tooltip_markup( gaplabel.setToolTip(
"Size of the gaps in the toolpath\n" "Size of the gaps in the toolpath\n"
"that will remain to hold the\n" "that will remain to hold the\n"
"board in place." "board in place."
) )
grid2.attach(l6, 0, 2, 1, 1) grid2.addWidget(gaplabel, 2, 0)
self.cutout_gap_entry = LengthEntry() self.cutout_gap_entry = LengthEntry()
grid2.attach(self.cutout_gap_entry, 1, 2, 1, 1) grid2.addWidget(self.cutout_gap_entry, 2, 1)
l7 = Gtk.Label('Gaps:', xalign=1) gapslabel = QtGui.QLabel('Gaps:')
l7.set_tooltip_markup( gapslabel.setToolTip(
"Where to place the gaps, Top/Bottom\n" "Where to place the gaps, Top/Bottom\n"
"Left/Rigt, or on all 4 sides." "Left/Rigt, or on all 4 sides."
) )
grid2.attach(l7, 0, 3, 1, 1) grid2.addWidget(gapslabel, 3, 0)
self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'}, self.gaps_radio = RadioSet([{'label': '2 (T/B)', 'value': 'tb'},
{'label': '2 (L/R)', 'value': 'lr'}, {'label': '2 (L/R)', 'value': 'lr'},
{'label': '4', 'value': '4'}]) {'label': '4', 'value': '4'}])
grid2.attach(self.gaps_radio, 1, 3, 1, 1) grid2.addWidget(self.gaps_radio, 3, 1)
self.generate_cutout_button = Gtk.Button(label='Generate Geometry') self.generate_cutout_button = QtGui.QPushButton('Generate Geometry')
self.generate_cutout_button.set_tooltip_markup( self.generate_cutout_button.setToolTip(
"Generate the geometry for\n" "Generate the geometry for\n"
"the board cutout." "the board cutout."
) )
self.custom_box.pack_start(self.generate_cutout_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_cutout_button)
## Non-copper regions ## Non-copper regions
self.noncopper_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.noncopper_label = QtGui.QLabel("<b>Non-copper regions:</b>")
self.noncopper_label.set_markup("<b>Non-copper regions:</b>") self.noncopper_label.setToolTip(
self.noncopper_label.set_tooltip_markup(
"Create polygons covering the\n" "Create polygons covering the\n"
"areas without copper on the PCB.\n" "areas without copper on the PCB.\n"
"Equivalent to the inverse of this\n" "Equivalent to the inverse of this\n"
"object. Can be used to remove all\n" "object. Can be used to remove all\n"
"copper from a specified region." "copper from a specified region."
) )
self.custom_box.pack_start(self.noncopper_label, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.noncopper_label)
grid3 = Gtk.Grid(column_spacing=3, row_spacing=2) grid3 = QtGui.QGridLayout()
self.custom_box.pack_start(grid3, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid3)
l8 = Gtk.Label('Boundary margin:', xalign=1) # Margin
l8.set_tooltip_markup( bmlabel = QtGui.QLabel('Boundary Margin:')
bmlabel.setToolTip(
"Specify the edge of the PCB\n" "Specify the edge of the PCB\n"
"by drawing a box around all\n" "by drawing a box around all\n"
"objects with this minimum\n" "objects with this minimum\n"
"distance." "distance."
) )
grid3.attach(l8, 0, 0, 1, 1) grid3.addWidget(bmlabel, 0, 0)
self.noncopper_margin_entry = LengthEntry() self.noncopper_margin_entry = LengthEntry()
grid3.attach(self.noncopper_margin_entry, 1, 0, 1, 1) grid3.addWidget(self.noncopper_margin_entry, 0, 1)
# Rounded corners
self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners") self.noncopper_rounded_cb = FCCheckBox(label="Rounded corners")
self.noncopper_rounded_cb.set_tooltip_markup( self.noncopper_rounded_cb.setToolTip(
"If the boundary of the board\n"
"is to have rounded corners\n"
"their radius is equal to the margin."
)
grid3.attach(self.noncopper_rounded_cb, 0, 1, 2, 1)
self.generate_noncopper_button = Gtk.Button(label='Generate Geometry')
self.generate_noncopper_button.set_tooltip_markup(
"Creates a Geometry objects with polygons\n" "Creates a Geometry objects with polygons\n"
"covering the copper-free areas of the PCB." "covering the copper-free areas of the PCB."
) )
self.custom_box.pack_start(self.generate_noncopper_button, expand=True, fill=False, padding=2) grid3.addWidget(self.noncopper_rounded_cb, 1, 0, 1, 2)
self.generate_noncopper_button = QtGui.QPushButton('Generate Geometry')
self.custom_box.addWidget(self.generate_noncopper_button)
## Bounding box ## Bounding box
self.boundingbox_label = Gtk.Label(justify=Gtk.Justification.LEFT, xalign=0, margin_top=5) self.boundingbox_label = QtGui.QLabel('<b>Bounding Box:</b>')
self.boundingbox_label.set_markup('<b>Bounding Box:</b>') self.custom_box.addWidget(self.boundingbox_label)
self.boundingbox_label.set_tooltip_markup(
"Create a Geometry object with a rectangle\n"
"enclosing all polygons at a given distance."
)
self.custom_box.pack_start(self.boundingbox_label, expand=True, fill=False, padding=2)
grid4 = Gtk.Grid(column_spacing=3, row_spacing=2) grid4 = QtGui.QGridLayout()
self.custom_box.pack_start(grid4, expand=True, fill=False, padding=2) self.custom_box.addLayout(grid4)
l9 = Gtk.Label('Boundary Margin:', xalign=1) bbmargin = QtGui.QLabel('Boundary Margin:')
l9.set_tooltip_markup( bbmargin.setToolTip(
"Distance of the edges of the box\n" "Distance of the edges of the box\n"
"to the nearest polygon." "to the nearest polygon."
) )
grid4.attach(l9, 0, 0, 1, 1) grid4.addWidget(bbmargin, 0, 0)
self.bbmargin_entry = LengthEntry() self.bbmargin_entry = LengthEntry()
grid4.attach(self.bbmargin_entry, 1, 0, 1, 1) grid4.addWidget(self.bbmargin_entry, 0, 1)
self.bbrounded_cb = FCCheckBox(label="Rounded corners") self.bbrounded_cb = FCCheckBox(label="Rounded corners")
self.bbrounded_cb.set_tooltip_markup( self.bbrounded_cb.setToolTip(
"If the bounding box is \n" "If the bounding box is \n"
"to have rounded corners\n" "to have rounded corners\n"
"their radius is equal to\n" "their radius is equal to\n"
"the margin." "the margin."
) )
grid4.attach(self.bbrounded_cb, 0, 1, 2, 1) grid4.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
self.generate_bb_button = Gtk.Button(label='Generate Geometry') self.generate_bb_button = QtGui.QPushButton('Generate Geometry')
self.generate_bb_button.set_tooltip_markup( self.generate_bb_button.setToolTip(
"Genrate the Geometry object." "Genrate the Geometry object."
) )
self.custom_box.pack_start(self.generate_bb_button, expand=True, fill=False, padding=2) self.custom_box.addWidget(self.generate_bb_button)
# def main():
#
# app = QtGui.QApplication(sys.argv)
# fc = GerberObjectUI()
# sys.exit(app.exec_())
#
#
# if __name__ == '__main__':
# main()

View File

@@ -1,10 +1,26 @@
from gi.repository import Gdk ############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure from matplotlib.figure import Figure
from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
#from FlatCAMApp import *
import FlatCAMApp import FlatCAMApp
# class FCFigureCanvas(FigureCanvas):
#
# resized = QtCore.pyqtSignal()
#
# def resizeEvent(self, event):
# FigureCanvas.resizeEvent(self, event)
# self.emit(QtCore.SIGNAL("resized"))
# print self.geometry()
class PlotCanvas: class PlotCanvas:
""" """
Class handling the plotting area in the application. Class handling the plotting area in the application.
@@ -38,18 +54,21 @@ class PlotCanvas:
# The canvas is the top level container (Gtk.DrawingArea) # The canvas is the top level container (Gtk.DrawingArea)
self.canvas = FigureCanvas(self.figure) self.canvas = FigureCanvas(self.figure)
self.canvas.set_hexpand(1) #self.canvas.set_hexpand(1)
self.canvas.set_vexpand(1) #self.canvas.set_vexpand(1)
self.canvas.set_can_focus(True) # For key press #self.canvas.set_can_focus(True) # For key press
# Attach to parent # Attach to parent
self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns??
self.container.addWidget(self.canvas) # Qt
# Events # Events
self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
self.canvas.connect('configure-event', self.auto_adjust_axes) #self.canvas.connect('configure-event', self.auto_adjust_axes)
self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) self.canvas.mpl_connect('resize_event', self.auto_adjust_axes)
self.canvas.connect("scroll-event", self.on_scroll) #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK)
#self.canvas.connect("scroll-event", self.on_scroll)
self.canvas.mpl_connect('scroll_event', self.on_scroll)
self.canvas.mpl_connect('key_press_event', self.on_key_down) self.canvas.mpl_connect('key_press_event', self.on_key_down)
self.canvas.mpl_connect('key_release_event', self.on_key_up) self.canvas.mpl_connect('key_release_event', self.on_key_up)
@@ -62,6 +81,7 @@ class PlotCanvas:
:param event: :param event:
:return: :return:
""" """
FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
self.key = event.key self.key = event.key
def on_key_up(self, event): def on_key_up(self, event):
@@ -125,7 +145,7 @@ class PlotCanvas:
self.axes.grid(True) self.axes.grid(True)
# Re-draw # Re-draw
self.canvas.queue_draw() self.canvas.draw()
def adjust_axes(self, xmin, ymin, xmax, ymax): def adjust_axes(self, xmin, ymin, xmax, ymax):
""" """
@@ -144,7 +164,7 @@ class PlotCanvas:
:return: None :return: None
""" """
FlatCAMApp.App.log.debug("PC.adjust_axes()") # FlatCAMApp.App.log.debug("PC.adjust_axes()")
width = xmax - xmin width = xmax - xmin
height = ymax - ymin height = ymax - ymin
@@ -182,7 +202,7 @@ class PlotCanvas:
ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio]) ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
# Re-draw # Re-draw
self.canvas.queue_draw() self.canvas.draw()
def auto_adjust_axes(self, *args): def auto_adjust_axes(self, *args):
""" """
@@ -234,7 +254,7 @@ class PlotCanvas:
ax.set_ylim((ymin, ymax)) ax.set_ylim((ymin, ymax))
# Re-draw # Re-draw
self.canvas.queue_draw() self.canvas.draw()
def pan(self, x, y): def pan(self, x, y):
xmin, xmax = self.axes.get_xlim() xmin, xmax = self.axes.get_xlim()
@@ -248,7 +268,7 @@ class PlotCanvas:
ax.set_ylim((ymin + y*height, ymax + y*height)) ax.set_ylim((ymin + y*height, ymax + y*height))
# Re-draw # Re-draw
self.canvas.queue_draw() self.canvas.draw()
def new_axes(self, name): def new_axes(self, name):
""" """
@@ -261,24 +281,24 @@ class PlotCanvas:
return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name)
def on_scroll(self, canvas, event): def on_scroll(self, event):
""" """
Scroll event handler. Scroll event handler.
:param canvas: The widget generating the event. Ignored.
:param event: Event object containing the event information. :param event: Event object containing the event information.
:return: None :return: None
""" """
# So it can receive key presses # So it can receive key presses
self.canvas.grab_focus() # self.canvas.grab_focus()
self.canvas.setFocus()
# Event info # Event info
z, direction = event.get_scroll_direction() # z, direction = event.get_scroll_direction()
if self.key is None: if self.key is None:
if direction is Gdk.ScrollDirection.UP: if event.button == 'up':
self.zoom(1.5, self.mouse) self.zoom(1.5, self.mouse)
else: else:
self.zoom(1/1.5, self.mouse) self.zoom(1/1.5, self.mouse)
@@ -286,15 +306,15 @@ class PlotCanvas:
if self.key == 'shift': if self.key == 'shift':
if direction is Gdk.ScrollDirection.UP: if event.button == 'up':
self.pan(0.3, 0) self.pan(0.3, 0)
else: else:
self.pan(-0.3, 0) self.pan(-0.3, 0)
return return
if self.key == 'ctrl+control': if self.key == 'control':
if direction is Gdk.ScrollDirection.UP: if event.button == 'up':
self.pan(0, 0.3) self.pan(0, 0.3)
else: else:
self.pan(0, -0.3) self.pan(0, -0.3)
@@ -308,3 +328,4 @@ class PlotCanvas:
:return: None :return: None
""" """
self.mouse = [event.xdata, event.ydata] self.mouse = [event.xdata, event.ydata]

View File

@@ -1 +1 @@
{"gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "geometry_plot": true, "excellon_feedrate": 5.0, "gerber_plot": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "excellon_plot": true, "gerber_isotooldia": 0.016, "gerber_bboxmargin": 0.0, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.2, "excellon_solid": false, "geometry_paintmargin": 0.01, "geometry_cutz": -0.002, "geometry_cnctooldia": 0.016, "gerber_cutouttooldia": 0.07, "gerber_gaps": "4", "geometry_painttooldia": 0.0625, "cncjob_plot": true, "gerber_cutoutgapsize": 0.15, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "gerber_noncoppermargin": 0.0} {"gerber_cutoutgapsize": 0.15, "gerber_noncopperrounded": false, "geometry_paintoverlap": 0.15, "excellon_plot": true, "gerber_isotooldia": 0.016, "gerber_plot": true, "excellon_drillz": -0.1, "geometry_feedrate": 3.0, "units": "IN", "excellon_travelz": 0.1, "gerber_multicolored": false, "gerber_solid": true, "gerber_isopasses": 1, "cncjob_append": "", "excellon_feedrate": 3.0, "cncjob_tooldia": 0.016, "geometry_travelz": 0.1, "gerber_cutoutmargin": 0.1, "excellon_solid": true, "geometry_paintmargin": 0.0, "geometry_cutz": -0.002, "gerber_noncoppermargin": 0.0, "gerber_cutouttooldia": 0.07, "gerber_gaps": "4", "gerber_bboxmargin": 0.0, "cncjob_plot": true, "geometry_plot": true, "gerber_isooverlap": 0.15, "gerber_bboxrounded": false, "geometry_cnctooldia": 0.016, "geometry_painttooldia": 0.07}

View File

@@ -1 +1 @@
[{"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\pcbcam\\test_files\\bedini 7 coils capacitor discharge.gbr"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\test.cnc"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2_test.fcproj"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\Controller_Board_Shield\\CBS-B_Cu.gbl"}, {"kind": "cncjob", "filename": "C:\\Users\\jpcaram\\Dropbox\\CNC\\squarerpcb\\KiCad_Squarer.gcode"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Squarer\\KiCad_Squarer.drl"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Squarer\\KiCad_Squarer-F_Cu.gtl"}, {"kind": "project", "filename": "C:\\Users\\jpcaram\\Dropbox\\VNA\\KiCad_Bridge2\\Bridge2.fcproj"}, {"kind": "excellon", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.TXT"}, {"kind": "gerber", "filename": "C:\\Users\\jpcaram\\Dropbox\\PhD\\Kenney\\Project Outputs for AnalogPredistortion1\\apd.GTL"}] [{"kind": "excellon", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/FlatCam_Drilling_Test/FlatCam_Drilling_Test.drl"}, {"kind": "excellon", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/Excellon_Planck/X-Y CONTROLLER - Drill Data - Through Hole.drl"}, {"kind": "gerber", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/7V-PSU/7V PSU.GTL"}, {"kind": "cncjob", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/output.gcode"}, {"kind": "project", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/project_copy.fcproj"}, {"kind": "project", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/test_files/a_project.fcproj"}, {"kind": "project", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/cirkuix/tests/FlatCAM_TestProject.fcproj"}, {"kind": "cncjob", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/cirkuix/tests/FlatCAM_TestGCode.gcode"}, {"kind": "cncjob", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/cirkuix/tests/CBS-B_Cu_ISOLATION_GCODE.ngc"}, {"kind": "excellon", "filename": "C:/Users/jpcaram/Dropbox/CNC/pcbcam/cirkuix/tests/FlatCAM_TestDrill.drl"}]

View File

@@ -1,783 +0,0 @@
G04 (created by PCBNEW (2013-07-07 BZR 4022)-stable) date 11/20/2013 5:37:08 PM*
%MOIN*%
G04 Gerber Fmt 3.4, Leading zero omitted, Abs format*
%FSLAX34Y34*%
G01*
G70*
G90*
G04 APERTURE LIST*
%ADD10C,0.00590551*%
%ADD11C,0.015*%
%ADD12R,0.0393X0.0629*%
%ADD13R,0.2125X0.244*%
%ADD14R,0.02X0.03*%
%ADD15R,0.144X0.08*%
%ADD16R,0.04X0.08*%
%ADD17R,0.0394X0.0354*%
%ADD18R,0.0354X0.0394*%
%ADD19R,0.063X0.071*%
%ADD20R,0.071X0.063*%
%ADD21C,0.1*%
%ADD22R,0.08X0.08*%
%ADD23C,0.08*%
%ADD24C,0.04*%
%ADD25C,0.02*%
G04 APERTURE END LIST*
G54D10*
G54D11*
X27233Y12376D02*
X27728Y12376D01*
X27461Y12071D01*
X27576Y12071D01*
X27652Y12033D01*
X27690Y11995D01*
X27728Y11919D01*
X27728Y11728D01*
X27690Y11652D01*
X27652Y11614D01*
X27576Y11576D01*
X27347Y11576D01*
X27271Y11614D01*
X27233Y11652D01*
X24021Y12300D02*
X24059Y12338D01*
X24135Y12376D01*
X24326Y12376D01*
X24402Y12338D01*
X24440Y12300D01*
X24478Y12223D01*
X24478Y12147D01*
X24440Y12033D01*
X23983Y11576D01*
X24478Y11576D01*
X4578Y11701D02*
X4121Y11701D01*
X4350Y11701D02*
X4350Y12501D01*
X4273Y12386D01*
X4197Y12310D01*
X4121Y12272D01*
X569Y1526D02*
X1064Y1526D01*
X797Y1221D01*
X911Y1221D01*
X988Y1183D01*
X1026Y1145D01*
X1064Y1069D01*
X1064Y878D01*
X1026Y802D01*
X988Y764D01*
X911Y726D01*
X683Y726D01*
X607Y764D01*
X569Y802D01*
X1407Y802D02*
X1445Y764D01*
X1407Y726D01*
X1369Y764D01*
X1407Y802D01*
X1407Y726D01*
X1711Y1526D02*
X2207Y1526D01*
X1940Y1221D01*
X2054Y1221D01*
X2130Y1183D01*
X2169Y1145D01*
X2207Y1069D01*
X2207Y878D01*
X2169Y802D01*
X2130Y764D01*
X2054Y726D01*
X1826Y726D01*
X1750Y764D01*
X1711Y802D01*
X2435Y1526D02*
X2702Y726D01*
X2969Y1526D01*
X1672Y14551D02*
X1291Y14551D01*
X1253Y14170D01*
X1291Y14208D01*
X1367Y14246D01*
X1558Y14246D01*
X1634Y14208D01*
X1672Y14170D01*
X1710Y14094D01*
X1710Y13903D01*
X1672Y13827D01*
X1634Y13789D01*
X1558Y13751D01*
X1367Y13751D01*
X1291Y13789D01*
X1253Y13827D01*
X1939Y14551D02*
X2205Y13751D01*
X2472Y14551D01*
X26525Y14651D02*
X26677Y14651D01*
X26753Y14613D01*
X26829Y14536D01*
X26867Y14384D01*
X26867Y14117D01*
X26829Y13965D01*
X26753Y13889D01*
X26677Y13851D01*
X26525Y13851D01*
X26448Y13889D01*
X26372Y13965D01*
X26334Y14117D01*
X26334Y14384D01*
X26372Y14536D01*
X26448Y14613D01*
X26525Y14651D01*
X27210Y14651D02*
X27210Y14003D01*
X27248Y13927D01*
X27286Y13889D01*
X27363Y13851D01*
X27515Y13851D01*
X27591Y13889D01*
X27629Y13927D01*
X27667Y14003D01*
X27667Y14651D01*
X27934Y14651D02*
X28391Y14651D01*
X28163Y13851D02*
X28163Y14651D01*
X17800Y13451D02*
X17952Y13451D01*
X18028Y13413D01*
X18104Y13336D01*
X18142Y13184D01*
X18142Y12917D01*
X18104Y12765D01*
X18028Y12689D01*
X17952Y12651D01*
X17800Y12651D01*
X17723Y12689D01*
X17647Y12765D01*
X17609Y12917D01*
X17609Y13184D01*
X17647Y13336D01*
X17723Y13413D01*
X17800Y13451D01*
X18485Y13451D02*
X18485Y12803D01*
X18523Y12727D01*
X18561Y12689D01*
X18638Y12651D01*
X18790Y12651D01*
X18866Y12689D01*
X18904Y12727D01*
X18942Y12803D01*
X18942Y13451D01*
X19209Y13451D02*
X19666Y13451D01*
X19438Y12651D02*
X19438Y13451D01*
X8900Y13326D02*
X9052Y13326D01*
X9128Y13288D01*
X9204Y13211D01*
X9242Y13059D01*
X9242Y12792D01*
X9204Y12640D01*
X9128Y12564D01*
X9052Y12526D01*
X8900Y12526D01*
X8823Y12564D01*
X8747Y12640D01*
X8709Y12792D01*
X8709Y13059D01*
X8747Y13211D01*
X8823Y13288D01*
X8900Y13326D01*
X9585Y13326D02*
X9585Y12678D01*
X9623Y12602D01*
X9661Y12564D01*
X9738Y12526D01*
X9890Y12526D01*
X9966Y12564D01*
X10004Y12602D01*
X10042Y12678D01*
X10042Y13326D01*
X10309Y13326D02*
X10766Y13326D01*
X10538Y12526D02*
X10538Y13326D01*
X28880Y726D02*
X28880Y1526D01*
X29261Y726D02*
X29261Y1526D01*
X29719Y726D01*
X29719Y1526D01*
X23705Y2126D02*
X23705Y2926D01*
X24086Y2126D02*
X24086Y2926D01*
X24544Y2126D01*
X24544Y2926D01*
X3355Y3126D02*
X3355Y3926D01*
X3736Y3126D02*
X3736Y3926D01*
X4194Y3126D01*
X4194Y3926D01*
G54D12*
X12278Y8961D03*
X14072Y8961D03*
G54D13*
X13175Y11834D03*
G54D14*
X5850Y7450D03*
X6600Y7450D03*
X5850Y8450D03*
X6225Y7450D03*
X6600Y8450D03*
X21075Y7400D03*
X21825Y7400D03*
X21075Y8400D03*
X21450Y7400D03*
X21825Y8400D03*
G54D15*
X15962Y2837D03*
G54D16*
X15962Y5437D03*
X15062Y5437D03*
X16862Y5437D03*
G54D17*
X23150Y6429D03*
X23150Y7021D03*
G54D18*
X20546Y5800D03*
X19954Y5800D03*
G54D17*
X22825Y7829D03*
X22825Y8421D03*
X21825Y9404D03*
X21825Y9996D03*
G54D18*
X19579Y9325D03*
X20171Y9325D03*
X4454Y9325D03*
X5046Y9325D03*
G54D17*
X6600Y9629D03*
X6600Y10221D03*
X7600Y8004D03*
X7600Y8596D03*
X8000Y6429D03*
X8000Y7021D03*
G54D18*
X5171Y5850D03*
X4579Y5850D03*
G54D19*
X12650Y7900D03*
X13750Y7900D03*
G54D20*
X11025Y9750D03*
X11025Y10850D03*
X13737Y4387D03*
X13737Y5487D03*
G54D19*
X16962Y6787D03*
X15862Y6787D03*
G54D17*
X20175Y7804D03*
X20175Y8396D03*
X6225Y5854D03*
X6225Y6446D03*
G54D18*
X16608Y7712D03*
X16016Y7712D03*
X23421Y9375D03*
X22829Y9375D03*
X24296Y7850D03*
X23704Y7850D03*
G54D17*
X21450Y5804D03*
X21450Y6396D03*
G54D18*
X9146Y8000D03*
X8554Y8000D03*
X8196Y9625D03*
X7604Y9625D03*
G54D17*
X10100Y10104D03*
X10100Y10696D03*
X5050Y7854D03*
X5050Y8446D03*
G54D14*
X28950Y6850D03*
X29700Y6850D03*
X28950Y7850D03*
X29325Y6850D03*
X29700Y7850D03*
G54D21*
X21825Y11925D03*
X22825Y12925D03*
X20825Y12925D03*
X20825Y10925D03*
X22825Y10925D03*
X21450Y3475D03*
X22450Y4475D03*
X20450Y4475D03*
X20450Y2475D03*
X22450Y2475D03*
X29325Y3475D03*
X30325Y4475D03*
X28325Y4475D03*
X28325Y2475D03*
X30325Y2475D03*
X29700Y11925D03*
X30700Y12925D03*
X28700Y12925D03*
X28700Y10925D03*
X30700Y10925D03*
G54D17*
X27425Y8571D03*
X27425Y7979D03*
G54D22*
X1375Y8325D03*
G54D23*
X1375Y9325D03*
X1375Y10325D03*
G54D22*
X16375Y9625D03*
G54D23*
X16375Y10625D03*
X16375Y11625D03*
G54D22*
X26375Y8425D03*
G54D23*
X26375Y9425D03*
X26375Y10425D03*
G54D22*
X10225Y2475D03*
G54D23*
X11225Y2475D03*
G54D22*
X28375Y8850D03*
G54D21*
X10400Y6275D03*
X25125Y5450D03*
X6600Y11925D03*
X7600Y12925D03*
X5600Y12925D03*
X5600Y10925D03*
X7600Y10925D03*
X6225Y3475D03*
X7225Y4475D03*
X5225Y4475D03*
X5225Y2475D03*
X7225Y2475D03*
G54D17*
X28225Y7254D03*
X28225Y7846D03*
G54D23*
X12375Y2475D03*
X30075Y6475D03*
X28225Y6775D03*
X20350Y7075D03*
X22300Y6950D03*
X7100Y6900D03*
X19475Y5175D03*
X24325Y9375D03*
X25175Y7850D03*
X17525Y7600D03*
X13625Y3325D03*
X11025Y8900D03*
X10000Y8000D03*
X8875Y9625D03*
X4950Y7275D03*
X3825Y5850D03*
G54D24*
X15962Y2837D02*
X15962Y1162D01*
X15962Y1162D02*
X15975Y1150D01*
X16375Y9625D02*
X17450Y9625D01*
X18650Y8425D02*
X18650Y1150D01*
X17450Y9625D02*
X18650Y8425D01*
X26375Y8425D02*
X26375Y7775D01*
X26350Y7800D02*
X26375Y7775D01*
X26375Y7775D02*
X26975Y7175D01*
X25700Y1150D02*
X18650Y1150D01*
X18650Y1150D02*
X16925Y1150D01*
X15975Y1150D02*
X16925Y1150D01*
X26975Y2425D02*
X25700Y1150D01*
X26975Y7175D02*
X26975Y2425D01*
X15962Y2837D02*
X15962Y5437D01*
X13175Y1150D02*
X13550Y1150D01*
X1375Y8325D02*
X1375Y3774D01*
X4000Y1150D02*
X13175Y1150D01*
X1375Y3774D02*
X4000Y1150D01*
X13550Y1150D02*
X15975Y1150D01*
G54D25*
X16016Y7712D02*
X15862Y7558D01*
X15862Y7558D02*
X15862Y6787D01*
G54D24*
X15962Y5437D02*
X15962Y6112D01*
X15862Y6212D02*
X15862Y6787D01*
X15962Y6112D02*
X15862Y6212D01*
X13175Y11834D02*
X13175Y14200D01*
X13150Y14225D02*
X13150Y14200D01*
X13175Y14200D02*
X13150Y14225D01*
X16375Y11625D02*
X16400Y11650D01*
X16400Y14175D02*
X16400Y14200D01*
X16400Y11650D02*
X16400Y14175D01*
X26375Y10425D02*
X26375Y12750D01*
X9775Y14200D02*
X13150Y14200D01*
X1375Y11475D02*
X4100Y14200D01*
X4100Y14200D02*
X9775Y14200D01*
X1375Y10325D02*
X1375Y11475D01*
X13150Y14200D02*
X15025Y14200D01*
X26375Y12750D02*
X24925Y14200D01*
X24925Y14200D02*
X16400Y14200D01*
X16400Y14200D02*
X14825Y14200D01*
X11025Y10850D02*
X12191Y10850D01*
X12191Y10850D02*
X13175Y11834D01*
G54D25*
X10100Y10696D02*
X10871Y10696D01*
X10871Y10696D02*
X11025Y10850D01*
X6225Y3399D02*
X6225Y5854D01*
X6225Y5854D02*
X5175Y5854D01*
X5175Y5854D02*
X5171Y5850D01*
X21450Y3799D02*
X21450Y5804D01*
X20546Y5800D02*
X21446Y5800D01*
X21446Y5800D02*
X21450Y5804D01*
X6600Y10221D02*
X6600Y11575D01*
X21825Y11525D02*
X21825Y9996D01*
X8000Y7021D02*
X8000Y7200D01*
X7600Y7600D02*
X7600Y8004D01*
X8000Y7200D02*
X7600Y7600D01*
X7600Y8004D02*
X8550Y8004D01*
X8550Y8004D02*
X8554Y8000D01*
X10400Y6275D02*
X9875Y6275D01*
X9721Y6429D02*
X9875Y6275D01*
X9721Y6429D02*
X8000Y6429D01*
X6225Y6446D02*
X6554Y6446D01*
X7579Y6429D02*
X8000Y6429D01*
X7375Y6225D02*
X7579Y6429D01*
X6775Y6225D02*
X7375Y6225D01*
X6554Y6446D02*
X6775Y6225D01*
X8000Y6429D02*
X8000Y6275D01*
X8000Y6275D02*
X8000Y6429D01*
X6225Y6446D02*
X6225Y7450D01*
X6600Y9629D02*
X6600Y8450D01*
X7604Y9625D02*
X6604Y9625D01*
X6604Y9625D02*
X6600Y9629D01*
X7600Y8596D02*
X7600Y9621D01*
X7600Y9621D02*
X7604Y9625D01*
X23150Y7021D02*
X23150Y7275D01*
X22825Y7600D02*
X22825Y7829D01*
X23150Y7275D02*
X22825Y7600D01*
X22825Y7829D02*
X23683Y7829D01*
X23683Y7829D02*
X23704Y7850D01*
X24725Y6050D02*
X25125Y5650D01*
X24346Y6429D02*
X24725Y6050D01*
X23150Y6429D02*
X24346Y6429D01*
X25125Y5650D02*
X25125Y5450D01*
X21450Y6396D02*
X21679Y6396D01*
X23150Y6425D02*
X23150Y6429D01*
X22775Y6425D02*
X23150Y6425D01*
X22600Y6250D02*
X22775Y6425D01*
X21825Y6250D02*
X22600Y6250D01*
X21679Y6396D02*
X21825Y6250D01*
X21450Y7400D02*
X21450Y6396D01*
X21825Y8400D02*
X21825Y9404D01*
X21825Y9404D02*
X21854Y9375D01*
X21854Y9375D02*
X22829Y9375D01*
X22829Y9375D02*
X22825Y9371D01*
X22825Y9371D02*
X22825Y8421D01*
G54D24*
X1375Y9325D02*
X4454Y9325D01*
X5050Y8725D02*
X5050Y9321D01*
X5050Y9321D02*
X5046Y9325D01*
X5050Y8446D02*
X5050Y8725D01*
X5850Y8725D02*
X5850Y8450D01*
X5050Y8725D02*
X5850Y8725D01*
X19579Y9325D02*
X19400Y9325D01*
X18100Y10625D02*
X19400Y9325D01*
X18100Y10625D02*
X16375Y10625D01*
X20171Y9325D02*
X20171Y8400D01*
X20171Y8400D02*
X21075Y8400D01*
X26375Y9425D02*
X27050Y9425D01*
X27425Y8625D02*
X27425Y8571D01*
X27425Y9050D02*
X27425Y8625D01*
X27050Y9425D02*
X27425Y9050D01*
G54D25*
X28375Y8850D02*
X28375Y7925D01*
X28375Y7925D02*
X28225Y7846D01*
G54D24*
X28225Y7846D02*
X28921Y7846D01*
X28925Y7850D02*
X28950Y7850D01*
X28921Y7846D02*
X28925Y7850D01*
X28225Y7846D02*
X27454Y7846D01*
X27425Y7875D02*
X27425Y7979D01*
X27454Y7846D02*
X27425Y7875D01*
X10225Y2475D02*
X10225Y2950D01*
X12175Y5550D02*
X12162Y5550D01*
X12175Y4900D02*
X12175Y5550D01*
X10225Y2950D02*
X12175Y4900D01*
X13750Y7900D02*
X14072Y8222D01*
X12162Y5487D02*
X12162Y5550D01*
X12162Y5550D02*
X12162Y6362D01*
X14072Y7147D02*
X14072Y8961D01*
X13650Y6725D02*
X14072Y7147D01*
X12525Y6725D02*
X13650Y6725D01*
X12162Y6362D02*
X12525Y6725D01*
X12162Y5487D02*
X13737Y5487D01*
X15062Y5437D02*
X13787Y5437D01*
X13787Y5437D02*
X13737Y5487D01*
X14072Y8222D02*
X14072Y8961D01*
X11225Y2475D02*
X12375Y2475D01*
G54D25*
X29700Y6850D02*
X29800Y6750D01*
X29800Y6750D02*
X29700Y6850D01*
G54D24*
X29825Y6725D02*
X30100Y6450D01*
X28225Y6800D02*
X28800Y6800D01*
X28850Y6800D02*
X28825Y6775D01*
X28800Y6800D02*
X28850Y6800D01*
X28225Y7254D02*
X28225Y6800D01*
X28225Y6800D02*
X28225Y6800D01*
X28225Y6800D02*
X28225Y6775D01*
G54D25*
X21075Y7400D02*
X20900Y7400D01*
G54D24*
X20900Y7400D02*
X20625Y7400D01*
G54D25*
X5850Y7450D02*
X5725Y7450D01*
G54D24*
X5050Y7475D02*
X5075Y7450D01*
X5075Y7450D02*
X5725Y7450D01*
X5050Y7475D02*
X5050Y7854D01*
X20625Y7400D02*
X20350Y7125D01*
X20350Y7125D02*
X20350Y7075D01*
G54D25*
X21825Y7400D02*
X21850Y7400D01*
X21850Y7400D02*
X22300Y6950D01*
X6600Y7450D02*
X6625Y7450D01*
X7125Y6925D02*
X7100Y6900D01*
X7150Y6925D02*
X7125Y6925D01*
X6625Y7450D02*
X7150Y6925D01*
G54D24*
X19954Y5800D02*
X19479Y5179D01*
X19479Y5179D02*
X19475Y5175D01*
X23421Y9375D02*
X24325Y9375D01*
X24296Y7850D02*
X25175Y7850D01*
X16962Y6787D02*
X16962Y7037D01*
X16962Y7037D02*
X17525Y7600D01*
X13737Y4387D02*
X13737Y3437D01*
X13737Y3437D02*
X13625Y3325D01*
X11025Y9750D02*
X11025Y8900D01*
X9146Y8000D02*
X10000Y8000D01*
X8196Y9625D02*
X8875Y9625D01*
X5050Y7854D02*
X5050Y7375D01*
X5050Y7375D02*
X4950Y7275D01*
X4579Y5850D02*
X3825Y5850D01*
G54D25*
X16608Y7712D02*
X16962Y7358D01*
X16962Y7358D02*
X16962Y6787D01*
G54D24*
X16862Y5437D02*
X16962Y5537D01*
X16962Y5537D02*
X16962Y6787D01*
X12278Y8961D02*
X12278Y8272D01*
X12278Y8272D02*
X12650Y7900D01*
X11025Y9750D02*
X11489Y9750D01*
X11489Y9750D02*
X12278Y8961D01*
G54D25*
X10100Y10104D02*
X10454Y9750D01*
X10454Y9750D02*
X11025Y9750D01*
G54D24*
X20175Y7804D02*
X20175Y7750D01*
X20525Y7400D02*
X20625Y7400D01*
X20175Y7750D02*
X20525Y7400D01*
G54D25*
X29325Y6850D02*
X29325Y3475D01*
X29700Y11925D02*
X29700Y7850D01*
M02*

View File

@@ -1,77 +0,0 @@
M48
;DRILL file {Pcbnew (2013-07-07 BZR 4022)-stable} date 11/20/2013 1:52:19 PM
;FORMAT={2:4/ absolute / inch / keep zeros}
FMAT,2
INCH,TZ
T1C0.030
T2C0.040
T3C0.060
%
G90
G05
M72
T1
X010400Y006275
X025125Y005450
T2
X001375Y010325
X001375Y009325
X001375Y008325
X003825Y005850
X004950Y007275
X007100Y006900
X008875Y009625
X010000Y008000
X010225Y002475
X011025Y008900
X011225Y002475
X012375Y002475
X013625Y003325
X016375Y011625
X016375Y010625
X016375Y009625
X017525Y007600
X019475Y005175
X020350Y007075
X022300Y006950
X024325Y009375
X025175Y007850
X026375Y010425
X026375Y009425
X026375Y008425
X028225Y006775
X028375Y008850
X030075Y006475
T3
X005225Y004475
X005225Y002475
X005600Y012925
X005600Y010925
X006225Y003475
X006600Y011925
X007225Y004475
X007225Y002475
X007600Y012925
X007600Y010925
X020450Y004475
X020450Y002475
X020825Y012925
X020825Y010925
X021450Y003475
X021825Y011925
X022450Y004475
X022450Y002475
X022825Y012925
X022825Y010925
X028325Y004475
X028325Y002475
X028700Y012925
X028700Y010925
X029325Y003475
X029700Y011925
X030325Y004475
X030325Y002475
X030700Y012925
X030700Y010925
T0
M30