-clean-up before merge
33
FlatCAM.spec
|
@ -1,33 +0,0 @@
|
|||
# -*- mode: python -*-
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
a = Analysis(['FlatCAM.py'],
|
||||
pathex=['/home/jpcaram/flatcam'],
|
||||
binaries=None,
|
||||
datas=[('share/*', 'share')],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='FlatCAM',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='FlatCAM')
|
4402
FlatCAMApp.py
|
@ -1,48 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
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
|
||||
|
1549
FlatCAMDraw.py
979
FlatCAMGUI.py
|
@ -1,979 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
from PyQt4 import QtGui, QtCore, Qt
|
||||
from GUIElements import *
|
||||
|
||||
|
||||
class FlatCAMGUI(QtGui.QMainWindow):
|
||||
|
||||
# Emitted when persistent window geometry needs to be retained
|
||||
geom_update = QtCore.pyqtSignal(int, int, int, int, name='geomUpdate')
|
||||
|
||||
def __init__(self, version, name=None):
|
||||
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 project', self)
|
||||
self.menufile.addAction(self.menufilenew)
|
||||
|
||||
# Recent
|
||||
self.recent = self.menufile.addMenu(QtGui.QIcon('share/folder16.png'), "Open recent ...")
|
||||
|
||||
# Separator
|
||||
self.menufile.addSeparator()
|
||||
|
||||
# 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)
|
||||
|
||||
# Separator
|
||||
self.menufile.addSeparator()
|
||||
|
||||
# Import SVG ...
|
||||
self.menufileimportsvg = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Import &SVG ...', self)
|
||||
self.menufile.addAction(self.menufileimportsvg)
|
||||
|
||||
# Export SVG ...
|
||||
self.menufileexportsvg = QtGui.QAction(QtGui.QIcon('share/folder16.png'), 'Export &SVG ...', self)
|
||||
self.menufile.addAction(self.menufileexportsvg)
|
||||
|
||||
# Separator
|
||||
self.menufile.addSeparator()
|
||||
|
||||
# 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)
|
||||
|
||||
# Separator
|
||||
self.menufile.addSeparator()
|
||||
|
||||
# Quit
|
||||
self.exit_action = QtGui.QAction(QtGui.QIcon('share/power16.png'), '&Exit', self)
|
||||
self.menufile.addAction(self.exit_action)
|
||||
# exitAction.setShortcut('Ctrl+Q')
|
||||
# exitAction.setStatusTip('Exit application')
|
||||
#self.exit_action.triggered.connect(QtGui.qApp.quit)
|
||||
|
||||
### Edit ###
|
||||
self.menuedit = self.menu.addMenu('&Edit')
|
||||
self.menueditnew = self.menuedit.addAction(QtGui.QIcon('share/new_geo16.png'), 'New Geometry')
|
||||
self.menueditedit = self.menuedit.addAction(QtGui.QIcon('share/edit16.png'), 'Edit Geometry')
|
||||
self.menueditok = self.menuedit.addAction(QtGui.QIcon('share/edit_ok16.png'), 'Update Geometry')
|
||||
# Separator
|
||||
self.menuedit.addSeparator()
|
||||
self.menueditjoin = self.menuedit.addAction(QtGui.QIcon('share/join16.png'), 'Join Geometry')
|
||||
self.menueditdelete = self.menuedit.addAction(QtGui.QIcon('share/trash16.png'), 'Delete')
|
||||
self.menuedit.addSeparator()
|
||||
|
||||
|
||||
### Options ###
|
||||
self.menuoptions = self.menu.addMenu('&Options')
|
||||
self.menuoptions_transfer = self.menuoptions.addMenu(QtGui.QIcon('share/transfer.png'), '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 = QtGui.QMenu('&Tool')
|
||||
self.menutoolaction = self.menu.addMenu(self.menutool)
|
||||
self.menutoolshell = self.menutool.addAction(QtGui.QIcon('share/shell16.png'), '&Command Line')
|
||||
|
||||
### Help ###
|
||||
self.menuhelp = self.menu.addMenu('&Help')
|
||||
self.menuhelp_about = self.menuhelp.addAction(QtGui.QIcon('share/tv16.png'), 'About FlatCAM')
|
||||
self.menuhelp_home = self.menuhelp.addAction(QtGui.QIcon('share/home16.png'), 'Home')
|
||||
self.menuhelp_manual = self.menuhelp.addAction(QtGui.QIcon('share/globe16.png'), 'Manual')
|
||||
|
||||
###############
|
||||
### Toolbar ###
|
||||
###############
|
||||
self.toolbarfile = QtGui.QToolBar('File Toolbar')
|
||||
self.addToolBar(self.toolbarfile)
|
||||
self.open_gerber_btn = self.toolbarfile.addAction(QtGui.QIcon('share/flatcam_icon32.png'), "Open &Gerber")
|
||||
self.open_exc_btn = self.toolbarfile.addAction(QtGui.QIcon('share/drill32.png'), "Open &Excellon")
|
||||
self.open_gcode_btn = self.toolbarfile.addAction(QtGui.QIcon('share/cnc32.png'), "Open Gco&de")
|
||||
self.save_btn = self.toolbarfile.addAction(QtGui.QIcon('share/floppy32.png'), 'Save Project &As ...')
|
||||
|
||||
self.toolbarview= QtGui.QToolBar('View Toolbar')
|
||||
self.addToolBar(self.toolbarview)
|
||||
self.zoom_fit_btn = self.toolbarview.addAction(QtGui.QIcon('share/zoom_fit32.png'), "&Zoom Fit")
|
||||
self.zoom_out_btn = self.toolbarview.addAction(QtGui.QIcon('share/zoom_out32.png'), "&Zoom Out")
|
||||
self.zoom_in_btn = self.toolbarview.addAction(QtGui.QIcon('share/zoom_in32.png'), "&Zoom In")
|
||||
# Separator
|
||||
self.toolbarview.addSeparator()
|
||||
self.clear_plot_btn = self.toolbarview.addAction(QtGui.QIcon('share/clear_plot32.png'), "&Clear Plot")
|
||||
self.replot_btn = self.toolbarview.addAction(QtGui.QIcon('share/replot32.png'), "&Replot")
|
||||
|
||||
self.toolbareditobj = QtGui.QToolBar('Obj.Editor Toolbar')
|
||||
self.addToolBar(self.toolbareditobj)
|
||||
self.newgeo_btn = self.toolbareditobj.addAction(QtGui.QIcon('share/new_geo32.png'), "New Blank Geometry")
|
||||
self.editgeo_btn = self.toolbareditobj.addAction(QtGui.QIcon('share/edit32.png'), "Edit Geometry")
|
||||
self.updategeo_btn = self.toolbareditobj.addAction(QtGui.QIcon('share/edit_ok32.png'), "Update Geometry")
|
||||
self.updategeo_btn.setEnabled(False)
|
||||
|
||||
self.toolbaredit = QtGui.QToolBar('Edit Toolbar')
|
||||
self.addToolBar(self.toolbaredit)
|
||||
self.delete_btn = self.toolbaredit.addAction(QtGui.QIcon('share/delete32.png'), "&Delete")
|
||||
|
||||
self.toolbartools = QtGui.QToolBar('Tools Toolbar')
|
||||
self.addToolBar(self.toolbartools)
|
||||
self.shell_btn = self.toolbartools.addAction(QtGui.QIcon('share/shell32.png'), "&Command Line")
|
||||
self.measure_btn = self.toolbartools.addAction(QtGui.QIcon('share/measure32.png'), "&Measurement Tool")
|
||||
|
||||
################
|
||||
### Splitter ###
|
||||
################
|
||||
self.splitter = QtGui.QSplitter()
|
||||
self.setCentralWidget(self.splitter)
|
||||
|
||||
################
|
||||
### Notebook ###
|
||||
################
|
||||
self.notebook = QtGui.QTabWidget()
|
||||
|
||||
# self.notebook.setMinimumWidth(250)
|
||||
|
||||
### Project ###
|
||||
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.fcinfo = FlatCAMInfoBar()
|
||||
infobar.addWidget(self.fcinfo, 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)
|
||||
|
||||
self.activity_view = FlatCAMActivityView()
|
||||
infobar.addWidget(self.activity_view)
|
||||
|
||||
#############
|
||||
### Icons ###
|
||||
#############
|
||||
self.app_icon = QtGui.QIcon()
|
||||
self.app_icon.addFile('share/flatcam_icon16.png', QtCore.QSize(16, 16))
|
||||
self.app_icon.addFile('share/flatcam_icon24.png', QtCore.QSize(24, 24))
|
||||
self.app_icon.addFile('share/flatcam_icon32.png', QtCore.QSize(32, 32))
|
||||
self.app_icon.addFile('share/flatcam_icon48.png', QtCore.QSize(48, 48))
|
||||
self.app_icon.addFile('share/flatcam_icon128.png', QtCore.QSize(128, 128))
|
||||
self.app_icon.addFile('share/flatcam_icon256.png', QtCore.QSize(256, 256))
|
||||
self.setWindowIcon(self.app_icon)
|
||||
|
||||
self.setGeometry(100, 100, 1024, 650)
|
||||
title = 'FlatCAM {}'.format(version)
|
||||
if name is not None:
|
||||
title += ' - {}'.format(name)
|
||||
self.setWindowTitle(title)
|
||||
self.show()
|
||||
|
||||
def closeEvent(self, event):
|
||||
grect = self.geometry()
|
||||
self.geom_update.emit(grect.x(), grect.y(), grect.width(), grect.height())
|
||||
QtGui.qApp.quit()
|
||||
|
||||
|
||||
|
||||
class FlatCAMActivityView(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FlatCAMActivityView, self).__init__(parent=parent)
|
||||
|
||||
self.setMinimumWidth(200)
|
||||
|
||||
self.icon = QtGui.QLabel(self)
|
||||
self.icon.setGeometry(0, 0, 12, 12)
|
||||
self.movie = QtGui.QMovie("share/active.gif")
|
||||
self.icon.setMovie(self.movie)
|
||||
#self.movie.start()
|
||||
|
||||
layout = QtGui.QHBoxLayout()
|
||||
layout.setContentsMargins(5, 0, 5, 0)
|
||||
layout.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(self.icon)
|
||||
self.text = QtGui.QLabel(self)
|
||||
self.text.setText("Idle.")
|
||||
|
||||
layout.addWidget(self.text)
|
||||
|
||||
def set_idle(self):
|
||||
self.movie.stop()
|
||||
self.text.setText("Idle.")
|
||||
|
||||
def set_busy(self, msg):
|
||||
self.movie.start()
|
||||
self.text.setText(msg)
|
||||
|
||||
|
||||
class FlatCAMInfoBar(QtGui.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(FlatCAMInfoBar, self).__init__(parent=parent)
|
||||
|
||||
self.icon = QtGui.QLabel(self)
|
||||
self.icon.setGeometry(0, 0, 12, 12)
|
||||
self.pmap = QtGui.QPixmap('share/graylight12.png')
|
||||
self.icon.setPixmap(self.pmap)
|
||||
|
||||
layout = QtGui.QHBoxLayout()
|
||||
layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(self.icon)
|
||||
|
||||
self.text = QtGui.QLabel(self)
|
||||
self.text.setText("Hello!")
|
||||
self.text.setToolTip("Hello!")
|
||||
|
||||
layout.addWidget(self.text)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
def set_text_(self, text):
|
||||
self.text.setText(text)
|
||||
self.text.setToolTip(text)
|
||||
|
||||
def set_status(self, text, level="info"):
|
||||
level = str(level)
|
||||
self.pmap.fill()
|
||||
if level == "error":
|
||||
self.pmap = QtGui.QPixmap('share/redlight12.png')
|
||||
elif level == "success":
|
||||
self.pmap = QtGui.QPixmap('share/greenlight12.png')
|
||||
elif level == "warning":
|
||||
self.pmap = QtGui.QPixmap('share/yellowlight12.png')
|
||||
else:
|
||||
self.pmap = QtGui.QPixmap('share/graylight12.png')
|
||||
|
||||
self.icon.setPixmap(self.pmap)
|
||||
self.set_text_(text)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
self.combine_passes_cb = FCCheckBox(label='Combine Passes')
|
||||
self.combine_passes_cb.setToolTip(
|
||||
"Combine all passes into one object"
|
||||
)
|
||||
grid1.addWidget(self.combine_passes_cb, 3, 0)
|
||||
|
||||
## 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)
|
||||
|
||||
toolchangezlabel = QtGui.QLabel('Toolchange Z:')
|
||||
toolchangezlabel.setToolTip(
|
||||
"Tool Z where user can change drill bit\n"
|
||||
)
|
||||
grid1.addWidget(toolchangezlabel, 3, 0)
|
||||
self.toolchangez_entry = LengthEntry()
|
||||
grid1.addWidget(self.toolchangez_entry, 3, 1)
|
||||
|
||||
spdlabel = QtGui.QLabel('Spindle speed:')
|
||||
spdlabel.setToolTip(
|
||||
"Speed of the spindle\n"
|
||||
"in RPM (optional)"
|
||||
)
|
||||
grid1.addWidget(spdlabel, 4, 0)
|
||||
self.spindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid1.addWidget(self.spindlespeed_entry, 4, 1)
|
||||
|
||||
#### Milling Holes ####
|
||||
self.mill_hole_label = QtGui.QLabel('<b>Mill Holes</b>')
|
||||
self.mill_hole_label.setToolTip(
|
||||
"Create Geometry for milling holes."
|
||||
)
|
||||
self.layout.addWidget(self.mill_hole_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.tooldia_entry = LengthEntry()
|
||||
grid1.addWidget(self.tooldia_entry, 0, 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)
|
||||
|
||||
spdlabel = QtGui.QLabel('Spindle speed:')
|
||||
spdlabel.setToolTip(
|
||||
"Speed of the spindle\n"
|
||||
"in RPM (optional)"
|
||||
)
|
||||
grid1.addWidget(spdlabel, 4, 0)
|
||||
self.cncspindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid1.addWidget(self.cncspindlespeed_entry, 4, 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, 2, 1)
|
||||
|
||||
# Method
|
||||
methodlabel = QtGui.QLabel('Method:')
|
||||
methodlabel.setToolTip(
|
||||
"Algorithm to paint the polygon:<BR>"
|
||||
"<B>Standard</B>: Fixed step inwards.<BR>"
|
||||
"<B>Seed-based</B>: Outwards from seed."
|
||||
)
|
||||
grid2.addWidget(methodlabel, 3, 0)
|
||||
self.paintmethod_combo = RadioSet([
|
||||
{"label": "Standard", "value": "standard"},
|
||||
{"label": "Seed-based", "value": "seed"},
|
||||
{"label": "Straight lines", "value": "lines"}
|
||||
], orientation='vertical')
|
||||
grid2.addWidget(self.paintmethod_combo, 3, 1)
|
||||
|
||||
# Connect lines
|
||||
pathconnectlabel = QtGui.QLabel("Connect:")
|
||||
pathconnectlabel.setToolTip(
|
||||
"Draw lines between resulting\n"
|
||||
"segments to minimize tool lifts."
|
||||
)
|
||||
grid2.addWidget(pathconnectlabel, 4, 0)
|
||||
self.pathconnect_cb = FCCheckBox()
|
||||
grid2.addWidget(self.pathconnect_cb, 4, 1)
|
||||
|
||||
# Paint contour
|
||||
contourlabel = QtGui.QLabel("Contour:")
|
||||
contourlabel.setToolTip(
|
||||
"Cut around the perimeter of the polygon\n"
|
||||
"to trim rough edges."
|
||||
)
|
||||
grid2.addWidget(contourlabel, 5, 0)
|
||||
self.contour_cb = FCCheckBox()
|
||||
grid2.addWidget(self.contour_cb, 5, 1)
|
||||
|
||||
# Polygon selection
|
||||
selectlabel = QtGui.QLabel('Selection:')
|
||||
selectlabel.setToolTip(
|
||||
"How to select the polygons to paint."
|
||||
)
|
||||
grid2.addWidget(selectlabel, 6, 0)
|
||||
# grid3 = QtGui.QGridLayout()
|
||||
self.selectmethod_combo = RadioSet([
|
||||
{"label": "Single", "value": "single"},
|
||||
{"label": "All", "value": "all"},
|
||||
# {"label": "Rectangle", "value": "rectangle"}
|
||||
])
|
||||
grid2.addWidget(self.selectmethod_combo, 6, 1)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# Prepend to G-Code
|
||||
prependlabel = QtGui.QLabel('Prepend to G-Code:')
|
||||
prependlabel.setToolTip(
|
||||
"Type here any G-Code commands you would\n"
|
||||
"like to add at the beginning of the G-Code file."
|
||||
)
|
||||
self.layout.addWidget(prependlabel)
|
||||
|
||||
self.prepend_text = FCTextArea()
|
||||
self.layout.addWidget(self.prepend_text)
|
||||
|
||||
# Append text to G-Code
|
||||
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)
|
||||
|
||||
# Dwell
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.layout.addLayout(grid1)
|
||||
|
||||
dwelllabel = QtGui.QLabel('Dwell:')
|
||||
dwelllabel.setToolTip(
|
||||
"Pause to allow the spindle to reach its\n"
|
||||
"speed before cutting."
|
||||
)
|
||||
dwelltime = QtGui.QLabel('Duration [sec.]:')
|
||||
dwelltime.setToolTip(
|
||||
"Number of second to dwell."
|
||||
)
|
||||
self.dwell_cb = FCCheckBox()
|
||||
self.dwelltime_cb = FCEntry()
|
||||
grid1.addWidget(dwelllabel, 0, 0)
|
||||
grid1.addWidget(self.dwell_cb, 0, 1)
|
||||
grid1.addWidget(dwelltime, 1, 0)
|
||||
grid1.addWidget(self.dwelltime_cb, 1, 1)
|
||||
|
||||
|
||||
class GlobalOptionsUI(QtGui.QWidget):
|
||||
"""
|
||||
This is the app and project options editor.
|
||||
"""
|
||||
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()
|
1737
FlatCAMObj.py
|
@ -1,156 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
from FlatCAMGUI import FlatCAMActivityView
|
||||
from PyQt4 import QtCore
|
||||
import weakref
|
||||
|
||||
|
||||
# import logging
|
||||
|
||||
# log = logging.getLogger('base2')
|
||||
# #log.setLevel(logging.DEBUG)
|
||||
# log.setLevel(logging.WARNING)
|
||||
# #log.setLevel(logging.INFO)
|
||||
# formatter = logging.Formatter('[%(levelname)s] %(message)s')
|
||||
# handler = logging.StreamHandler()
|
||||
# handler.setFormatter(formatter)
|
||||
# log.addHandler(handler)
|
||||
|
||||
|
||||
class FCProcess(object):
|
||||
|
||||
app = None
|
||||
|
||||
def __init__(self, descr):
|
||||
self.callbacks = {
|
||||
"done": []
|
||||
}
|
||||
self.descr = descr
|
||||
self.status = "Active"
|
||||
|
||||
def __del__(self):
|
||||
self.done()
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is not None:
|
||||
self.app.log.error("Abnormal termination of process!")
|
||||
self.app.log.error(exc_type)
|
||||
self.app.log.error(exc_val)
|
||||
self.app.log.error(exc_tb)
|
||||
|
||||
self.done()
|
||||
|
||||
def done(self):
|
||||
for fcn in self.callbacks["done"]:
|
||||
fcn(self)
|
||||
|
||||
def connect(self, callback, event="done"):
|
||||
if callback not in self.callbacks[event]:
|
||||
self.callbacks[event].append(callback)
|
||||
|
||||
def disconnect(self, callback, event="done"):
|
||||
try:
|
||||
self.callbacks[event].remove(callback)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def set_status(self, status_string):
|
||||
self.status = status_string
|
||||
|
||||
def status_msg(self):
|
||||
return self.descr
|
||||
|
||||
|
||||
class FCProcessContainer(object):
|
||||
"""
|
||||
This is the process container, or controller (as in MVC)
|
||||
of the Process/Activity tracking.
|
||||
|
||||
FCProcessContainer keeps weak references to the FCProcess'es
|
||||
such that their __del__ method is called when the user
|
||||
looses track of their reference.
|
||||
"""
|
||||
|
||||
app = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.procs = []
|
||||
|
||||
def add(self, proc):
|
||||
|
||||
self.procs.append(weakref.ref(proc))
|
||||
|
||||
def new(self, descr):
|
||||
proc = FCProcess(descr)
|
||||
|
||||
proc.connect(self.on_done, event="done")
|
||||
|
||||
self.add(proc)
|
||||
|
||||
self.on_change(proc)
|
||||
|
||||
return proc
|
||||
|
||||
def on_change(self, proc):
|
||||
pass
|
||||
|
||||
def on_done(self, proc):
|
||||
self.remove(proc)
|
||||
|
||||
def remove(self, proc):
|
||||
|
||||
to_be_removed = []
|
||||
|
||||
for pref in self.procs:
|
||||
if pref() == proc or pref() is None:
|
||||
to_be_removed.append(pref)
|
||||
|
||||
for pref in to_be_removed:
|
||||
self.procs.remove(pref)
|
||||
|
||||
|
||||
class FCVisibleProcessContainer(QtCore.QObject, FCProcessContainer):
|
||||
something_changed = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, view):
|
||||
assert isinstance(view, FlatCAMActivityView), \
|
||||
"Expected a FlatCAMActivityView, got %s" % type(view)
|
||||
|
||||
FCProcessContainer.__init__(self)
|
||||
QtCore.QObject.__init__(self)
|
||||
|
||||
self.view = view
|
||||
|
||||
self.something_changed.connect(self.update_view)
|
||||
|
||||
def on_done(self, proc):
|
||||
self.app.log.debug("FCVisibleProcessContainer.on_done()")
|
||||
super(FCVisibleProcessContainer, self).on_done(proc)
|
||||
|
||||
self.something_changed.emit()
|
||||
|
||||
def on_change(self, proc):
|
||||
self.app.log.debug("FCVisibleProcessContainer.on_change()")
|
||||
super(FCVisibleProcessContainer, self).on_change(proc)
|
||||
|
||||
self.something_changed.emit()
|
||||
|
||||
def update_view(self):
|
||||
if len(self.procs) == 0:
|
||||
self.view.set_idle()
|
||||
|
||||
elif len(self.procs) == 1:
|
||||
self.view.set_busy(self.procs[0]().status_msg())
|
||||
|
||||
else:
|
||||
self.view.set_busy("%d processes running." % len(self.procs))
|
|
@ -1,33 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
import termwidget
|
||||
|
||||
|
||||
class FCShell(termwidget.TermWidget):
|
||||
def __init__(self, sysShell, *args):
|
||||
termwidget.TermWidget.__init__(self, *args)
|
||||
self._sysShell = sysShell
|
||||
|
||||
def is_command_complete(self, text):
|
||||
def skipQuotes(text):
|
||||
quote = text[0]
|
||||
text = text[1:]
|
||||
endIndex = str(text).index(quote)
|
||||
return text[endIndex:]
|
||||
while text:
|
||||
if text[0] in ('"', "'"):
|
||||
try:
|
||||
text = skipQuotes(text)
|
||||
except ValueError:
|
||||
return False
|
||||
text = text[1:]
|
||||
return True
|
||||
|
||||
def child_exec_command(self, text):
|
||||
self._sysShell.exec_command(text)
|
|
@ -1,80 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
from PyQt4 import QtGui
|
||||
|
||||
|
||||
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, icon=None, separator=None, **kwargs):
|
||||
before = None
|
||||
|
||||
# 'pos' is the menu where the Action has to be installed
|
||||
# if no 'pos' kwarg is provided then by default our Action will be installed in the menutool
|
||||
# as it previously was
|
||||
if 'pos' in kwargs:
|
||||
pos = kwargs['pos']
|
||||
else:
|
||||
pos = self.app.ui.menutool
|
||||
|
||||
# 'before' is the Action in the menu stated by 'pos' kwarg, before which we want our Action to be installed
|
||||
# if 'before' kwarg is not provided, by default our Action will be added in the last place.
|
||||
if 'before' in kwargs:
|
||||
before = (kwargs['before'])
|
||||
|
||||
# create the new Action
|
||||
self.menuAction = QtGui.QAction(self)
|
||||
# if provided, add an icon to this Action
|
||||
if icon is not None:
|
||||
self.menuAction.setIcon(icon)
|
||||
# set the text name of the Action, which will be displayed in the menu
|
||||
self.menuAction.setText(self.toolName)
|
||||
# add a ToolTip to the new Action
|
||||
# self.menuAction.setToolTip(self.toolTip) # currently not available
|
||||
|
||||
# insert the action in the position specified by 'before' and 'pos' kwargs
|
||||
pos.insertAction(before, self.menuAction)
|
||||
|
||||
# if separator parameter is True add a Separator after the newly created Action
|
||||
if separator is True:
|
||||
pos.addSeparator()
|
||||
|
||||
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()
|
|
@ -1,34 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
#################################################
|
||||
# FlatCAM - Version settings #
|
||||
#################################################
|
||||
|
||||
import logging
|
||||
|
||||
version = {
|
||||
"number": 8.5,
|
||||
"date": (2016, 7, 1), # Year, Month, Day
|
||||
"name": None,
|
||||
"release": False,
|
||||
}
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.version = version["number"]
|
||||
app.version_date = version["date"]
|
||||
if version["release"]:
|
||||
app.log.setLevel(logging.WARNING)
|
||||
else:
|
||||
app.log.setLevel(logging.DEBUG)
|
||||
|
||||
if version["name"] is None and version["release"] == False:
|
||||
app.version_name = "Development Version"
|
||||
else:
|
||||
app.version_name = version["name"]
|
|
@ -1,66 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
from PyQt4 import QtCore
|
||||
|
||||
|
||||
class Worker(QtCore.QObject):
|
||||
"""
|
||||
Implements a queue of tasks to be carried out in order
|
||||
in a single independent thread.
|
||||
"""
|
||||
|
||||
# avoid multiple tests for debug availability
|
||||
pydevd_failed = False
|
||||
|
||||
def __init__(self, app, name=None):
|
||||
super(Worker, self).__init__()
|
||||
self.app = app
|
||||
self.name = name
|
||||
|
||||
def allow_debug(self):
|
||||
"""
|
||||
allow debuging/breakpoints in this threads
|
||||
should work from PyCharm and PyDev
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not self.pydevd_failed:
|
||||
try:
|
||||
import pydevd
|
||||
pydevd.settrace(suspend=False, trace_only_current_thread=True)
|
||||
except ImportError:
|
||||
self.pydevd_failed=True
|
||||
|
||||
def run(self):
|
||||
|
||||
self.app.log.debug("Worker Started!")
|
||||
|
||||
self.allow_debug()
|
||||
|
||||
# Tasks are queued in the event listener.
|
||||
self.app.worker_task.connect(self.do_worker_task)
|
||||
|
||||
def do_worker_task(self, task):
|
||||
|
||||
self.app.log.debug("Running task: %s" % str(task))
|
||||
|
||||
self.allow_debug()
|
||||
|
||||
if ('worker_name' in task and task['worker_name'] == self.name) or \
|
||||
('worker_name' not in task and self.name is None):
|
||||
|
||||
try:
|
||||
task['fcn'](*task['params'])
|
||||
except Exception as e:
|
||||
self.app.thread_exception.emit(e)
|
||||
raise e
|
||||
|
||||
return
|
||||
|
||||
self.app.log.debug("Task ignored.")
|
407
GUIElements.py
|
@ -1,407 +0,0 @@
|
|||
from PyQt4 import QtGui, QtCore
|
||||
from copy import copy
|
||||
#import FlatCAMApp
|
||||
import re
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
||||
class RadioSet(QtGui.QWidget):
|
||||
def __init__(self, choices, orientation='horizontal', parent=None):
|
||||
"""
|
||||
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.
|
||||
:param orientation: 'horizontal' (default) of 'vertical'.
|
||||
:param parent: Qt parent widget.
|
||||
:type choices: list
|
||||
"""
|
||||
super(RadioSet, self).__init__(parent)
|
||||
self.choices = copy(choices)
|
||||
|
||||
if orientation == 'horizontal':
|
||||
layout = QtGui.QHBoxLayout()
|
||||
else:
|
||||
layout = QtGui.QVBoxLayout()
|
||||
|
||||
group = QtGui.QButtonGroup(self)
|
||||
|
||||
for choice in self.choices:
|
||||
choice['radio'] = QtGui.QRadioButton(choice['label'])
|
||||
group.addButton(choice['radio'])
|
||||
layout.addWidget(choice['radio'], stretch=0)
|
||||
choice['radio'].toggled.connect(self.on_toggle)
|
||||
|
||||
layout.addStretch()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.group_toggle_fn = lambda: None
|
||||
|
||||
def on_toggle(self):
|
||||
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'].isChecked():
|
||||
return choice['value']
|
||||
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'].setChecked(True)
|
||||
return
|
||||
log.error("Value given is not part of this RadioSet: %s" % str(val))
|
||||
|
||||
|
||||
class LengthEntry(QtGui.QLineEdit):
|
||||
def __init__(self, output_units='IN', parent=None):
|
||||
super(LengthEntry, self).__init__(parent)
|
||||
|
||||
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.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(LengthEntry, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(LengthEntry, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def returnPressed(self, *args, **kwargs):
|
||||
val = self.get_value()
|
||||
if val is not None:
|
||||
self.set_text(str(val))
|
||||
else:
|
||||
log.warning("Could not interpret entry: %s" % self.get_text())
|
||||
|
||||
def get_value(self):
|
||||
raw = str(self.text()).strip(' ')
|
||||
# match = self.format_re.search(raw)
|
||||
|
||||
try:
|
||||
units = raw[-2:]
|
||||
units = self.scales[self.output_units][units.upper()]
|
||||
value = raw[:-2]
|
||||
return float(eval(value))*units
|
||||
except IndexError:
|
||||
value = raw
|
||||
return float(eval(value))
|
||||
except KeyError:
|
||||
value = raw
|
||||
return float(eval(value))
|
||||
except:
|
||||
log.warning("Could not parse value in entry: %s" % str(raw))
|
||||
return None
|
||||
|
||||
def set_value(self, val):
|
||||
self.setText(str(val))
|
||||
|
||||
|
||||
class FloatEntry(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(FloatEntry, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(FloatEntry, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(FloatEntry, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def returnPressed(self, *args, **kwargs):
|
||||
val = self.get_value()
|
||||
if val is not None:
|
||||
self.set_text(str(val))
|
||||
else:
|
||||
log.warning("Could not interpret entry: %s" % self.text())
|
||||
|
||||
def get_value(self):
|
||||
raw = str(self.text()).strip(' ')
|
||||
try:
|
||||
evaled = eval(raw)
|
||||
except:
|
||||
log.error("Could not evaluate: %s" % str(raw))
|
||||
return None
|
||||
|
||||
return float(evaled)
|
||||
|
||||
def set_value(self, val):
|
||||
self.setText("%.6f" % val)
|
||||
|
||||
|
||||
class IntEntry(QtGui.QLineEdit):
|
||||
|
||||
def __init__(self, parent=None, allow_empty=False, empty_val=None):
|
||||
super(IntEntry, self).__init__(parent)
|
||||
self.allow_empty = allow_empty
|
||||
self.empty_val = empty_val
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(IntEntry, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(IntEntry, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self):
|
||||
|
||||
if self.allow_empty:
|
||||
if str(self.text()) == "":
|
||||
return self.empty_val
|
||||
|
||||
return int(self.text())
|
||||
|
||||
def set_value(self, val):
|
||||
|
||||
if val == self.empty_val and self.allow_empty:
|
||||
self.setText("")
|
||||
return
|
||||
|
||||
self.setText(str(val))
|
||||
|
||||
|
||||
class FCEntry(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(FCEntry, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(FCEntry, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(FCEntry, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self):
|
||||
return str(self.text())
|
||||
|
||||
def set_value(self, val):
|
||||
self.setText(str(val))
|
||||
|
||||
|
||||
class EvalEntry(QtGui.QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
super(EvalEntry, self).__init__(parent)
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(EvalEntry, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(EvalEntry, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def returnPressed(self, *args, **kwargs):
|
||||
val = self.get_value()
|
||||
if val is not None:
|
||||
self.setText(str(val))
|
||||
else:
|
||||
log.warning("Could not interpret entry: %s" % self.get_text())
|
||||
|
||||
def get_value(self):
|
||||
raw = str(self.text()).strip(' ')
|
||||
try:
|
||||
return eval(raw)
|
||||
except:
|
||||
log.error("Could not evaluate: %s" % str(raw))
|
||||
return None
|
||||
|
||||
def set_value(self, val):
|
||||
self.setText(str(val))
|
||||
|
||||
|
||||
class FCCheckBox(QtGui.QCheckBox):
|
||||
def __init__(self, label='', parent=None):
|
||||
super(FCCheckBox, self).__init__(str(label), parent)
|
||||
|
||||
def get_value(self):
|
||||
return self.isChecked()
|
||||
|
||||
def set_value(self, val):
|
||||
self.setChecked(val)
|
||||
|
||||
def toggle(self):
|
||||
self.set_value(not self.get_value())
|
||||
|
||||
|
||||
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 FCInputDialog(QtGui.QInputDialog):
|
||||
def __init__(self, parent=None, ok=False, val=None):
|
||||
super(FCInputDialog, self).__init__(parent)
|
||||
self.allow_empty = ok
|
||||
self.empty_val = val
|
||||
self.readyToEdit = True
|
||||
|
||||
def mousePressEvent(self, e, Parent=None):
|
||||
# required to deselect on 2nd click
|
||||
super(FCInputDialog, self).mousePressEvent(e)
|
||||
if self.readyToEdit:
|
||||
self.selectAll()
|
||||
self.readyToEdit = False
|
||||
|
||||
def focusOutEvent(self, e):
|
||||
# required to remove cursor on focusOut
|
||||
super(FCInputDialog, self).focusOutEvent(e)
|
||||
self.deselect()
|
||||
self.readyToEdit = True
|
||||
|
||||
def get_value(self, title=None, message=None, min=None, max=None, decimals=None):
|
||||
if title is None:
|
||||
title = "FlatCAM action"
|
||||
if message is None:
|
||||
message = "Please enter the value: "
|
||||
if min is None:
|
||||
min = 0.0
|
||||
if max is None:
|
||||
max = 100.0
|
||||
if decimals is None:
|
||||
decimals = 1
|
||||
self.val,self.ok = self.getDouble(self, title, message, min=min,
|
||||
max=max, decimals=decimals)
|
||||
return [self.val,self.ok]
|
||||
|
||||
def set_value(self, val):
|
||||
pass
|
||||
|
||||
|
||||
class FCButton(QtGui.QPushButton):
|
||||
def __init__(self, parent=None):
|
||||
super(FCButton, self).__init__(parent)
|
||||
|
||||
def get_value(self):
|
||||
return self.isChecked()
|
||||
|
||||
def set_value(self, val):
|
||||
self.setText(str(val))
|
||||
|
||||
|
||||
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():
|
||||
# log.debug("VerticalScrollArea: Widget resized:")
|
||||
# log.debug(" minimumSizeHint().width() = %d" % self.widget().minimumSizeHint().width())
|
||||
# log.debug(" verticalScrollBar().width() = %d" % self.verticalScrollBar().width())
|
||||
|
||||
self.setMinimumWidth(self.widget().sizeHint().width() +
|
||||
self.verticalScrollBar().sizeHint().width())
|
||||
|
||||
# if self.verticalScrollBar().isVisible():
|
||||
# log.debug(" Scroll bar visible")
|
||||
# self.setMinimumWidth(self.widget().minimumSizeHint().width() +
|
||||
# self.verticalScrollBar().width())
|
||||
# else:
|
||||
# log.debug(" Scroll bar hidden")
|
||||
# self.setMinimumWidth(self.widget().minimumSizeHint().width())
|
||||
return QtGui.QWidget.eventFilter(self, source, event)
|
||||
|
||||
|
||||
class OptionalInputSection:
|
||||
|
||||
def __init__(self, cb, optinputs):
|
||||
"""
|
||||
Associates the a checkbox with a set of inputs.
|
||||
|
||||
:param cb: Checkbox that enables the optional inputs.
|
||||
:param optinputs: List of widgets that are optional.
|
||||
:return:
|
||||
"""
|
||||
assert isinstance(cb, FCCheckBox), \
|
||||
"Expected an FCCheckBox, got %s" % type(cb)
|
||||
|
||||
self.cb = cb
|
||||
self.optinputs = optinputs
|
||||
|
||||
self.on_cb_change()
|
||||
self.cb.stateChanged.connect(self.on_cb_change)
|
||||
|
||||
def on_cb_change(self):
|
||||
|
||||
if self.cb.checkState():
|
||||
|
||||
for widget in self.optinputs:
|
||||
widget.setEnabled(True)
|
||||
|
||||
else:
|
||||
|
||||
for widget in self.optinputs:
|
||||
widget.setEnabled(False)
|
||||
|
9
LICENSE
|
@ -1,9 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2016 Juan Pablo Caram
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,9 +0,0 @@
|
|||
recursive-include share *.png
|
||||
recursive-include share *.svg
|
||||
|
||||
include flatcam.desktop
|
||||
include LICENSE
|
||||
include README.md
|
||||
include *.py
|
||||
include MANIFEST.in
|
||||
|
|
@ -1,362 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
from FlatCAMObj import *
|
||||
import inspect # TODO: Remove
|
||||
import FlatCAMApp
|
||||
from PyQt4 import Qt, QtGui, QtCore
|
||||
|
||||
|
||||
class KeySensitiveListView(QtGui.QListView):
|
||||
"""
|
||||
QtGui.QListView extended to emit a signal on key press.
|
||||
"""
|
||||
|
||||
keyPressed = QtCore.pyqtSignal(int)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
super(KeySensitiveListView, self).keyPressEvent(event)
|
||||
self.keyPressed.emit(event.key())
|
||||
|
||||
|
||||
#class ObjectCollection(QtCore.QAbstractListModel):
|
||||
class ObjectCollection():
|
||||
"""
|
||||
Object storage and management.
|
||||
"""
|
||||
|
||||
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, parent=None):
|
||||
#QtCore.QAbstractListModel.__init__(self, parent=parent)
|
||||
|
||||
### Icons for the list view
|
||||
self.icons = {}
|
||||
for kind in ObjectCollection.icon_files:
|
||||
self.icons[kind] = QtGui.QPixmap(ObjectCollection.icon_files[kind])
|
||||
|
||||
### Data ###
|
||||
self.object_list = []
|
||||
self.checked_indexes = []
|
||||
|
||||
# Names of objects that are expected to become available.
|
||||
# For example, when the creation of a new object will run
|
||||
# in the background and will complete some time in the
|
||||
# future. This is a way to reserve the name and to let other
|
||||
# tasks know that they have to wait until available.
|
||||
self.promises = set()
|
||||
|
||||
### View
|
||||
#self.view = QtGui.QListView()
|
||||
self.view = KeySensitiveListView()
|
||||
self.view.setSelectionMode(Qt.QAbstractItemView.ExtendedSelection)
|
||||
self.model = QtGui.QStandardItemModel(self.view)
|
||||
self.view.setModel(self.model)
|
||||
self.model.itemChanged.connect(self.on_item_changed)
|
||||
|
||||
self.click_modifier = None
|
||||
|
||||
## GUI Events
|
||||
self.view.selectionModel().selectionChanged.connect(self.on_list_selection_change)
|
||||
self.view.activated.connect(self.on_item_activated)
|
||||
self.view.keyPressed.connect(self.on_key)
|
||||
self.view.clicked.connect(self.on_mouse_down)
|
||||
|
||||
def promise(self, obj_name):
|
||||
FlatCAMApp.App.log.debug("Object %s has been promised." % obj_name)
|
||||
self.promises.add(obj_name)
|
||||
|
||||
def has_promises(self):
|
||||
return len(self.promises) > 0
|
||||
|
||||
def on_key(self, key):
|
||||
|
||||
# Delete
|
||||
if key == QtCore.Qt.Key_Delete:
|
||||
# Delete via the application to
|
||||
# ensure cleanup of the GUI
|
||||
self.get_active().app.on_delete()
|
||||
return
|
||||
|
||||
if key == QtCore.Qt.Key_Space:
|
||||
self.get_active().ui.plot_cb.toggle()
|
||||
return
|
||||
|
||||
def print_list(self):
|
||||
for obj in self.object_list:
|
||||
print(obj)
|
||||
|
||||
def on_mouse_down(self, event):
|
||||
FlatCAMApp.App.log.debug("Mouse button pressed on list")
|
||||
#self.print_list()
|
||||
|
||||
def rowCount(self, parent=QtCore.QModelIndex(), *args, **kwargs):
|
||||
return len(self.object_list)
|
||||
|
||||
def columnCount(self, *args, **kwargs):
|
||||
return 1
|
||||
|
||||
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]
|
||||
# if role == Qt.Qt.CheckStateRole:
|
||||
# if row in self.checked_indexes:
|
||||
# return Qt.Qt.Checked
|
||||
# else:
|
||||
# return Qt.Qt.Unchecked
|
||||
|
||||
def append(self, obj, active=False):
|
||||
FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> OC.append()")
|
||||
|
||||
name = obj.options["name"]
|
||||
|
||||
# Check promises and clear if exists
|
||||
if name in self.promises:
|
||||
self.promises.remove(name)
|
||||
FlatCAMApp.App.log.debug("Promised object %s became available." % name)
|
||||
FlatCAMApp.App.log.debug("%d promised objects remaining." % len(self.promises))
|
||||
|
||||
# Prevent same name
|
||||
while name in self.get_names():
|
||||
## Create a new name
|
||||
# Ends with number?
|
||||
FlatCAMApp.App.log.debug("new_object(): Object name (%s) exists, changing." % name)
|
||||
match = re.search(r'(.*[^\d])?(\d+)$', name)
|
||||
if match: # Yes: Increment the number!
|
||||
base = match.group(1) or ''
|
||||
num = int(match.group(2))
|
||||
name = base + str(num + 1)
|
||||
else: # No: add a number!
|
||||
name += "_1"
|
||||
obj.options["name"] = name
|
||||
|
||||
obj.set_ui(obj.ui_type())
|
||||
|
||||
# Required before appending (Qt MVC)
|
||||
#self.beginInsertRows(QtCore.QModelIndex(), len(self.object_list), len(self.object_list))
|
||||
|
||||
# Simply append to the python list
|
||||
self.object_list.append(obj)
|
||||
|
||||
# Create the model item to insert into the QListView
|
||||
icon = QtGui.QIcon(self.icons[obj.kind])#self.icons["gerber"])
|
||||
item = QtGui.QStandardItem(icon, str(name))
|
||||
# Item is not editable, so that double click
|
||||
# does not allow cell value modification.
|
||||
item.setEditable(False)
|
||||
# The item is checkable, to add the checkbox.
|
||||
item.setCheckable(True)
|
||||
if obj.options["plot"] is True:
|
||||
item.setCheckState(2) #Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(0) #Qt.Unchecked)
|
||||
|
||||
self.model.appendRow(item)
|
||||
|
||||
obj.option_changed.connect(self.on_object_option_changed)
|
||||
|
||||
# Required after appending (Qt MVC)
|
||||
#self.endInsertRows()
|
||||
|
||||
def on_object_option_changed(self, obj, key):
|
||||
if key == "plot":
|
||||
self.model.blockSignals(True)
|
||||
name = obj.options["name"]
|
||||
state = 0 #Qt.Unchecked
|
||||
for index in range(self.model.rowCount()):
|
||||
item = self.model.item(index)
|
||||
if self.object_list[item.row()].options["name"] == name:
|
||||
if obj.options["plot"] == True:
|
||||
state = 2 #Qt.Checked
|
||||
|
||||
item.setCheckState(state)
|
||||
obj.ui.plot_cb.set_value(state)
|
||||
break
|
||||
self.model.blockSignals(False)
|
||||
|
||||
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()")
|
||||
return [x.options['name'] for x in self.object_list]
|
||||
|
||||
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
|
||||
|
||||
for obj in self.object_list:
|
||||
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.")
|
||||
|
||||
return [xmin, ymin, xmax, ymax]
|
||||
|
||||
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()")
|
||||
|
||||
for obj in self.object_list:
|
||||
if obj.options['name'] == name:
|
||||
return obj
|
||||
return None
|
||||
|
||||
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.model.removeRow(row)
|
||||
|
||||
#self.endRemoveRows()
|
||||
|
||||
def get_active(self):
|
||||
"""
|
||||
Returns the active object or None
|
||||
|
||||
:return: FlatCAMObj or None
|
||||
"""
|
||||
selections = self.view.selectedIndexes()
|
||||
if len(selections) == 0:
|
||||
return None
|
||||
row = selections[0].row()
|
||||
return self.object_list[row]
|
||||
|
||||
def get_selected(self):
|
||||
"""
|
||||
Returns list of objects selected in the view.
|
||||
|
||||
:return: List of objects
|
||||
"""
|
||||
return [self.object_list[sel.row()] for sel in self.view.selectedIndexes()]
|
||||
|
||||
def set_active(self, name):
|
||||
"""
|
||||
Selects object by name from the project list. This triggers the
|
||||
list_selection_changed event and call on_list_selection_changed.
|
||||
|
||||
:param name: Name of the FlatCAM Object
|
||||
:return: None
|
||||
"""
|
||||
iobj = self.model.createIndex(self.get_names().index(name), 0) # Column 0
|
||||
self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Select)
|
||||
|
||||
def set_inactive(self, name):
|
||||
"""
|
||||
Unselect object by name from the project list. This triggers the
|
||||
list_selection_changed event and call on_list_selection_changed.
|
||||
|
||||
:param name: Name of the FlatCAM Object
|
||||
:return: None
|
||||
"""
|
||||
iobj = self.model.createIndex(self.get_names().index(name), 0) # Column 0
|
||||
self.view.selectionModel().select(iobj, QtGui.QItemSelectionModel.Deselect)
|
||||
|
||||
def set_all_inactive(self):
|
||||
"""
|
||||
Unselect all objects from the project list. This triggers the
|
||||
list_selection_changed event and call on_list_selection_changed.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
for name in self.get_names():
|
||||
self.set_inactive(name)
|
||||
|
||||
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_changed(self, item):
|
||||
FlatCAMApp.App.log.debug("on_item_changed(): " + str(item.row()) + " " + self.object_list[item.row()].options["name"])
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
self.object_list[item.row()].options["plot"] = True #(item.checkState() == QtCore.Qt.Checked)
|
||||
else:
|
||||
self.object_list[item.row()].options["plot"] = False #(item.checkState() == QtCore.Qt.Checked)
|
||||
|
||||
self.object_list[item.row()].plot()
|
||||
return
|
||||
|
||||
def on_item_activated(self, index):
|
||||
"""
|
||||
Double-click or Enter on item.
|
||||
|
||||
:param index: Index of the item in the list.
|
||||
:return: None
|
||||
"""
|
||||
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.model.removeRows(0, self.model.rowCount())
|
||||
self.object_list = []
|
||||
self.checked_indexes = []
|
||||
|
||||
# self.endResetModel()
|
||||
|
||||
def get_list(self):
|
||||
return self.object_list
|
||||
|
875
ObjectUI.py
|
@ -1,875 +0,0 @@
|
|||
import sys
|
||||
from PyQt4 import QtGui, QtCore
|
||||
#from GUIElements import *
|
||||
from GUIElements import FCEntry, FloatEntry, EvalEntry, FCCheckBox, \
|
||||
LengthEntry, FCTextArea, IntEntry, RadioSet, OptionalInputSection
|
||||
|
||||
|
||||
class ObjectUI(QtGui.QWidget):
|
||||
"""
|
||||
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', parent=None):
|
||||
QtGui.QWidget.__init__(self, parent=parent)
|
||||
|
||||
layout = QtGui.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
## Page Title box (spacing between children)
|
||||
self.title_box = QtGui.QHBoxLayout()
|
||||
layout.addLayout(self.title_box)
|
||||
|
||||
## Page Title icon
|
||||
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 = 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 = 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.addWidget(self.name_entry)
|
||||
|
||||
## Box box for custom widgets
|
||||
# This gets populated in offspring implementations.
|
||||
self.custom_box = QtGui.QVBoxLayout()
|
||||
layout.addLayout(self.custom_box)
|
||||
|
||||
###########################
|
||||
## Common to all objects ##
|
||||
###########################
|
||||
|
||||
#### Scale ####
|
||||
self.scale_label = QtGui.QLabel('<b>Scale:</b>')
|
||||
self.scale_label.setToolTip(
|
||||
"Change the size of the object."
|
||||
)
|
||||
layout.addWidget(self.scale_label)
|
||||
|
||||
self.scale_grid = QtGui.QGridLayout()
|
||||
layout.addLayout(self.scale_grid)
|
||||
|
||||
# Factor
|
||||
faclabel = QtGui.QLabel('Factor:')
|
||||
faclabel.setToolTip(
|
||||
"Factor by which to multiply\n"
|
||||
"geometric features of this object."
|
||||
)
|
||||
self.scale_grid.addWidget(faclabel, 0, 0)
|
||||
self.scale_entry = FloatEntry()
|
||||
self.scale_entry.set_value(1.0)
|
||||
self.scale_grid.addWidget(self.scale_entry, 0, 1)
|
||||
|
||||
# GO Button
|
||||
self.scale_button = QtGui.QPushButton('Scale')
|
||||
self.scale_button.setToolTip(
|
||||
"Perform scaling operation."
|
||||
)
|
||||
layout.addWidget(self.scale_button)
|
||||
|
||||
#### Offset ####
|
||||
self.offset_label = QtGui.QLabel('<b>Offset:</b>')
|
||||
self.offset_label.setToolTip(
|
||||
"Change the position of this object."
|
||||
)
|
||||
layout.addWidget(self.offset_label)
|
||||
|
||||
self.offset_grid = QtGui.QGridLayout()
|
||||
layout.addLayout(self.offset_grid)
|
||||
|
||||
self.offset_vectorlabel = QtGui.QLabel('Vector:')
|
||||
self.offset_vectorlabel.setToolTip(
|
||||
"Amount by which to move the object\n"
|
||||
"in the x and y axes in (x, y) format."
|
||||
)
|
||||
self.offset_grid.addWidget(self.offset_vectorlabel, 0, 0)
|
||||
self.offsetvector_entry = EvalEntry()
|
||||
self.offsetvector_entry.setText("(0.0, 0.0)")
|
||||
self.offset_grid.addWidget(self.offsetvector_entry, 0, 1)
|
||||
|
||||
self.offset_button = QtGui.QPushButton('Offset')
|
||||
self.offset_button.setToolTip(
|
||||
"Perform the offset operation."
|
||||
)
|
||||
layout.addWidget(self.offset_button)
|
||||
|
||||
self.auto_offset_button = QtGui.QPushButton('Offset auto')
|
||||
self.auto_offset_button.setToolTip(
|
||||
"Align the object with the x and y axes."
|
||||
)
|
||||
layout.addWidget(self.auto_offset_button)
|
||||
|
||||
#### Mirror ####
|
||||
self.mirror_label = QtGui.QLabel('<b>Mirror:</b>')
|
||||
self.mirror_label.setToolTip(
|
||||
"Flip the object along an axis."
|
||||
)
|
||||
layout.addWidget(self.mirror_label)
|
||||
|
||||
self.mirror_axis_grid = QtGui.QGridLayout()
|
||||
layout.addLayout(self.mirror_axis_grid)
|
||||
|
||||
axislabel = QtGui.QLabel('Axis:')
|
||||
axislabel.setToolTip(
|
||||
"Mirror axis parallel to the x or y axis."
|
||||
)
|
||||
self.mirror_axis_grid.addWidget(axislabel, 0, 0)
|
||||
|
||||
self.mirror_axis_radio = RadioSet([{'label': 'X', 'value': 'X'},
|
||||
{'label': 'Y', 'value': 'Y'}])
|
||||
self.mirror_axis_radio.set_value('Y')
|
||||
self.mirror_axis_grid.addWidget(self.mirror_axis_radio, 0, 1)
|
||||
|
||||
self.mirror_auto_center_cb = FCCheckBox(label='Center axis automatically')
|
||||
self.mirror_auto_center_cb.setToolTip(
|
||||
"Place the mirror axis on the middle of the object."
|
||||
)
|
||||
self.mirror_auto_center_cb.set_value(True)
|
||||
layout.addWidget(self.mirror_auto_center_cb)
|
||||
|
||||
self.mirror_button = QtGui.QPushButton('Mirror')
|
||||
self.mirror_button.setToolTip(
|
||||
"Perform the mirror operation."
|
||||
)
|
||||
layout.addWidget(self.mirror_button)
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
|
||||
class CNCObjectUI(ObjectUI):
|
||||
"""
|
||||
User interface for CNCJob objects.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Creates the user interface for CNCJob objects. GUI elements should
|
||||
be placed in ``self.custom_box`` to preserve the layout.
|
||||
"""
|
||||
|
||||
ObjectUI.__init__(self, title='CNC Job Object', icon_file='share/cnc32.png', parent=parent)
|
||||
|
||||
# Scale and offset are not available for CNCJob objects.
|
||||
# Hiding from the GUI.
|
||||
for i in range(0, self.scale_grid.count()):
|
||||
self.scale_grid.itemAt(i).widget().hide()
|
||||
self.scale_label.hide()
|
||||
self.scale_button.hide()
|
||||
|
||||
for i in range(0, self.offset_grid.count()):
|
||||
self.offset_grid.itemAt(i).widget().hide()
|
||||
self.offset_label.hide()
|
||||
self.offset_button.hide()
|
||||
self.auto_offset_button.hide()
|
||||
|
||||
self.mirror_label.hide()
|
||||
for i in range(0, self.mirror_axis_grid.count()):
|
||||
self.mirror_axis_grid.itemAt(i).widget().hide()
|
||||
self.mirror_auto_center_cb.hide()
|
||||
self.mirror_button.hide()
|
||||
|
||||
## Plot options
|
||||
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 = 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)
|
||||
|
||||
# Update plot button
|
||||
self.updateplot_button = QtGui.QPushButton('Update Plot')
|
||||
self.updateplot_button.setToolTip(
|
||||
"Update the plot."
|
||||
)
|
||||
self.custom_box.addWidget(self.updateplot_button)
|
||||
|
||||
##################
|
||||
## 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.custom_box.addWidget(self.export_gcode_label)
|
||||
|
||||
# Prepend text to Gerber
|
||||
prependlabel = QtGui.QLabel('Prepend to G-Code:')
|
||||
prependlabel.setToolTip(
|
||||
"Type here any G-Code commands you would\n"
|
||||
"like to add to the beginning of the generated file."
|
||||
)
|
||||
self.custom_box.addWidget(prependlabel)
|
||||
|
||||
self.prepend_text = FCTextArea()
|
||||
self.custom_box.addWidget(self.prepend_text)
|
||||
|
||||
# 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)
|
||||
|
||||
processorlabel = QtGui.QLabel('Postprocessing-Script:')
|
||||
processorlabel.setToolTip(
|
||||
"Enter a Postprocessing Script here.\n"
|
||||
"It gets applied to the G-Code after it\n"
|
||||
"is generated."
|
||||
)
|
||||
self.custom_box.addWidget(processorlabel)
|
||||
self.process_script = FCTextArea()
|
||||
self.custom_box.addWidget(self.process_script)
|
||||
|
||||
|
||||
# Dwell
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.addLayout(grid1)
|
||||
|
||||
dwelllabel = QtGui.QLabel('Dwell:')
|
||||
dwelllabel.setToolTip(
|
||||
"Pause to allow the spindle to reach its\n"
|
||||
"speed before cutting."
|
||||
)
|
||||
dwelltime = QtGui.QLabel('Duration [sec.]:')
|
||||
dwelltime.setToolTip(
|
||||
"Number of second to dwell."
|
||||
)
|
||||
self.dwell_cb = FCCheckBox()
|
||||
self.dwelltime_entry = FCEntry()
|
||||
grid1.addWidget(dwelllabel, 0, 0)
|
||||
grid1.addWidget(self.dwell_cb, 0, 1)
|
||||
grid1.addWidget(dwelltime, 1, 0)
|
||||
grid1.addWidget(self.dwelltime_entry, 1, 1)
|
||||
|
||||
# GO Button
|
||||
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.addWidget(self.export_gcode_button)
|
||||
|
||||
|
||||
class GeometryObjectUI(ObjectUI):
|
||||
"""
|
||||
User interface for Geometry objects.
|
||||
"""
|
||||
|
||||
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 = 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.setToolTip(
|
||||
"Plot (show) this object."
|
||||
)
|
||||
self.custom_box.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.custom_box.addWidget(self.cncjob_label)
|
||||
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
# Spindlespeed
|
||||
spdlabel = QtGui.QLabel('Spindle speed:')
|
||||
spdlabel.setToolTip(
|
||||
"Speed of the spindle\n"
|
||||
"in RPM (optional)"
|
||||
)
|
||||
grid1.addWidget(spdlabel, 4, 0)
|
||||
self.cncspindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid1.addWidget(self.cncspindlespeed_entry, 4, 1)
|
||||
|
||||
# Multi-pass
|
||||
mpasslabel = QtGui.QLabel('Multi-Depth:')
|
||||
mpasslabel.setToolTip(
|
||||
"Use multiple passes to limit\n"
|
||||
"the cut depth in each pass. Will\n"
|
||||
"cut multiple times until Cut Z is\n"
|
||||
"reached."
|
||||
)
|
||||
grid1.addWidget(mpasslabel, 5, 0)
|
||||
self.mpass_cb = FCCheckBox()
|
||||
grid1.addWidget(self.mpass_cb, 5, 1)
|
||||
|
||||
maxdepthlabel = QtGui.QLabel('Depth/pass:')
|
||||
maxdepthlabel.setToolTip(
|
||||
"Depth of each pass (positive)."
|
||||
)
|
||||
grid1.addWidget(maxdepthlabel, 6, 0)
|
||||
self.maxdepth_entry = LengthEntry()
|
||||
grid1.addWidget(self.maxdepth_entry, 6, 1)
|
||||
|
||||
self.ois_mpass = OptionalInputSection(self.mpass_cb, [self.maxdepth_entry])
|
||||
|
||||
# Button
|
||||
self.generate_cnc_button = QtGui.QPushButton('Generate')
|
||||
self.generate_cnc_button.setToolTip(
|
||||
"Generate the CNC Job object."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_cnc_button)
|
||||
|
||||
#------------------------------
|
||||
# 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.addWidget(self.paint_label)
|
||||
|
||||
grid2 = QtGui.QGridLayout()
|
||||
self.custom_box.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, 2, 1)
|
||||
|
||||
# Method
|
||||
methodlabel = QtGui.QLabel('Method:')
|
||||
methodlabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
|
||||
methodlabel.setToolTip(
|
||||
"Algorithm to paint the polygon:<BR>"
|
||||
"<B>Standard</B>: Fixed step inwards.<BR>"
|
||||
"<B>Seed-based</B>: Outwards from seed."
|
||||
)
|
||||
grid2.addWidget(methodlabel, 3, 0)
|
||||
self.paintmethod_combo = RadioSet([
|
||||
{"label": "Standard", "value": "standard"},
|
||||
{"label": "Seed-based", "value": "seed"},
|
||||
{"label": "Straight lines", "value": "lines"}
|
||||
], orientation='vertical')
|
||||
grid2.addWidget(self.paintmethod_combo, 3, 1)
|
||||
|
||||
# Connect lines
|
||||
pathconnectlabel = QtGui.QLabel("Connect:")
|
||||
pathconnectlabel.setToolTip(
|
||||
"Draw lines between resulting\n"
|
||||
"segments to minimize tool lifts."
|
||||
)
|
||||
grid2.addWidget(pathconnectlabel, 4, 0)
|
||||
self.pathconnect_cb = FCCheckBox()
|
||||
grid2.addWidget(self.pathconnect_cb, 4, 1)
|
||||
|
||||
contourlabel = QtGui.QLabel("Contour:")
|
||||
contourlabel.setToolTip(
|
||||
"Cut around the perimeter of the polygon\n"
|
||||
"to trim rough edges."
|
||||
)
|
||||
grid2.addWidget(contourlabel, 5, 0)
|
||||
self.paintcontour_cb = FCCheckBox()
|
||||
grid2.addWidget(self.paintcontour_cb, 5, 1)
|
||||
|
||||
# Polygon selection
|
||||
selectlabel = QtGui.QLabel('Selection:')
|
||||
selectlabel.setToolTip(
|
||||
"How to select the polygons to paint."
|
||||
)
|
||||
grid2.addWidget(selectlabel, 6, 0)
|
||||
#grid3 = QtGui.QGridLayout()
|
||||
self.selectmethod_combo = RadioSet([
|
||||
{"label": "Single", "value": "single"},
|
||||
{"label": "All", "value": "all"},
|
||||
#{"label": "Rectangle", "value": "rectangle"}
|
||||
])
|
||||
grid2.addWidget(self.selectmethod_combo, 6, 1)
|
||||
|
||||
# GO Button
|
||||
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.addWidget(self.generate_paint_button)
|
||||
|
||||
|
||||
class ExcellonObjectUI(ObjectUI):
|
||||
"""
|
||||
User interface for Excellon objects.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
ObjectUI.__init__(self, title='Excellon Object',
|
||||
icon_file='share/drill32.png',
|
||||
parent=parent)
|
||||
|
||||
#### Plot options ####
|
||||
|
||||
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.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)
|
||||
|
||||
#### 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 = QtGui.QLabel('<b>Create CNC Job</b>')
|
||||
self.cncjob_label.setToolTip(
|
||||
"Create a CNC Job object\n"
|
||||
"for this drill object."
|
||||
)
|
||||
self.custom_box.addWidget(self.cncjob_label)
|
||||
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
# Tool change:
|
||||
toolchlabel = QtGui.QLabel("Tool change:")
|
||||
toolchlabel.setToolTip(
|
||||
"Include tool-change sequence\n"
|
||||
"in G-Code (Pause for tool change)."
|
||||
)
|
||||
self.toolchange_cb = FCCheckBox()
|
||||
grid1.addWidget(toolchlabel, 3, 0)
|
||||
grid1.addWidget(self.toolchange_cb, 3, 1)
|
||||
|
||||
# Tool change Z:
|
||||
toolchzlabel = QtGui.QLabel("Tool change Z:")
|
||||
toolchzlabel.setToolTip(
|
||||
"Z-axis position (height) for\n"
|
||||
"tool change."
|
||||
)
|
||||
grid1.addWidget(toolchzlabel, 4, 0)
|
||||
self.toolchangez_entry = LengthEntry()
|
||||
grid1.addWidget(self.toolchangez_entry, 4, 1)
|
||||
self.ois_tcz = OptionalInputSection(self.toolchange_cb, [self.toolchangez_entry])
|
||||
|
||||
# Spindlespeed
|
||||
spdlabel = QtGui.QLabel('Spindle speed:')
|
||||
spdlabel.setToolTip(
|
||||
"Speed of the spindle\n"
|
||||
"in RPM (optional)"
|
||||
)
|
||||
grid1.addWidget(spdlabel, 5, 0)
|
||||
self.spindlespeed_entry = IntEntry(allow_empty=True)
|
||||
grid1.addWidget(self.spindlespeed_entry, 5, 1)
|
||||
|
||||
choose_tools_label = QtGui.QLabel(
|
||||
"Select from the tools section above\n"
|
||||
"the tools you want to include."
|
||||
)
|
||||
self.custom_box.addWidget(choose_tools_label)
|
||||
|
||||
self.generate_cnc_button = QtGui.QPushButton('Generate')
|
||||
self.generate_cnc_button.setToolTip(
|
||||
"Generate the CNC Job."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_cnc_button)
|
||||
|
||||
#### Milling Holes ####
|
||||
self.mill_hole_label = QtGui.QLabel('<b>Mill Holes</b>')
|
||||
self.mill_hole_label.setToolTip(
|
||||
"Create Geometry for milling holes."
|
||||
)
|
||||
self.custom_box.addWidget(self.mill_hole_label)
|
||||
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.addLayout(grid1)
|
||||
tdlabel = QtGui.QLabel('Tool dia:')
|
||||
tdlabel.setToolTip(
|
||||
"Diameter of the cutting tool."
|
||||
)
|
||||
grid1.addWidget(tdlabel, 0, 0)
|
||||
self.tooldia_entry = LengthEntry()
|
||||
grid1.addWidget(self.tooldia_entry, 0, 1)
|
||||
|
||||
choose_tools_label2 = QtGui.QLabel(
|
||||
"Select from the tools section above\n"
|
||||
"the tools you want to include."
|
||||
)
|
||||
self.custom_box.addWidget(choose_tools_label2)
|
||||
|
||||
self.generate_milling_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.generate_milling_button.setToolTip(
|
||||
"Create the Geometry Object\n"
|
||||
"for milling toolpaths."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_milling_button)
|
||||
|
||||
|
||||
class GerberObjectUI(ObjectUI):
|
||||
"""
|
||||
User interface for Gerber objects.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
ObjectUI.__init__(self, title='Gerber Object', parent=parent)
|
||||
|
||||
## Plot options
|
||||
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_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.custom_box.addWidget(self.isolation_routing_label)
|
||||
|
||||
grid1 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
# combine all passes CB
|
||||
self.combine_passes_cb = FCCheckBox(label='Combine Passes')
|
||||
self.combine_passes_cb.setToolTip(
|
||||
"Combine all passes into one object"
|
||||
)
|
||||
grid1.addWidget(self.combine_passes_cb, 3, 0)
|
||||
|
||||
|
||||
self.generate_iso_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.generate_iso_button.setToolTip(
|
||||
"Create the Geometry Object\n"
|
||||
"for isolation routing."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_iso_button)
|
||||
|
||||
## 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.custom_box.addWidget(self.board_cutout_label)
|
||||
|
||||
grid2 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
self.generate_cutout_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.generate_cutout_button.setToolTip(
|
||||
"Generate the geometry for\n"
|
||||
"the board cutout."
|
||||
)
|
||||
self.custom_box.addWidget(self.generate_cutout_button)
|
||||
|
||||
## 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.custom_box.addWidget(self.noncopper_label)
|
||||
|
||||
grid3 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
self.generate_noncopper_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.custom_box.addWidget(self.generate_noncopper_button)
|
||||
|
||||
## Bounding box
|
||||
self.boundingbox_label = QtGui.QLabel('<b>Bounding Box:</b>')
|
||||
self.custom_box.addWidget(self.boundingbox_label)
|
||||
|
||||
grid4 = QtGui.QGridLayout()
|
||||
self.custom_box.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)
|
||||
|
||||
self.generate_bb_button = QtGui.QPushButton('Generate Geometry')
|
||||
self.generate_bb_button.setToolTip(
|
||||
"Generate the Geometry object."
|
||||
)
|
||||
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()
|
526
PlotCanvas.py
|
@ -1,526 +0,0 @@
|
|||
############################################################
|
||||
# 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
|
||||
|
||||
# Prevent conflict with Qt5 and above.
|
||||
from matplotlib import use as mpl_use
|
||||
mpl_use("Qt4Agg")
|
||||
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||||
import FlatCAMApp
|
||||
import logging
|
||||
|
||||
log = logging.getLogger('base')
|
||||
|
||||
|
||||
class CanvasCache(QtCore.QObject):
|
||||
"""
|
||||
|
||||
Case story #1:
|
||||
|
||||
1) No objects in the project.
|
||||
2) Object is created (new_object() emits object_created(obj)).
|
||||
on_object_created() adds (i) object to collection and emits
|
||||
(ii) new_object_available() then calls (iii) object.plot()
|
||||
3) object.plot() creates axes if necessary on
|
||||
app.collection.figure. Then plots on it.
|
||||
4) Plots on a cache-size canvas (in background).
|
||||
5) Plot completes. Bitmap is generated.
|
||||
6) Visible canvas is painted.
|
||||
|
||||
"""
|
||||
|
||||
# Signals:
|
||||
# A bitmap is ready to be displayed.
|
||||
new_screen = QtCore.pyqtSignal()
|
||||
|
||||
def __init__(self, plotcanvas, app, dpi=50):
|
||||
|
||||
super(CanvasCache, self).__init__()
|
||||
|
||||
self.app = app
|
||||
|
||||
self.plotcanvas = plotcanvas
|
||||
self.dpi = dpi
|
||||
|
||||
self.figure = Figure(dpi=dpi)
|
||||
|
||||
self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0)
|
||||
self.axes.set_frame_on(False)
|
||||
self.axes.set_xticks([])
|
||||
self.axes.set_yticks([])
|
||||
|
||||
self.canvas = FigureCanvasAgg(self.figure)
|
||||
|
||||
self.cache = None
|
||||
|
||||
def run(self):
|
||||
|
||||
log.debug("CanvasCache Thread Started!")
|
||||
|
||||
self.plotcanvas.update_screen_request.connect(self.on_update_req)
|
||||
|
||||
self.app.new_object_available.connect(self.on_new_object_available)
|
||||
|
||||
def on_update_req(self, extents):
|
||||
"""
|
||||
Event handler for an updated display request.
|
||||
|
||||
:param extents: [xmin, xmax, ymin, ymax, zoom(optional)]
|
||||
"""
|
||||
|
||||
log.debug("Canvas update requested: %s" % str(extents))
|
||||
|
||||
# Note: This information below might be out of date. Establish
|
||||
# a protocol regarding when to change the canvas in the main
|
||||
# thread and when to check these values here in the background,
|
||||
# or pass this data in the signal (safer).
|
||||
log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize()))
|
||||
log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density()))
|
||||
|
||||
# Move the requested screen portion to the main thread
|
||||
# and inform about the update:
|
||||
|
||||
self.new_screen.emit()
|
||||
|
||||
# Continue to update the cache.
|
||||
|
||||
def on_new_object_available(self):
|
||||
|
||||
log.debug("A new object is available. Should plot it!")
|
||||
|
||||
|
||||
class PlotCanvas(QtCore.QObject):
|
||||
"""
|
||||
Class handling the plotting area in the application.
|
||||
"""
|
||||
|
||||
# Signals:
|
||||
# Request for new bitmap to display. The parameter
|
||||
# is a list with [xmin, xmax, ymin, ymax, zoom(optional)]
|
||||
update_screen_request = QtCore.pyqtSignal(list)
|
||||
|
||||
def __init__(self, container, app):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
super(PlotCanvas, self).__init__()
|
||||
|
||||
self.app = app
|
||||
|
||||
# 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)
|
||||
self.axes.axhline(color='Black')
|
||||
self.axes.axvline(color='Black')
|
||||
|
||||
# The canvas is the top level container (FigureCanvasQTAgg)
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
# self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
# self.canvas.setFocus()
|
||||
|
||||
#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.addWidget(self.canvas) # Qt
|
||||
|
||||
# Copy a bitmap of the canvas for quick animation.
|
||||
# Update every time the canvas is re-drawn.
|
||||
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
|
||||
|
||||
### Bitmap Cache
|
||||
self.cache = CanvasCache(self, self.app)
|
||||
self.cache_thread = QtCore.QThread()
|
||||
self.cache.moveToThread(self.cache_thread)
|
||||
super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run)
|
||||
# self.connect()
|
||||
self.cache_thread.start()
|
||||
self.cache.new_screen.connect(self.on_new_screen)
|
||||
|
||||
# Events
|
||||
self.canvas.mpl_connect('button_press_event', self.on_mouse_press)
|
||||
self.canvas.mpl_connect('button_release_event', self.on_mouse_release)
|
||||
self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
|
||||
#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)
|
||||
self.canvas.mpl_connect('draw_event', self.on_draw)
|
||||
|
||||
self.mouse = [0, 0]
|
||||
self.key = None
|
||||
|
||||
self.pan_axes = []
|
||||
self.panning = False
|
||||
|
||||
def on_new_screen(self):
|
||||
|
||||
log.debug("Cache updated the screen!")
|
||||
|
||||
def on_key_down(self, event):
|
||||
"""
|
||||
|
||||
:param event:
|
||||
:return:
|
||||
"""
|
||||
FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key))
|
||||
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 Qt 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.draw_idle()
|
||||
|
||||
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])
|
||||
|
||||
# Sync re-draw to proper paint on form resize
|
||||
self.canvas.draw()
|
||||
|
||||
##### Temporary place-holder for cached update #####
|
||||
self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
|
||||
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))
|
||||
|
||||
# Async re-draw
|
||||
self.canvas.draw_idle()
|
||||
|
||||
##### Temporary place-holder for cached update #####
|
||||
self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
|
||||
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.draw_idle()
|
||||
|
||||
##### Temporary place-holder for cached update #####
|
||||
self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
|
||||
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, event):
|
||||
"""
|
||||
Scroll event handler.
|
||||
|
||||
:param event: Event object containing the event information.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# So it can receive key presses
|
||||
# self.canvas.grab_focus()
|
||||
self.canvas.setFocus()
|
||||
|
||||
# Event info
|
||||
# z, direction = event.get_scroll_direction()
|
||||
|
||||
if self.key is None:
|
||||
|
||||
if event.button == 'up':
|
||||
self.zoom(1.5, self.mouse)
|
||||
else:
|
||||
self.zoom(1 / 1.5, self.mouse)
|
||||
return
|
||||
|
||||
if self.key == 'shift':
|
||||
|
||||
if event.button == 'up':
|
||||
self.pan(0.3, 0)
|
||||
else:
|
||||
self.pan(-0.3, 0)
|
||||
return
|
||||
|
||||
if self.key == 'control':
|
||||
|
||||
if event.button == 'up':
|
||||
self.pan(0, 0.3)
|
||||
else:
|
||||
self.pan(0, -0.3)
|
||||
return
|
||||
|
||||
def on_mouse_press(self, event):
|
||||
|
||||
# Check for middle mouse button press
|
||||
if event.button == self.app.mouse_pan_button:
|
||||
|
||||
# Prepare axes for pan (using 'matplotlib' pan function)
|
||||
self.pan_axes = []
|
||||
for a in self.figure.get_axes():
|
||||
if (event.x is not None and event.y is not None and a.in_axes(event) and
|
||||
a.get_navigate() and a.can_pan()):
|
||||
a.start_pan(event.x, event.y, 1)
|
||||
self.pan_axes.append(a)
|
||||
|
||||
# Set pan view flag
|
||||
if len(self.pan_axes) > 0: self.panning = True;
|
||||
|
||||
def on_mouse_release(self, event):
|
||||
|
||||
# Check for middle mouse button release to complete pan procedure
|
||||
if event.button == self.app.mouse_pan_button:
|
||||
for a in self.pan_axes:
|
||||
a.end_pan()
|
||||
|
||||
# Clear pan flag
|
||||
self.panning = False
|
||||
|
||||
def on_mouse_move(self, event):
|
||||
"""
|
||||
Mouse movement event hadler. Stores the coordinates. Updates view on pan.
|
||||
|
||||
:param event: Contains information about the event.
|
||||
:return: None
|
||||
"""
|
||||
self.mouse = [event.xdata, event.ydata]
|
||||
|
||||
# Update pan view on mouse move
|
||||
if self.panning is True:
|
||||
for a in self.pan_axes:
|
||||
a.drag_pan(1, event.key, event.x, event.y)
|
||||
|
||||
# Async re-draw (redraws only on thread idle state, uses timer on backend)
|
||||
self.canvas.draw_idle()
|
||||
|
||||
##### Temporary place-holder for cached update #####
|
||||
self.update_screen_request.emit([0, 0, 0, 0, 0])
|
||||
|
||||
def on_draw(self, renderer):
|
||||
|
||||
# Store background on canvas redraw
|
||||
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
|
||||
|
||||
def get_axes_pixelsize(self):
|
||||
"""
|
||||
Axes size in pixels.
|
||||
|
||||
:return: Pixel width and height
|
||||
:rtype: tuple
|
||||
"""
|
||||
bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
|
||||
width, height = bbox.width, bbox.height
|
||||
width *= self.figure.dpi
|
||||
height *= self.figure.dpi
|
||||
return width, height
|
||||
|
||||
def get_density(self):
|
||||
"""
|
||||
Returns unit length per pixel on horizontal
|
||||
and vertical axes.
|
||||
|
||||
:return: X and Y density
|
||||
:rtype: tuple
|
||||
"""
|
||||
xpx, ypx = self.get_axes_pixelsize()
|
||||
|
||||
xmin, xmax = self.axes.get_xlim()
|
||||
ymin, ymax = self.axes.get_ylim()
|
||||
width = xmax - xmin
|
||||
height = ymax - ymin
|
||||
|
||||
return width / xpx, height / ypx
|
|
@ -1,8 +0,0 @@
|
|||
FlatCAM: 2D Computer-Aided PCB Manufacturing
|
||||
============================================
|
||||
|
||||
(c) 2014-2016 Juan Pablo Caram
|
||||
|
||||
FlatCAM is a program for preparing CNC jobs for making PCBs on a CNC router.
|
||||
Among other things, it can take a Gerber file generated by your favorite PCB
|
||||
CAD program, and create G-Code for Isolation routing.
|
198
ToolDblSided.py
|
@ -1,198 +0,0 @@
|
|||
from PyQt4 import QtGui
|
||||
from GUIElements import RadioSet, EvalEntry, LengthEntry
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
#from FlatCAMObj import FlatCAMGerber, FlatCAMExcellon
|
||||
from FlatCAMObj import *
|
||||
from shapely.geometry import Point
|
||||
from shapely import affinity
|
||||
|
||||
|
||||
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.model)
|
||||
self.botlay_label = QtGui.QLabel("Bottom Layer:")
|
||||
self.botlay_label.setToolTip(
|
||||
"Layer to be mirrorer."
|
||||
)
|
||||
# form_layout.addRow("Bottom Layer:", self.object_combo)
|
||||
form_layout.addRow(self.botlay_label, self.object_combo)
|
||||
|
||||
## Axis
|
||||
self.mirror_axis = RadioSet([{'label': 'X', 'value': 'X'},
|
||||
{'label': 'Y', 'value': 'Y'}])
|
||||
self.mirax_label = QtGui.QLabel("Mirror Axis:")
|
||||
self.mirax_label.setToolTip(
|
||||
"Mirror vertically (X) or horizontally (Y)."
|
||||
)
|
||||
# form_layout.addRow("Mirror Axis:", self.mirror_axis)
|
||||
form_layout.addRow(self.mirax_label, self.mirror_axis)
|
||||
|
||||
## Axis Location
|
||||
self.axis_location = RadioSet([{'label': 'Point', 'value': 'point'},
|
||||
{'label': 'Box', 'value': 'box'}])
|
||||
self.axloc_label = QtGui.QLabel("Axis Location:")
|
||||
self.axloc_label.setToolTip(
|
||||
"The axis should pass through a <b>point</b> or cut "
|
||||
"a specified <b>box</b> (in a Geometry object) in "
|
||||
"the middle."
|
||||
)
|
||||
# form_layout.addRow("Axis Location:", self.axis_location)
|
||||
form_layout.addRow(self.axloc_label, self.axis_location)
|
||||
|
||||
## Point/Box
|
||||
self.point_box_container = QtGui.QVBoxLayout()
|
||||
self.pb_label = QtGui.QLabel("Point/Box:")
|
||||
self.pb_label.setToolTip(
|
||||
"Specify the point (x, y) through which the mirror axis "
|
||||
"passes or the Geometry object containing a rectangle "
|
||||
"that the mirror axis cuts in half."
|
||||
)
|
||||
# form_layout.addRow("Point/Box:", self.point_box_container)
|
||||
form_layout.addRow(self.pb_label, 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.model)
|
||||
self.point_box_container.addWidget(self.box_combo)
|
||||
self.box_combo.hide()
|
||||
|
||||
## Alignment holes
|
||||
self.alignment_holes = EvalEntry()
|
||||
self.ah_label = QtGui.QLabel("Alignment Holes:")
|
||||
self.ah_label.setToolTip(
|
||||
"Alignment holes (x1, y1), (x2, y2), ... "
|
||||
"on one side of the mirror axis."
|
||||
)
|
||||
form_layout.addRow(self.ah_label, self.alignment_holes)
|
||||
|
||||
## Drill diameter for alignment holes
|
||||
self.drill_dia = LengthEntry()
|
||||
self.dd_label = QtGui.QLabel("Drill diam.:")
|
||||
self.dd_label.setToolTip(
|
||||
"Diameter of the drill for the "
|
||||
"alignment holes."
|
||||
)
|
||||
form_layout.addRow(self.dd_label, self.drill_dia)
|
||||
|
||||
## Buttons
|
||||
hlay = QtGui.QHBoxLayout()
|
||||
self.layout.addLayout(hlay)
|
||||
hlay.addStretch()
|
||||
self.create_alignment_hole_button = QtGui.QPushButton("Create Alignment Drill")
|
||||
self.create_alignment_hole_button.setToolTip(
|
||||
"Creates an Excellon Object containing the "
|
||||
"specified alignment holes and their mirror "
|
||||
"images."
|
||||
)
|
||||
self.mirror_object_button = QtGui.QPushButton("Mirror Object")
|
||||
self.mirror_object_button.setToolTip(
|
||||
"Mirrors (flips) the specified object around "
|
||||
"the specified axis. Does not create a new "
|
||||
"object, but modifies it."
|
||||
)
|
||||
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()
|
||||
holes = eval('[{}]'.format(self.alignment_holes.text()))
|
||||
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) and \
|
||||
not isinstance(fcobj, FlatCAMGeometry):
|
||||
self.app.inform.emit("ERROR: Only Gerber, Excellon and Geometry 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)
|
||||
|
||||
# Ensure that the selected object will display when it is mirrored.
|
||||
# If an object's plot setting is False it will still be available in
|
||||
# the combo box. If the plot is not enforced to True then the user
|
||||
# gets no feedback of the operation.
|
||||
fcobj.options["plot"] = True;
|
||||
|
||||
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()
|
|
@ -1,74 +0,0 @@
|
|||
from PyQt4 import QtGui
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from copy import copy
|
||||
from math import sqrt
|
||||
|
||||
|
||||
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, icon=None, separator=None, **kwargs):
|
||||
FlatCAMTool.install(self, icon, separator, **kwargs)
|
||||
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 == 'r':
|
||||
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()
|
319
ToolTransform.py
|
@ -1,319 +0,0 @@
|
|||
from PyQt4 import QtGui, QtCore
|
||||
from PyQt4 import Qt
|
||||
from GUIElements import FCEntry, FCButton
|
||||
from FlatCAMTool import FlatCAMTool
|
||||
from camlib import *
|
||||
|
||||
|
||||
class ToolTransform(FlatCAMTool):
|
||||
|
||||
toolName = "Object Transformation"
|
||||
rotateName = "Rotate Transformation"
|
||||
skewName = "Skew/Shear Transformation"
|
||||
flipName = "Flip Transformation"
|
||||
|
||||
def __init__(self, app):
|
||||
FlatCAMTool.__init__(self, app)
|
||||
|
||||
self.transform_lay = QtGui.QVBoxLayout()
|
||||
self.layout.addLayout(self.transform_lay)
|
||||
## Title
|
||||
title_label = QtGui.QLabel("<font size=4><b>%s</b></font><br>" % self.toolName)
|
||||
self.transform_lay.addWidget(title_label)
|
||||
|
||||
self.empty_label = QtGui.QLabel("")
|
||||
self.empty_label.setFixedWidth(80)
|
||||
self.empty_label1 = QtGui.QLabel("")
|
||||
self.empty_label1.setFixedWidth(80)
|
||||
self.empty_label2 = QtGui.QLabel("")
|
||||
self.empty_label2.setFixedWidth(80)
|
||||
self.transform_lay.addWidget(self.empty_label)
|
||||
|
||||
## Rotate Title
|
||||
rotate_title_label = QtGui.QLabel("<font size=3><b>%s</b></font>" % self.rotateName)
|
||||
self.transform_lay.addWidget(rotate_title_label)
|
||||
|
||||
## Form Layout
|
||||
form_layout = QtGui.QFormLayout()
|
||||
self.transform_lay.addLayout(form_layout)
|
||||
|
||||
self.rotate_entry = FCEntry()
|
||||
self.rotate_entry.setFixedWidth(70)
|
||||
self.rotate_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.rotate_label = QtGui.QLabel("Angle Rotation:")
|
||||
self.rotate_label.setToolTip(
|
||||
"Angle for Rotation action, in degrees.\n"
|
||||
"Float number between -360 and 359.\n"
|
||||
"Positive numbers for CW motion.\n"
|
||||
"Negative numbers for CCW motion."
|
||||
)
|
||||
self.rotate_label.setFixedWidth(80)
|
||||
|
||||
self.rotate_button = FCButton()
|
||||
self.rotate_button.set_value("Rotate")
|
||||
self.rotate_button.setToolTip(
|
||||
"Rotate the selected object(s).\n"
|
||||
"The point of reference is the middle of\n"
|
||||
"the bounding box for all selected objects.\n"
|
||||
)
|
||||
self.rotate_button.setFixedWidth(70)
|
||||
|
||||
form_layout.addRow(self.rotate_label, self.rotate_entry)
|
||||
form_layout.addRow(self.empty_label, self.rotate_button)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label1)
|
||||
|
||||
## Skew Title
|
||||
skew_title_label = QtGui.QLabel("<font size=3><b>%s</b></font>" % self.skewName)
|
||||
self.transform_lay.addWidget(skew_title_label)
|
||||
|
||||
## Form Layout
|
||||
form1_layout = QtGui.QFormLayout()
|
||||
self.transform_lay.addLayout(form1_layout)
|
||||
|
||||
self.skewx_entry = FCEntry()
|
||||
self.skewx_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.skewx_entry.setFixedWidth(70)
|
||||
self.skewx_label = QtGui.QLabel("Angle SkewX:")
|
||||
self.skewx_label.setToolTip(
|
||||
"Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359."
|
||||
)
|
||||
self.skewx_label.setFixedWidth(80)
|
||||
|
||||
self.skewx_button = FCButton()
|
||||
self.skewx_button.set_value("Skew_X")
|
||||
self.skewx_button.setToolTip(
|
||||
"Skew/shear the selected object(s).\n"
|
||||
"The point of reference is the middle of\n"
|
||||
"the bounding box for all selected objects.\n")
|
||||
self.skewx_button.setFixedWidth(70)
|
||||
|
||||
self.skewy_entry = FCEntry()
|
||||
self.skewy_entry.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.skewy_entry.setFixedWidth(70)
|
||||
self.skewy_label = QtGui.QLabel("Angle SkewY:")
|
||||
self.skewy_label.setToolTip(
|
||||
"Angle for Skew action, in degrees.\n"
|
||||
"Float number between -360 and 359."
|
||||
)
|
||||
self.skewy_label.setFixedWidth(80)
|
||||
|
||||
self.skewy_button = FCButton()
|
||||
self.skewy_button.set_value("Skew_Y")
|
||||
self.skewy_button.setToolTip(
|
||||
"Skew/shear the selected object(s).\n"
|
||||
"The point of reference is the middle of\n"
|
||||
"the bounding box for all selected objects.\n")
|
||||
self.skewy_button.setFixedWidth(70)
|
||||
|
||||
form1_layout.addRow(self.skewx_label, self.skewx_entry)
|
||||
form1_layout.addRow(self.empty_label, self.skewx_button)
|
||||
form1_layout.addRow(self.skewy_label, self.skewy_entry)
|
||||
form1_layout.addRow(self.empty_label, self.skewy_button)
|
||||
|
||||
self.transform_lay.addWidget(self.empty_label2)
|
||||
|
||||
## Flip Title
|
||||
flip_title_label = QtGui.QLabel("<font size=3><b>%s</b></font>" % self.flipName)
|
||||
self.transform_lay.addWidget(flip_title_label)
|
||||
|
||||
## Form Layout
|
||||
form2_layout = QtGui.QFormLayout()
|
||||
self.transform_lay.addLayout(form2_layout)
|
||||
|
||||
self.flipx_button = FCButton()
|
||||
self.flipx_button.set_value("Flip_X")
|
||||
self.flipx_button.setToolTip(
|
||||
"Flip the selected object(s) over the X axis.\n"
|
||||
"Does not create a new object.\n "
|
||||
)
|
||||
self.flipx_button.setFixedWidth(70)
|
||||
|
||||
self.flipy_button = FCButton()
|
||||
self.flipy_button.set_value("Flip_Y")
|
||||
self.flipy_button.setToolTip(
|
||||
"Flip the selected object(s) over the X axis.\n"
|
||||
"Does not create a new object.\n "
|
||||
)
|
||||
self.flipy_button.setFixedWidth(70)
|
||||
|
||||
form2_layout.setSpacing(16)
|
||||
form2_layout.addRow(self.flipx_button, self.flipy_button)
|
||||
|
||||
self.transform_lay.addStretch()
|
||||
|
||||
## Signals
|
||||
self.rotate_button.clicked.connect(self.on_rotate)
|
||||
self.skewx_button.clicked.connect(self.on_skewx)
|
||||
self.skewy_button.clicked.connect(self.on_skewy)
|
||||
self.flipx_button.clicked.connect(self.on_flipx)
|
||||
self.flipy_button.clicked.connect(self.on_flipy)
|
||||
|
||||
self.rotate_entry.returnPressed.connect(self.on_rotate)
|
||||
self.skewx_entry.returnPressed.connect(self.on_skewx)
|
||||
self.skewy_entry.returnPressed.connect(self.on_skewy)
|
||||
|
||||
## Initialize form
|
||||
self.rotate_entry.set_value('0')
|
||||
self.skewx_entry.set_value('0')
|
||||
self.skewy_entry.set_value('0')
|
||||
|
||||
def on_rotate(self):
|
||||
value = float(self.rotate_entry.get_value())
|
||||
self.on_rotate_action(value)
|
||||
return
|
||||
|
||||
def on_flipx(self):
|
||||
self.on_flip("Y")
|
||||
return
|
||||
|
||||
def on_flipy(self):
|
||||
self.on_flip("X")
|
||||
return
|
||||
|
||||
def on_skewx(self):
|
||||
value = float(self.skewx_entry.get_value())
|
||||
self.on_skew("X", value)
|
||||
return
|
||||
|
||||
def on_skewy(self):
|
||||
value = float(self.skewy_entry.get_value())
|
||||
self.on_skew("Y", value)
|
||||
return
|
||||
|
||||
def on_rotate_action(self, num):
|
||||
obj_list = self.app.collection.get_selected()
|
||||
xminlist = []
|
||||
yminlist = []
|
||||
xmaxlist = []
|
||||
ymaxlist = []
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit("WARNING: No object selected.")
|
||||
msg = "Please Select an object to rotate!"
|
||||
warningbox = QtGui.QMessageBox()
|
||||
warningbox.setText(msg)
|
||||
warningbox.setWindowTitle("Warning ...")
|
||||
warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
|
||||
warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
|
||||
warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
|
||||
warningbox.exec_()
|
||||
else:
|
||||
try:
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
xminlist.append(xmin)
|
||||
yminlist.append(ymin)
|
||||
xmaxlist.append(xmax)
|
||||
ymaxlist.append(ymax)
|
||||
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
xmaximal = max(xmaxlist)
|
||||
ymaximal = max(ymaxlist)
|
||||
|
||||
for sel_obj in obj_list:
|
||||
px = 0.5 * (xminimal + xmaximal)
|
||||
py = 0.5 * (yminimal + ymaximal)
|
||||
|
||||
sel_obj.rotate(-num, point=(px, py))
|
||||
sel_obj.plot()
|
||||
self.app.inform.emit('Object was rotated ...')
|
||||
except Exception as e:
|
||||
self.app.inform.emit("[ERROR] Due of %s, rotation movement was not executed." % str(e))
|
||||
raise
|
||||
|
||||
def on_flip(self, axis):
|
||||
obj_list = self.app.collection.get_selected()
|
||||
xminlist = []
|
||||
yminlist = []
|
||||
xmaxlist = []
|
||||
ymaxlist = []
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit("WARNING: No object selected.")
|
||||
msg = "Please Select an object to flip!"
|
||||
warningbox = QtGui.QMessageBox()
|
||||
warningbox.setText(msg)
|
||||
warningbox.setWindowTitle("Warning ...")
|
||||
warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
|
||||
warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
|
||||
warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
|
||||
warningbox.exec_()
|
||||
return
|
||||
else:
|
||||
try:
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
xminlist.append(xmin)
|
||||
yminlist.append(ymin)
|
||||
xmaxlist.append(xmax)
|
||||
ymaxlist.append(ymax)
|
||||
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
xmaximal = max(xmaxlist)
|
||||
ymaximal = max(ymaxlist)
|
||||
|
||||
px = 0.5 * (xminimal + xmaximal)
|
||||
py = 0.5 * (yminimal + ymaximal)
|
||||
|
||||
# execute mirroring
|
||||
for obj in obj_list:
|
||||
if axis is 'X':
|
||||
obj.mirror('X', [px, py])
|
||||
obj.plot()
|
||||
self.app.inform.emit('Flipped on the Y axis ...')
|
||||
elif axis is 'Y':
|
||||
obj.mirror('Y', [px, py])
|
||||
obj.plot()
|
||||
self.app.inform.emit('Flipped on the X axis ...')
|
||||
|
||||
except Exception as e:
|
||||
self.app.inform.emit("[ERROR] Due of %s, Flip action was not executed.")
|
||||
raise
|
||||
|
||||
def on_skew(self, axis, num):
|
||||
obj_list = self.app.collection.get_selected()
|
||||
xminlist = []
|
||||
yminlist = []
|
||||
|
||||
if not obj_list:
|
||||
self.app.inform.emit("WARNING: No object selected.")
|
||||
msg = "Please Select an object to skew/shear!"
|
||||
warningbox = QtGui.QMessageBox()
|
||||
warningbox.setText(msg)
|
||||
warningbox.setWindowTitle("Warning ...")
|
||||
warningbox.setWindowIcon(QtGui.QIcon('share/warning.png'))
|
||||
warningbox.setStandardButtons(QtGui.QMessageBox.Ok)
|
||||
warningbox.setDefaultButton(QtGui.QMessageBox.Ok)
|
||||
warningbox.exec_()
|
||||
else:
|
||||
try:
|
||||
# first get a bounding box to fit all
|
||||
for obj in obj_list:
|
||||
xmin, ymin, xmax, ymax = obj.bounds()
|
||||
xminlist.append(xmin)
|
||||
yminlist.append(ymin)
|
||||
|
||||
# get the minimum x,y and maximum x,y for all objects selected
|
||||
xminimal = min(xminlist)
|
||||
yminimal = min(yminlist)
|
||||
|
||||
for obj in obj_list:
|
||||
if axis is 'X':
|
||||
obj.skew(num, 0, point=(xminimal, yminimal))
|
||||
elif axis is 'Y':
|
||||
obj.skew(0, num, point=(xminimal, yminimal))
|
||||
obj.plot()
|
||||
self.app.inform.emit('Object was skewed on %s axis ...' % str(axis))
|
||||
except Exception as e:
|
||||
self.app.inform.emit("[ERROR] Due of %s, Skew action was not executed." % str(e))
|
||||
raise
|
||||
|
||||
# end of file
|
35
flatcam
|
@ -1,35 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
############################################################
|
||||
|
||||
import sys
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from FlatCAMApp import App
|
||||
|
||||
|
||||
def debug_trace():
|
||||
"""
|
||||
Set a tracepoint in the Python debugger that works with Qt
|
||||
:return: None
|
||||
"""
|
||||
from PyQt4.QtCore import pyqtRemoveInputHook
|
||||
#from pdb import set_trace
|
||||
pyqtRemoveInputHook()
|
||||
#set_trace()
|
||||
|
||||
debug_trace()
|
||||
|
||||
# All X11 calling should be thread safe otherwise we have strange issues
|
||||
# QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||
# NOTE: Never talk to the GUI from threads! This is why I commented the above.
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
QtCore.QDir.setSearchPaths("share", str(("share", "share/flatcam", "/usr/share/flatcam")));
|
||||
fc = App()
|
||||
sys.exit(app.exec_())
|
|
@ -1,8 +0,0 @@
|
|||
[Desktop Entry]
|
||||
Name=FlatCAM
|
||||
Comment=2D Computer-Aided PCB Manufacturing
|
||||
Exec=flatcam
|
||||
Icon=flatcam
|
||||
Keywords=cam;pcb;gerber
|
||||
Terminal=false
|
||||
Type=Application
|
|
@ -1,70 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Juan Pablo Caram (c) #
|
||||
# Date: 2/5/2014 #
|
||||
# MIT Licence #
|
||||
# #
|
||||
# Creates a portlable copy of FlatCAM, including Python #
|
||||
# itself and all dependencies. #
|
||||
# #
|
||||
# This is not an aid to install FlatCAM from source on #
|
||||
# Windows platforms. It is only useful when FlatCAM is up #
|
||||
# and running and ready to be packaged. #
|
||||
############################################################
|
||||
|
||||
# Files not needed: Qt, tk.dll, tcl.dll, tk/, tcl/, vtk/,
|
||||
# scipy.lib.lapack.flapack.pyd, scipy.lib.blas.fblas.pyd,
|
||||
# numpy.core._dotblas.pyd, scipy.sparse.sparsetools._bsr.pyd,
|
||||
# scipy.sparse.sparsetools._csr.pyd, scipy.sparse.sparsetools._csc.pyd,
|
||||
# scipy.sparse.sparsetools._coo.pyd
|
||||
|
||||
import os, site, sys
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
# this is done to solve the tkinter not being found (Python3)
|
||||
# still the DLL's need to be copied to the lib folder but the script can't do it
|
||||
PYTHON_INSTALL_DIR = os.path.dirname(os.path.dirname(os.__file__))
|
||||
os.environ['TCL_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tcl8.6')
|
||||
os.environ['TK_LIBRARY'] = os.path.join(PYTHON_INSTALL_DIR, 'tcl', 'tk8.6')
|
||||
|
||||
## Get the site-package folder, not everybody will install
|
||||
## Python into C:\PythonXX
|
||||
site_dir = site.getsitepackages()[1]
|
||||
|
||||
include_files = []
|
||||
include_files.append((os.path.join(site_dir, "shapely"), "shapely"))
|
||||
include_files.append((os.path.join(site_dir, "svg"), "svg"))
|
||||
include_files.append((os.path.join(site_dir, "svg/path"), "svg"))
|
||||
include_files.append((os.path.join(site_dir, "matplotlib"), "matplotlib"))
|
||||
include_files.append(("share", "share"))
|
||||
include_files.append((os.path.join(site_dir, "rtree"), "rtree"))
|
||||
include_files.append(("README.md", "README.md"))
|
||||
include_files.append(("LICENSE", "LICENSE"))
|
||||
|
||||
base = None
|
||||
|
||||
## Lets not open the console while running the app
|
||||
if sys.platform == "win32":
|
||||
base = "Win32GUI"
|
||||
|
||||
buildOptions = dict(
|
||||
include_files=include_files,
|
||||
# excludes=['PyQt4', 'tk', 'tcl']
|
||||
excludes=['scipy.lib.lapack.flapack.pyd',
|
||||
'scipy.lib.blas.fblas.pyd',
|
||||
'QtOpenGL4.dll']
|
||||
)
|
||||
|
||||
print(("INCLUDE_FILES", include_files))
|
||||
|
||||
exec(compile(open('clean.py').read(), 'clean.py', 'exec'))
|
||||
|
||||
setup(
|
||||
name="FlatCAM",
|
||||
author="Juan Pablo Caram",
|
||||
version="8.5",
|
||||
description="FlatCAM: 2D Computer Aided PCB Manufacturing",
|
||||
options=dict(build_exe=buildOptions),
|
||||
executables=[Executable("FlatCAM.py", icon='share/flatcam_icon48.ico', base=base)]
|
||||
)
|
|
@ -1,10 +0,0 @@
|
|||
# This file contains python only requirements to be installed with pip
|
||||
# Python pacakges that cannot be installed with pip (e.g. PyQT4) are not included.
|
||||
# Usage: pip install -r requirements.txt
|
||||
numpy>=1.8
|
||||
matplotlib>=1.3.1
|
||||
shapely>=1.3
|
||||
simplejson
|
||||
rtree
|
||||
scipy
|
||||
svg.path
|
75
setup.py
|
@ -1,75 +0,0 @@
|
|||
############################################################
|
||||
# FlatCAM: 2D Post-processing for Manufacturing #
|
||||
# http://flatcam.org #
|
||||
# Author: Damian Wrobel <dwrobel@ertelnet.rybnik.pl> #
|
||||
# Date: 05/23/2017 #
|
||||
# MIT Licence #
|
||||
# A setuptools based setup module #
|
||||
############################################################
|
||||
|
||||
from setuptools import setup
|
||||
import glob
|
||||
|
||||
setup(
|
||||
name='FlatCAM',
|
||||
|
||||
version='8.5',
|
||||
|
||||
description='2D Computer-Aided PCB Manufacturing',
|
||||
|
||||
long_description=('FlatCAM is a program for preparing CNC jobs for making'
|
||||
'PCBs on a CNC router. Among other things, it can take'
|
||||
'a Gerber file generated by your favorite PCB CAD'
|
||||
'program, and create G-Code for Isolation routing.'),
|
||||
|
||||
url='http://flatcam.org/',
|
||||
|
||||
author='Juan Pablo Caram',
|
||||
|
||||
license='MIT',
|
||||
|
||||
packages=[
|
||||
'descartes',
|
||||
'tclCommands'
|
||||
],
|
||||
|
||||
py_modules=[
|
||||
"camlib",
|
||||
"DblSidedTool",
|
||||
"FlatCAMApp",
|
||||
"FlatCAMCommon",
|
||||
"FlatCAMDraw",
|
||||
"FlatCAMGUI",
|
||||
"FlatCAMObj",
|
||||
"FlatCAMProcess",
|
||||
"FlatCAMShell",
|
||||
"FlatCAMTool",
|
||||
"FlatCAMVersion",
|
||||
"FlatCAMWorker",
|
||||
"GUIElements",
|
||||
"MeasurementTool",
|
||||
"ObjectCollection",
|
||||
"ObjectUI",
|
||||
"PlotCanvas",
|
||||
"svgparse",
|
||||
"termwidget"
|
||||
],
|
||||
|
||||
install_requires=[
|
||||
'simplejson',
|
||||
'numpy>=1.8',
|
||||
'scipy',
|
||||
'matplotlib>=1.3.1',
|
||||
'shapely>=1.3'
|
||||
'rtree',
|
||||
'svg.path'
|
||||
],
|
||||
|
||||
include_package_data=True,
|
||||
|
||||
data_files=[
|
||||
('share/flatcam', glob.glob('share/*'))
|
||||
],
|
||||
|
||||
scripts=['flatcam']
|
||||
)
|
|
@ -1,20 +0,0 @@
|
|||
#!/bin/sh -e
|
||||
# "-e" exists on first error.
|
||||
|
||||
apt-get install libpng-dev \
|
||||
libfreetype6 \
|
||||
libfreetype6-dev \
|
||||
python-dev \
|
||||
python-simplejson \
|
||||
python-qt4 \
|
||||
python-numpy \
|
||||
python-scipy \
|
||||
python-matplotlib \
|
||||
libgeos-dev \
|
||||
python-shapely \
|
||||
python-pip \
|
||||
libspatialindex-dev
|
||||
easy_install -U distribute
|
||||
pip install --upgrade matplotlib Shapely
|
||||
pip install rtree
|
||||
pip install svg.path
|
BIN
share/active.gif
Before Width: | Height: | Size: 1.5 KiB |
BIN
share/arc16.png
Before Width: | Height: | Size: 492 B |
BIN
share/arc24.png
Before Width: | Height: | Size: 652 B |
BIN
share/arc32.png
Before Width: | Height: | Size: 854 B |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 574 B |
BIN
share/bug16.png
Before Width: | Height: | Size: 509 B |
Before Width: | Height: | Size: 517 B |
Before Width: | Height: | Size: 788 B |
Before Width: | Height: | Size: 501 B |
Before Width: | Height: | Size: 720 B |
Before Width: | Height: | Size: 1.4 KiB |
BIN
share/cnc16.png
Before Width: | Height: | Size: 506 B |
BIN
share/cnc32.png
Before Width: | Height: | Size: 841 B |
BIN
share/copy16.png
Before Width: | Height: | Size: 444 B |
BIN
share/copy32.png
Before Width: | Height: | Size: 624 B |
Before Width: | Height: | Size: 957 B |
Before Width: | Height: | Size: 454 B |
Before Width: | Height: | Size: 523 B |
Before Width: | Height: | Size: 581 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 542 B |
Before Width: | Height: | Size: 679 B |
Before Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 385 B |
Before Width: | Height: | Size: 687 B |
BIN
share/edit16.png
Before Width: | Height: | Size: 526 B |
BIN
share/edit32.png
Before Width: | Height: | Size: 997 B |
Before Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 835 B |
BIN
share/file16.png
Before Width: | Height: | Size: 622 B |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 336 B |
Before Width: | Height: | Size: 521 B |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 603 B |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 833 B |
BIN
share/flipx.png
Before Width: | Height: | Size: 522 B |
BIN
share/flipy.png
Before Width: | Height: | Size: 514 B |
Before Width: | Height: | Size: 675 B |
Before Width: | Height: | Size: 373 B |
Before Width: | Height: | Size: 540 B |
BIN
share/gear32.png
Before Width: | Height: | Size: 670 B |
BIN
share/gear48.png
Before Width: | Height: | Size: 927 B |
Before Width: | Height: | Size: 615 B |
Before Width: | Height: | Size: 939 B |
Before Width: | Height: | Size: 1003 B |
Before Width: | Height: | Size: 445 B |
Before Width: | Height: | Size: 421 B |
BIN
share/grid16.png
Before Width: | Height: | Size: 674 B |
BIN
share/grid32.png
Before Width: | Height: | Size: 1.1 KiB |
BIN
share/home16.png
Before Width: | Height: | Size: 571 B |
BIN
share/info16.png
Before Width: | Height: | Size: 712 B |
Before Width: | Height: | Size: 487 B |
Before Width: | Height: | Size: 569 B |
Before Width: | Height: | Size: 659 B |
BIN
share/join16.png
Before Width: | Height: | Size: 508 B |
BIN
share/join32.png
Before Width: | Height: | Size: 869 B |
Before Width: | Height: | Size: 853 B |
Before Width: | Height: | Size: 511 B |
Before Width: | Height: | Size: 1010 B |
BIN
share/move32.png
Before Width: | Height: | Size: 659 B |
Before Width: | Height: | Size: 329 B |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 339 B |