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 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 2/5/2014 #
# MIT Licence #
############################################################
import sys
from PyQt4 import QtGui
from FlatCAMApp import App
from gi.repository import Gtk
from FlatCAMApp import *
def debug_trace():
'''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()
Gtk.main()
debug_trace()
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 @@
############################################################
# 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 PyQt4 import QtCore
from ObjectUI import *
class LoudDict(dict):
"""
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)
import FlatCAMApp
import inspect # TODO: For debugging only.
from camlib import *
from FlatCAMCommon import LoudDict
########################################
## FlatCAMObj ##
########################################
class FlatCAMObj(GObject.GObject, object):
class FlatCAMObj(QtCore.QObject):
"""
Base type of objects handled in FlatCAM. These become interactive
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.
app = None
def __init__(self, name, ui):
def __init__(self, name):
"""
:param name: Name of the object given by the user.
@ -79,53 +28,60 @@ class FlatCAMObj(GObject.GObject, object):
:type ui: ObjectUI
:return: FlatCAMObj
"""
GObject.GObject.__init__(self)
QtCore.QObject.__init__(self)
# View
self.ui = ui
self.ui = None
self.options = LoudDict(name=name)
self.options.set_change_callback(self.on_options_change)
self.form_fields = {"name": self.ui.name_entry}
self.radios = {} # Name value pairs for radio sets
self.radios_inv = {} # Inverse of self.radios
self.form_fields = {}
self.axes = None # Matplotlib axes
self.kind = None # Override with proper name
self.muted_ui = False
self.ui.name_entry.connect('activate', self.on_name_activate)
self.ui.offset_button.connect('clicked', self.on_offset_button_click)
self.ui.offset_button.connect('activate', self.on_offset_button_click)
self.ui.scale_button.connect('clicked', self.on_scale_button_click)
self.ui.scale_button.connect('activate', self.on_scale_button_click)
# 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 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):
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"])
new_name = self.ui.name_entry.get_text()
self.options["name"] = self.ui.name_entry.get_text()
new_name = self.ui.name_entry.get_value()
self.options["name"] = self.ui.name_entry.get_value()
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()
vect = self.ui.offsetvector_entry.get_value()
self.offset(vect)
self.plot()
def on_scale_button_click(self, *args):
def on_scale_button_click(self):
self.read_form()
factor = self.ui.scale_entry.get_value()
self.scale(factor)
self.plot()
def on_options_change(self, key):
self.form_fields[key].set_value(self.options[key])
return
def setup_axes(self, figure):
"""
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
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
box_children = box_selected.get_children()
for child in box_children:
box_selected.remove(child)
# box_children = self.app.ui.notebook.selected_contents.get_children()
# for child in box_children:
# 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
# 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()
GLib.idle_add(box_selected.show_all)
GLib.idle_add(self.ui.show_all)
self.muted_ui = False
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.
:rtype: bool
"""
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()")
# Axes must exist and be attached to canvas.
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
# 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)
return True
@ -284,29 +244,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
Represents Gerber code.
"""
ui_type = GerberObjectUI
def __init__(self, name):
Gerber.__init__(self)
FlatCAMObj.__init__(self, name, GerberObjectUI())
FlatCAMObj.__init__(self, name)
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
# Automatically updates the UI
self.options.update({
@ -331,21 +276,45 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
# from predecessors.
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)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click)
self.ui.solid_cb.connect('activate', self.on_solid_cb_click)
self.ui.multicolored_cb.connect('clicked', self.on_multicolored_cb_click)
self.ui.multicolored_cb.connect('activate', self.on_multicolored_cb_click)
self.ui.generate_iso_button.connect('clicked', self.on_iso_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)
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 on_generatenoncopper_button_click(self, *args):
self.read_form()
@ -478,17 +447,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
def plot(self):
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMGerber.plot()")
# Does all the required setup and returns False
# if the 'ptint' option is set to False.
if not FlatCAMObj.plot(self):
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
# Make sure geometry is iterable.
@ -523,8 +488,9 @@ class FlatCAMGerber(FlatCAMObj, Gerber):
x, y = ints.coords.xy
self.axes.plot(x, y, linespec)
# self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
self.app.plotcanvas.auto_adjust_axes()
#GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
#self.emit(QtCore.SIGNAL("plotChanged"), self)
def serialize(self):
return {
@ -538,29 +504,21 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
Represents Excellon/Drill code.
"""
ui_type = ExcellonObjectUI
def __init__(self, name):
Excellon.__init__(self)
FlatCAMObj.__init__(self, name, ExcellonObjectUI())
FlatCAMObj.__init__(self, name)
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({
"plot": True,
"solid": False,
"drillz": -0.1,
"travelz": 0.1,
"feedrate": 5.0,
"toolselection": ""
# "toolselection": ""
})
# TODO: Document this.
@ -571,49 +529,100 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
# from predecessors.
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)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.solid_cb.connect('clicked', self.on_solid_cb_click)
self.ui.solid_cb.connect('activate', self.on_solid_cb_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)
self.ui.plot_cb.stateChanged.connect(self.on_plot_cb_click)
self.ui.solid_cb.stateChanged.connect(self.on_solid_cb_click)
# self.ui.choose_tools_button.clicked.connect(self.show_tool_chooser)
self.ui.generate_cnc_button.clicked.connect(self.on_create_cncjob_button_click)
def on_create_cncjob_button_click(self, *args):
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"
# Object initialization function for app.new_object()
def job_init(job_obj, app_obj):
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_move = self.options["travelz"]
job_obj.feedrate = self.options["feedrate"]
# There could be more than one drill size...
# job_obj.tooldia = # TODO: duplicate variable!
# 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()
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()
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
def job_thread(app_obj):
app_obj.new_object("cncjob", job_name, job_init)
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, ""))
# GLib.idle_add(lambda: app_obj.set_progress_bar(1.0, "Done!"))
app_obj.progress.emit(100)
# GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, ""))
# 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):
if self.muted_ui:
@ -663,32 +672,34 @@ class FlatCAMExcellon(FlatCAMObj, Excellon):
x, y = ints.coords.xy
self.axes.plot(x, y, 'g-')
#self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
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):
win = Gtk.Window()
box = Gtk.Box(spacing=2)
box.set_orientation(Gtk.Orientation(1))
win.add(box)
for tool in self.tools:
self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + str(self.tools[tool]))
box.pack_start(self.tool_cbs[tool], False, False, 1)
button = Gtk.Button(label="Accept")
box.pack_start(button, False, False, 1)
win.show_all()
def on_accept(widget):
win.destroy()
tool_list = []
for toolx in self.tool_cbs:
if self.tool_cbs[toolx].get_active():
tool_list.append(toolx)
self.options["toolselection"] = ", ".join(tool_list)
self.to_form()
button.connect("activate", on_accept)
button.connect("clicked", on_accept)
# win = Gtk.Window()
# box = Gtk.Box(spacing=2)
# box.set_orientation(Gtk.Orientation(1))
# win.add(box)
# for tool in self.tools:
# self.tool_cbs[tool] = Gtk.CheckButton(label=tool + ": " + str(self.tools[tool]))
# box.pack_start(self.tool_cbs[tool], False, False, 1)
# button = Gtk.Button(label="Accept")
# box.pack_start(button, False, False, 1)
# win.show_all()
#
# def on_accept(widget):
# win.destroy()
# tool_list = []
# for toolx in self.tool_cbs:
# if self.tool_cbs[toolx].get_active():
# tool_list.append(toolx)
# self.options["toolselection"] = ", ".join(tool_list)
# self.to_form()
#
# button.connect("activate", on_accept)
# button.connect("clicked", on_accept)
return
class FlatCAMCNCjob(FlatCAMObj, CNCjob):
@ -696,23 +707,21 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
Represents G-Code.
"""
ui_type = CNCObjectUI
def __init__(self, name, units="in", kind="generic", z_move=0.1,
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,
feedrate=feedrate, z_cut=z_cut, tooldia=tooldia)
FlatCAMObj.__init__(self, name, CNCObjectUI())
FlatCAMObj.__init__(self, name)
self.kind = "cncjob"
self.options.update({
"plot": True,
"tooldia": 0.4 / 25.4 # 0.4mm in inches
})
self.form_fields.update({
"name": self.ui.name_entry,
"plot": self.ui.plot_cb,
"tooldia": self.ui.tooldia_entry
"tooldia": 0.4 / 25.4, # 0.4mm in inches
"append": ""
})
# Attributes to be included in serialization
@ -720,25 +729,47 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
# from predecessors.
self.ser_attrs += ['options', 'kind']
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.updateplot_button.connect('clicked', self.on_updateplot_button_click)
self.ui.updateplot_button.connect('activate', self.on_updateplot_button_click)
self.ui.export_gcode_button.connect('clicked', self.on_exportgcode_button_click)
self.ui.export_gcode_button.connect('activate', self.on_exportgcode_button_click)
def set_ui(self, ui):
FlatCAMObj.set_ui(self, ui)
FlatCAMApp.App.log.debug("FlatCAMCNCJob.set_ui()")
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):
"""
Callback for the "Updata Plot" button. Reads the form for updates
and plots the object.
"""
self.read_form()
self.plot()
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):
if self.muted_ui:
@ -755,8 +786,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob):
self.plot2(self.axes, tooldia=self.options["tooldia"])
#self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
self.app.plotcanvas.auto_adjust_axes()
def convert_units(self, units):
factor = CNCjob.convert_units(self, units)
@ -770,26 +800,14 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
format.
"""
ui_type = GeometryObjectUI
def __init__(self, name):
FlatCAMObj.__init__(self, name, GeometryObjectUI())
FlatCAMObj.__init__(self, name)
Geometry.__init__(self)
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({
"plot": True,
# "solid": False,
@ -803,31 +821,34 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
"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
# Always append to it because it carries contents
# from predecessors.
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)
self.ui.plot_cb.connect('clicked', self.on_plot_cb_click)
self.ui.plot_cb.connect('activate', self.on_plot_cb_click)
self.ui.generate_cnc_button.connect('clicked', self.on_generatecnc_button_click)
self.ui.generate_cnc_button.connect('activate', self.on_generatecnc_button_click)
self.ui.generate_paint_button.connect('clicked', self.on_paint_button_click)
self.ui.generate_paint_button.connect('activate', self.on_paint_button_click)
self.form_fields.update({
"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.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):
self.app.info("Click inside the desired polygon.")
@ -868,35 +889,41 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
# Propagate options
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_move = self.options["travelz"]
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.
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()
# 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..."))
#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
def job_thread(app_obj):
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.set_progress_bar(1.0, "Done!"))
GLib.timeout_add_seconds(1, lambda: app_obj.set_progress_bar(0.0, "Idle"))
# 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.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
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:
return
self.read_form_item('plot')
@ -994,5 +1021,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry):
FlatCAMApp.App.log.warning("Did not plot:", str(type(geo)))
#self.app.plotcanvas.auto_adjust_axes()
GLib.idle_add(self.app.plotcanvas.auto_adjust_axes)
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 @@
############################################################
# 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
from PyQt4 import QtCore
#import Queue
import FlatCAMApp
class Worker(threading.Thread):
class Worker(QtCore.QObject):
"""
Implements a queue of tasks to be carried out in order
in a single independent thread.
"""
def __init__(self):
def __init__(self, app, name=None):
super(Worker, self).__init__()
self.queue = Queue.Queue()
self.stoprequest = threading.Event()
self.app = app
self.name = name
def run(self):
while not self.stoprequest.isSet():
try:
task = self.queue.get(True, 0.05)
self.do_task(task)
except Queue.Empty:
continue
FlatCAMApp.App.log.debug("Worker Started!")
self.app.worker_task.connect(self.do_worker_task)
@staticmethod
def do_task(task):
task['fcn'](*task['params'])
return
def do_worker_task(self, task):
FlatCAMApp.App.log.debug("Running task: %s" % str(task))
if 'worker_name' in task and task['worker_name'] == self.name:
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()
if 'worker_name' not in task and self.name is None:
task['fcn'](*task['params'])
return

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 @@
############################################################
# 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 PyQt4 import QtGui, QtCore
from copy import copy
import FlatCAMApp
import re
class RadioSet(Gtk.Box):
def __init__(self, choices):
class RadioSet(QtGui.QWidget):
def __init__(self, choices, orientation='horizontal', parent=None):
"""
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.
:type choices: list
"""
Gtk.Box.__init__(self)
super(RadioSet, self).__init__(parent)
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:
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)
choice['radio'] = QtGui.QRadioButton(choice['label'])
group.addButton(choice['radio'])
layout.addWidget(choice['radio'], stretch=0)
choice['radio'].toggled.connect(self.on_toggle)
self.group_toggle_fn = lambda x, y: None
layout.addStretch()
self.setLayout(layout)
def on_toggle(self, btn):
if btn.get_active():
self.group_toggle_fn(btn, self.get_value)
self.group_toggle_fn = lambda: None
def on_toggle(self):
FlatCAMApp.App.log.debug("Radio toggled")
radio = self.sender()
if radio.isChecked():
self.group_toggle_fn()
return
def get_value(self):
for choice in self.choices:
if choice['radio'].get_active():
if choice['radio'].isChecked():
return choice['value']
FlatCAMApp.App.log.error("No button was toggled in RadioSet.")
return None
@ -52,33 +53,15 @@ class RadioSet(Gtk.Box):
def set_value(self, val):
for choice in self.choices:
if choice['value'] == val:
choice['radio'].set_active(True)
choice['radio'].setChecked(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.
class LengthEntry(QtGui.QLineEdit):
def __init__(self, output_units='IN', parent=None):
super(LengthEntry, self).__init__(parent)
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]+))?$")
@ -90,40 +73,22 @@ class LengthEntry(Gtk.Entry):
'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.
"""
def returnPressed(self, *args, **kwargs):
val = self.get_value()
if val is not None:
self.set_text(str(val))
self.set_text(QtCore.QString(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(' ')
raw = str(self.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()]
return float(eval(match.group(1)))*float(self.scales[self.output_units][match.group(2).upper()])
else:
return float(eval(match.group(1)))
except:
@ -131,24 +96,22 @@ class LengthEntry(Gtk.Entry):
return None
def set_value(self, val):
self.set_text(str(val))
self.setText(QtCore.QString(str(val)))
class FloatEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
class FloatEntry(QtGui.QLineEdit):
def __init__(self, parent=None):
super(FloatEntry, self).__init__(parent)
self.connect('activate', self.on_activate)
def on_activate(self, *args):
def returnPressed(self, *args, **kwargs):
val = self.get_value()
if val is not None:
self.set_text(str(val))
self.set_text(QtCore.QString(str(val)))
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):
raw = self.get_text().strip(' ')
raw = str(self.text()).strip(' ')
try:
evaled = eval(raw)
except:
@ -158,44 +121,44 @@ class FloatEntry(Gtk.Entry):
return float(evaled)
def set_value(self, val):
self.set_text(str(val))
self.setText("%.6f"%val)
class IntEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
class IntEntry(QtGui.QLineEdit):
def __init__(self, parent=None):
super(IntEntry, self).__init__(parent)
def get_value(self):
return int(self.get_text())
return int(self.text())
def set_value(self, val):
self.set_text(str(val))
self.setText(QtCore.QString(str(val)))
class FCEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
class FCEntry(QtGui.QLineEdit):
def __init__(self, parent=None):
super(FCEntry, self).__init__(parent)
def get_value(self):
return self.get_text()
return str(self.text())
def set_value(self, val):
self.set_text(str(val))
self.setText(QtCore.QString(str(val)))
class EvalEntry(Gtk.Entry):
def __init__(self):
Gtk.Entry.__init__(self)
class EvalEntry(QtGui.QLineEdit):
def __init__(self, parent=None):
super(EvalEntry, self).__init__(parent)
def on_activate(self, *args):
def returnPressed(self, *args, **kwargs):
val = self.get_value()
if val is not None:
self.set_text(str(val))
self.setText(QtCore.QString(str(val)))
else:
FlatCAMApp.App.log.warning("Could not interpret entry: %s" % self.get_text())
def get_value(self):
raw = self.get_text().strip(' ')
raw = str(self.text()).strip(' ')
try:
return eval(raw)
except:
@ -203,15 +166,65 @@ class EvalEntry(Gtk.Entry):
return None
def set_value(self, val):
self.set_text(str(val))
self.setText(QtCore.QString(str(val)))
class FCCheckBox(Gtk.CheckButton):
def __init__(self, label=''):
Gtk.CheckButton.__init__(self, label=label)
class FCCheckBox(QtGui.QCheckBox):
def __init__(self, label='', parent=None):
super(FCCheckBox, self).__init__(QtCore.QString(label), parent)
def get_value(self):
return self.get_active()
return self.isChecked()
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 @@
############################################################
# FlatCAM: 2D Post-processing for Manufacturing #
# http://caram.cl/software/flatcam #
# Author: Juan Pablo Caram (c) #
# Date: 4/20/2014 #
# MIT Licence #
############################################################
from PyQt4.QtCore import QModelIndex
from FlatCAMObj import *
from gi.repository import Gtk, GdkPixbuf
import inspect # TODO: Remove
import FlatCAMApp
from PyQt4 import Qt, QtGui, QtCore
class ObjectCollection:
class ObjectCollection(QtCore.QAbstractListModel):
classdict = {
"gerber": FlatCAMGerber,
@ -28,130 +21,51 @@ class ObjectCollection:
"geometry": "share/geometry16.png"
}
def __init__(self):
def __init__(self, parent=None):
QtCore.QAbstractListModel.__init__(self, parent=parent)
### 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])
self.icons[kind] = QtGui.QPixmap(ObjectCollection.icon_files[kind])
### GUI List components
## Model
self.store = Gtk.ListStore(FlatCAMObj)
self.object_list = []
## 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)
self.view = QtGui.QListView()
self.view.setModel(self)
self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
self.view.activated.connect(self.on_item_activated)
## Renderers
# Icon
renderer_pixbuf = Gtk.CellRendererPixbuf()
column_pixbuf = Gtk.TreeViewColumn("Type", renderer_pixbuf)
def rowCount(self, parent=QtCore.QModelIndex(), *args, **kwargs):
return len(self.object_list)
def _set_cell_icon(column, cell, model, it, data):
obj = model.get_value(it, 0)
cell.set_property('pixbuf', self.icons[obj.kind])
def columnCount(self, *args, **kwargs):
return 1
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 data(self, index, role=Qt.Qt.DisplayRole):
if not index.isValid() or not 0 <= index.row() < self.rowCount():
return QtCore.QVariant()
row = index.row()
if role == Qt.Qt.DisplayRole:
return self.object_list[row].options["name"]
if role == Qt.Qt.DecorationRole:
return self.icons[self.object_list[row].kind]
def print_list(self):
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
for obj in self.object_list:
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):
"""
Add a FlatCAMObj the the collection. This method is thread-safe.
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
: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()")
obj.set_ui(obj.ui_type())
def guitask():
self.store.append([obj])
if active:
self.set_list_selection(obj.options["name"])
GLib.idle_add(guitask)
# Required before appending
self.beginInsertRows(QtCore.QModelIndex(), len(self.object_list), len(self.object_list))
self.object_list.append(obj)
# Required after appending
self.endInsertRows()
def get_names(self):
"""
@ -160,14 +74,9 @@ class ObjectCollection:
: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
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.get_names()")
return [x.options['name'] for x in self.object_list]
def get_bounds(self):
"""
@ -185,9 +94,7 @@ class ObjectCollection:
xmax = -Inf
ymax = -Inf
iterat = self.store.get_iter_first()
while iterat is not None:
obj = self.store[iterat][0]
for obj in self.object_list:
try:
gxmin, gymin, gxmax, gymax = obj.bounds()
xmin = min([xmin, gxmin])
@ -196,25 +103,9 @@ class ObjectCollection:
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`.
@ -226,33 +117,57 @@ class ObjectCollection:
"""
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:
for obj in self.object_list:
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
def delete_active(self):
selections = self.view.selectedIndexes()
if len(selections) == 0:
return
row = selections[0].row()
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
self.object_list.pop(row)
self.endRemoveRows()
def get_active(self):
selections = self.view.selectedIndexes()
if len(selections) == 0:
return None
row = selections[0].row()
return self.object_list[row]
def set_active(self, name):
iobj = self.createIndex(self.get_names().index(name))
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 @@
############################################################
# 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 sys
from PyQt4 import QtGui, QtCore
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'):
Gtk.VBox.__init__(self, spacing=3, margin=5, vexpand=False)
def __init__(self, icon_file='share/flatcam_icon32.png', title='FlatCAM Object', parent=None):
QtGui.QWidget.__init__(self, parent=parent)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
## 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)
self.title_box = QtGui.QHBoxLayout()
layout.addLayout(self.title_box)
## 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)
pixmap = QtGui.QPixmap(icon_file)
self.icon = QtGui.QLabel()
self.icon.setPixmap(pixmap)
self.title_box.addWidget(self.icon, stretch=0)
## 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)
self.title_label = QtGui.QLabel("<font size=5><b>" + title + "</b></font>")
self.title_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.title_box.addWidget(self.title_label, stretch=1)
## 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_box = QtGui.QHBoxLayout()
layout.addLayout(self.name_box)
name_label = QtGui.QLabel("Name:")
self.name_box.addWidget(name_label)
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
self.custom_box = Gtk.VBox(spacing=3, margin=0, vexpand=False)
self.pack_start(self.custom_box, expand=False, fill=False, padding=0)
self.custom_box = QtGui.QVBoxLayout()
layout.addLayout(self.custom_box)
## 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(
self.scale_label = QtGui.QLabel('<b>Scale:</b>')
self.scale_label.setToolTip(
"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)
self.pack_start(grid5, expand=False, fill=False, padding=2)
grid1 = QtGui.QGridLayout()
layout.addLayout(grid1)
# Factor
l10 = Gtk.Label('Factor:', xalign=1)
l10.set_tooltip_markup(
faclabel = QtGui.QLabel('Factor:')
faclabel.setToolTip(
"Factor by which to multiply\n"
"geometric features of this object."
)
grid5.attach(l10, 0, 0, 1, 1)
grid1.addWidget(faclabel, 0, 0)
self.scale_entry = FloatEntry()
self.scale_entry.set_text("1.0")
grid5.attach(self.scale_entry, 1, 0, 1, 1)
self.scale_entry.set_value(1.0)
grid1.addWidget(self.scale_entry, 0, 1)
# GO Button
self.scale_button = Gtk.Button(label='Scale')
self.scale_button.set_tooltip_markup(
self.scale_button = QtGui.QPushButton('Scale')
self.scale_button.setToolTip(
"Perform scaling operation."
)
self.pack_start(self.scale_button, expand=False, fill=False, padding=2)
layout.addWidget(self.scale_button)
## 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(
self.offset_label = QtGui.QLabel('<b>Offset:</b>')
self.offset_label.setToolTip(
"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)
self.pack_start(grid6, expand=False, fill=False, padding=2)
grid2 = QtGui.QGridLayout()
layout.addLayout(grid2)
# Vector
l11 = Gtk.Label('Offset Vector:', xalign=1)
l11.set_tooltip_markup(
self.offset_label = QtGui.QLabel('Vector:')
self.offset_label.setToolTip(
"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)
grid2.addWidget(self.offset_label, 0, 0)
self.offsetvector_entry = EvalEntry()
self.offsetvector_entry.set_text("(0.0, 0.0)")
grid6.attach(self.offsetvector_entry, 1, 0, 1, 1)
self.offsetvector_entry.setText("(0.0, 0.0)")
grid2.addWidget(self.offsetvector_entry, 0, 1)
self.offset_button = Gtk.Button(label='Scale')
self.offset_button.set_tooltip_markup(
self.offset_button = QtGui.QPushButton('Offset')
self.offset_button.setToolTip(
"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):
getattr(self, name).set_value(value)
def get_field(self, name):
return getattr(self, name).get_value()
layout.addStretch()
class CNCObjectUI(ObjectUI):
@ -116,57 +105,68 @@ class CNCObjectUI(ObjectUI):
User interface for CNCJob objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png')
def __init__(self, parent=None):
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png', parent=parent)
## 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)
self.plot_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.custom_box.addWidget(self.plot_options_label)
grid0 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid0, expand=False, fill=False, padding=2)
grid0 = QtGui.QGridLayout()
self.custom_box.addLayout(grid0)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
# self.plot_cb = QtGui.QCheckBox('Plot')
self.plot_cb = FCCheckBox('Plot')
self.plot_cb.setToolTip(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 2, 1)
grid0.addWidget(self.plot_cb, 0, 0)
# Tool dia for plot
l1 = Gtk.Label('Tool dia:', xalign=1)
l1.set_tooltip_markup(
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"Diameter of the tool to be\n"
"rendered in the plot."
)
grid0.attach(l1, 0, 1, 1, 1)
grid0.addWidget(tdlabel, 1, 0)
self.tooldia_entry = LengthEntry()
grid0.attach(self.tooldia_entry, 1, 1, 1, 1)
grid0.addWidget(self.tooldia_entry, 1, 1)
# Update plot button
self.updateplot_button = Gtk.Button(label='Update Plot')
self.updateplot_button.set_tooltip_markup(
self.updateplot_button = QtGui.QPushButton('Update Plot')
self.updateplot_button.setToolTip(
"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
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(
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.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
self.export_gcode_button = Gtk.Button(label='Export G-Code')
self.export_gcode_button.set_tooltip_markup(
self.export_gcode_button = QtGui.QPushButton('Export G-Code')
self.export_gcode_button.setToolTip(
"Opens dialog to save G-Code\n"
"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):
@ -174,135 +174,131 @@ class GeometryObjectUI(ObjectUI):
User interface for Geometry objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Geometry Object', icon_file='share/geometry32.png')
def __init__(self, parent=None):
super(GeometryObjectUI, self).__init__(title='Geometry Object', icon_file='share/geometry32.png', parent=parent)
## 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_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.custom_box.addWidget(self.plot_options_label)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
self.plot_cb.setToolTip(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 1, 1)
self.custom_box.addWidget(self.plot_cb)
## 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(
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.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)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
grid1 = QtGui.QGridLayout()
self.custom_box.addLayout(grid1)
# Cut Z
l1 = Gtk.Label('Cut Z:', xalign=1)
l1.set_tooltip_markup(
cutzlabel = QtGui.QLabel('Cut Z:')
cutzlabel.setToolTip(
"Cutting depth (negative)\n"
"below the copper surface."
)
grid1.attach(l1, 0, 0, 1, 1)
grid1.addWidget(cutzlabel, 0, 0)
self.cutz_entry = LengthEntry()
grid1.attach(self.cutz_entry, 1, 0, 1, 1)
grid1.addWidget(self.cutz_entry, 0, 1)
# Travel Z
l2 = Gtk.Label('Travel Z:', xalign=1)
l2.set_tooltip_markup(
travelzlabel = QtGui.QLabel('Travel Z:')
travelzlabel.setToolTip(
"Height of the tool when\n"
"moving without cutting."
)
grid1.attach(l2, 0, 1, 1, 1)
grid1.addWidget(travelzlabel, 1, 0)
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)
l3.set_tooltip_markup(
# Feedrate
frlabel = QtGui.QLabel('Feed Rate:')
frlabel.setToolTip(
"Cutting speed in the XY\n"
"plane in units per minute"
)
grid1.attach(l3, 0, 2, 1, 1)
grid1.addWidget(frlabel, 2, 0)
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)
l4.set_tooltip_markup(
# Tooldia
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"The diameter of the cutting\n"
"tool (just for display)."
)
grid1.attach(l4, 0, 3, 1, 1)
grid1.addWidget(tdlabel, 3, 0)
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.set_tooltip_markup(
self.generate_cnc_button = QtGui.QPushButton('Generate')
self.generate_cnc_button.setToolTip(
"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
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(
## 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.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)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
grid2 = QtGui.QGridLayout()
self.custom_box.addLayout(grid2)
# Tool dia
l5 = Gtk.Label('Tool dia:', xalign=1)
l5.set_tooltip_markup(
ptdlabel = QtGui.QLabel('Tool dia:')
ptdlabel.setToolTip(
"Diameter of the tool to\n"
"be used in the operation."
)
grid2.attach(l5, 0, 0, 1, 1)
grid2.addWidget(ptdlabel, 0, 0)
self.painttooldia_entry = LengthEntry()
grid2.attach(self.painttooldia_entry, 1, 0, 1, 1)
grid2.addWidget(self.painttooldia_entry, 0, 1)
# Overlap
l6 = Gtk.Label('Overlap:', xalign=1)
l6.set_tooltip_markup(
ovlabel = QtGui.QLabel('Overlap:')
ovlabel.setToolTip(
"How much (fraction) of the tool\n"
"width to overlap each tool pass."
)
grid2.attach(l6, 0, 1, 1, 1)
grid2.addWidget(ovlabel, 1, 0)
self.paintoverlap_entry = LengthEntry()
grid2.attach(self.paintoverlap_entry, 1, 1, 1, 1)
grid2.addWidget(self.paintoverlap_entry, 1, 1)
# Margin
l7 = Gtk.Label('Margin:', xalign=1)
l7.set_tooltip_markup(
marginlabel = QtGui.QLabel('Margin:')
marginlabel.setToolTip(
"Distance by which to avoid\n"
"the edges of the polygon to\n"
"be painted."
)
grid2.attach(l7, 0, 2, 1, 1)
grid2.addWidget(marginlabel, 2, 0)
self.paintmargin_entry = LengthEntry()
grid2.attach(self.paintmargin_entry, 1, 2, 1, 1)
grid2.addWidget(self.paintmargin_entry)
# GO Button
self.generate_paint_button = Gtk.Button(label='Generate')
self.generate_paint_button.set_tooltip_markup(
self.generate_paint_button = QtGui.QPushButton('Generate')
self.generate_paint_button.setToolTip(
"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)
self.custom_box.addWidget(self.generate_paint_button)
class ExcellonObjectUI(ObjectUI):
@ -310,90 +306,85 @@ class ExcellonObjectUI(ObjectUI):
User interface for Excellon objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png')
def __init__(self, parent=None):
ObjectUI.__init__(self, title='Excellon Object', icon_file='share/drill32.png', parent=parent)
## 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_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.custom_box.addWidget(self.plot_options_label)
grid0 = QtGui.QGridLayout()
self.custom_box.addLayout(grid0)
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
self.plot_cb.setToolTip(
"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.set_tooltip_markup(
self.solid_cb.setToolTip(
"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
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(
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.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)
self.custom_box.pack_start(grid1, expand=True, fill=False, padding=2)
grid1 = QtGui.QGridLayout()
self.custom_box.addLayout(grid1)
l1 = Gtk.Label('Cut Z:', xalign=1)
l1.set_tooltip_markup(
cutzlabel = QtGui.QLabel('Cut Z:')
cutzlabel.setToolTip(
"Drill depth (negative)\n"
"below the copper surface."
)
grid1.attach(l1, 0, 0, 1, 1)
grid1.addWidget(cutzlabel, 0, 0)
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)
l2.set_tooltip_markup(
travelzlabel = QtGui.QLabel('Travel Z:')
travelzlabel.setToolTip(
"Tool height when travelling\n"
"across the XY plane."
)
grid1.attach(l2, 0, 1, 1, 1)
grid1.addWidget(travelzlabel, 1, 0)
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)
l3.set_tooltip_markup(
frlabel = QtGui.QLabel('Feed rate:')
frlabel.setToolTip(
"Tool speed while drilling\n"
"(in units per minute)."
)
grid1.attach(l3, 0, 2, 1, 1)
grid1.addWidget(frlabel, 2, 0)
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)
l4.set_tooltip_markup(
"Which tools to include\n"
"in the CNC Job."
choose_tools_label = QtGui.QLabel(
"Select from the tools section above\n"
"the tools you want to include."
)
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.custom_box.addWidget(choose_tools_label)
self.generate_cnc_button = Gtk.Button(label='Generate')
self.generate_cnc_button.set_tooltip_markup(
self.generate_cnc_button = QtGui.QPushButton('Generate')
self.generate_cnc_button.setToolTip(
"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):
@ -401,214 +392,210 @@ class GerberObjectUI(ObjectUI):
User interface for Gerber objects.
"""
def __init__(self):
ObjectUI.__init__(self, title='Gerber Object')
def __init__(self, parent=None):
ObjectUI.__init__(self, title='Gerber Object', parent=parent)
## 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_options_label = QtGui.QLabel("<b>Plot Options:</b>")
self.custom_box.addWidget(self.plot_options_label)
grid0 = QtGui.QGridLayout()
self.custom_box.addLayout(grid0)
# Plot CB
self.plot_cb = FCCheckBox(label='Plot')
self.plot_cb.set_tooltip_markup(
self.plot_options_label.setToolTip(
"Plot (show) this object."
)
grid0.attach(self.plot_cb, 0, 0, 1, 1)
grid0.addWidget(self.plot_cb, 0, 0)
# Solid CB
self.solid_cb = FCCheckBox(label='Solid')
self.solid_cb.set_tooltip_markup(
self.solid_cb.setToolTip(
"Solid color polygons."
)
grid0.attach(self.solid_cb, 1, 0, 1, 1)
grid0.addWidget(self.solid_cb, 0, 1)
# Multicolored CB
self.multicolored_cb = FCCheckBox(label='Multicolored')
self.multicolored_cb.set_tooltip_markup(
self.multicolored_cb.setToolTip(
"Draw polygons in different colors."
)
grid0.attach(self.multicolored_cb, 2, 0, 1, 1)
grid0.addWidget(self.multicolored_cb, 0, 2)
## 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(
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.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)
self.custom_box.pack_start(grid, expand=True, fill=False, padding=2)
l1 = Gtk.Label('Tool diam:', xalign=1)
l1.set_tooltip_markup(
grid1 = QtGui.QGridLayout()
self.custom_box.addLayout(grid1)
tdlabel = QtGui.QLabel('Tool dia:')
tdlabel.setToolTip(
"Diameter of the cutting tool."
)
grid.attach(l1, 0, 0, 1, 1)
grid1.addWidget(tdlabel, 0, 0)
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)
l2.set_tooltip_markup(
passlabel = QtGui.QLabel('Width (# passes):')
passlabel.setToolTip(
"Width of the isolation gap in\n"
"number (integer) of tool widths."
)
grid.attach(l2, 0, 1, 1, 1)
grid1.addWidget(passlabel, 1, 0)
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)
l3.set_tooltip_markup(
overlabel = QtGui.QLabel('Pass overlap:')
overlabel.setToolTip(
"How much (fraction of tool width)\n"
"to overlap each pass."
)
grid.attach(l3, 0, 2, 1, 1)
grid1.addWidget(overlabel, 2, 0)
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.set_tooltip_markup(
self.generate_iso_button = QtGui.QPushButton('Generate Geometry')
self.generate_iso_button.setToolTip(
"Create the Geometry Object\n"
"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
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(
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.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)
self.custom_box.pack_start(grid2, expand=True, fill=False, padding=2)
l4 = Gtk.Label('Tool dia:', xalign=1)
l4.set_tooltip_markup(
grid2 = QtGui.QGridLayout()
self.custom_box.addLayout(grid2)
tdclabel = QtGui.QLabel('Tool dia:')
tdclabel.setToolTip(
"Diameter of the cutting tool."
)
grid2.attach(l4, 0, 0, 1, 1)
grid2.addWidget(tdclabel, 0, 0)
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)
l5.set_tooltip_markup(
marginlabel = QtGui.QLabel('Margin:')
marginlabel.setToolTip(
"Distance from objects at which\n"
"to draw the cutout."
)
grid2.attach(l5, 0, 1, 1, 1)
grid2.addWidget(marginlabel, 1, 0)
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)
l6.set_tooltip_markup(
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.attach(l6, 0, 2, 1, 1)
grid2.addWidget(gaplabel, 2, 0)
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)
l7.set_tooltip_markup(
gapslabel = QtGui.QLabel('Gaps:')
gapslabel.setToolTip(
"Where to place the gaps, Top/Bottom\n"
"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'},
{'label': '2 (L/R)', 'value': 'lr'},
{'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.set_tooltip_markup(
self.generate_cutout_button = QtGui.QPushButton('Generate Geometry')
self.generate_cutout_button.setToolTip(
"Generate the geometry for\n"
"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
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(
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.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)
self.custom_box.pack_start(grid3, expand=True, fill=False, padding=2)
grid3 = QtGui.QGridLayout()
self.custom_box.addLayout(grid3)
l8 = Gtk.Label('Boundary margin:', xalign=1)
l8.set_tooltip_markup(
# 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.attach(l8, 0, 0, 1, 1)
grid3.addWidget(bmlabel, 0, 0)
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.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(
self.noncopper_rounded_cb.setToolTip(
"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)
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
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)
self.boundingbox_label = QtGui.QLabel('<b>Bounding Box:</b>')
self.custom_box.addWidget(self.boundingbox_label)
grid4 = Gtk.Grid(column_spacing=3, row_spacing=2)
self.custom_box.pack_start(grid4, expand=True, fill=False, padding=2)
grid4 = QtGui.QGridLayout()
self.custom_box.addLayout(grid4)
l9 = Gtk.Label('Boundary Margin:', xalign=1)
l9.set_tooltip_markup(
bbmargin = QtGui.QLabel('Boundary Margin:')
bbmargin.setToolTip(
"Distance of the edges of the box\n"
"to the nearest polygon."
)
grid4.attach(l9, 0, 0, 1, 1)
grid4.addWidget(bbmargin, 0, 0)
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.set_tooltip_markup(
self.bbrounded_cb.setToolTip(
"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)
grid4.addWidget(self.bbrounded_cb, 1, 0, 1, 2)
self.generate_bb_button = Gtk.Button(label='Generate Geometry')
self.generate_bb_button.set_tooltip_markup(
self.generate_bb_button = QtGui.QPushButton('Generate Geometry')
self.generate_bb_button.setToolTip(
"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.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas
#from FlatCAMApp import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
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 handling the plotting area in the application.
@ -38,18 +54,21 @@ class PlotCanvas:
# 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
#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??
#self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns??
self.container.addWidget(self.canvas) # Qt
# 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.connect('configure-event', self.auto_adjust_axes)
self.canvas.mpl_connect('resize_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('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)
@ -62,6 +81,7 @@ class PlotCanvas:
:param event:
:return:
"""
FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
self.key = event.key
def on_key_up(self, event):
@ -125,7 +145,7 @@ class PlotCanvas:
self.axes.grid(True)
# Re-draw
self.canvas.queue_draw()
self.canvas.draw()
def adjust_axes(self, xmin, ymin, xmax, ymax):
"""
@ -144,7 +164,7 @@ class PlotCanvas:
:return: None
"""
FlatCAMApp.App.log.debug("PC.adjust_axes()")
# FlatCAMApp.App.log.debug("PC.adjust_axes()")
width = xmax - xmin
height = ymax - ymin
@ -182,7 +202,7 @@ class PlotCanvas:
ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio])
# Re-draw
self.canvas.queue_draw()
self.canvas.draw()
def auto_adjust_axes(self, *args):
"""
@ -234,7 +254,7 @@ class PlotCanvas:
ax.set_ylim((ymin, ymax))
# Re-draw
self.canvas.queue_draw()
self.canvas.draw()
def pan(self, x, y):
xmin, xmax = self.axes.get_xlim()
@ -248,7 +268,7 @@ class PlotCanvas:
ax.set_ylim((ymin + y*height, ymax + y*height))
# Re-draw
self.canvas.queue_draw()
self.canvas.draw()
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)
def on_scroll(self, canvas, event):
def on_scroll(self, 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()
# self.canvas.grab_focus()
self.canvas.setFocus()
# Event info
z, direction = event.get_scroll_direction()
# z, direction = event.get_scroll_direction()
if self.key is None:
if direction is Gdk.ScrollDirection.UP:
if event.button == 'up':
self.zoom(1.5, self.mouse)
else:
self.zoom(1/1.5, self.mouse)
@ -286,15 +306,15 @@ class PlotCanvas:
if self.key == 'shift':
if direction is Gdk.ScrollDirection.UP:
if event.button == 'up':
self.pan(0.3, 0)
else:
self.pan(-0.3, 0)
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)
else:
self.pan(0, -0.3)
@ -308,3 +328,4 @@ class PlotCanvas:
:return: None
"""
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